400 lines
8.4 KiB
Markdown
400 lines
8.4 KiB
Markdown
# gRPC Clients
|
|
|
|
Building clients to consume gRPC services.
|
|
|
|
## C# Client
|
|
|
|
### Installation
|
|
|
|
```bash
|
|
dotnet add package Grpc.Net.Client
|
|
dotnet add package Google.Protobuf
|
|
dotnet add package Grpc.Tools
|
|
```
|
|
|
|
### Generate Client Code
|
|
|
|
**.csproj:**
|
|
```xml
|
|
<ItemGroup>
|
|
<Protobuf Include="Protos\cqrs_services.proto" GrpcServices="Client" />
|
|
</ItemGroup>
|
|
```
|
|
|
|
### Basic Client
|
|
|
|
```csharp
|
|
using Grpc.Net.Client;
|
|
using MyApp.Grpc;
|
|
|
|
// Create channel
|
|
var channel = GrpcChannel.ForAddress("https://localhost:5001");
|
|
|
|
// Create clients
|
|
var commandClient = new CommandService.CommandServiceClient(channel);
|
|
var queryClient = new QueryService.QueryServiceClient(channel);
|
|
|
|
// Call CreateUser
|
|
var createResponse = await commandClient.CreateUserAsync(new CreateUserCommand
|
|
{
|
|
Name = "John Doe",
|
|
Email = "john@example.com"
|
|
});
|
|
|
|
Console.WriteLine($"Created user: {createResponse.UserId}");
|
|
|
|
// Call GetUser
|
|
var user = await queryClient.GetUserAsync(new GetUserQuery
|
|
{
|
|
UserId = createResponse.UserId
|
|
});
|
|
|
|
Console.WriteLine($"User: {user.Name}, {user.Email}");
|
|
|
|
// Cleanup
|
|
await channel.ShutdownAsync();
|
|
```
|
|
|
|
### With Error Handling
|
|
|
|
```csharp
|
|
using Grpc.Core;
|
|
using Google.Rpc;
|
|
|
|
try
|
|
{
|
|
var user = await queryClient.GetUserAsync(new GetUserQuery { UserId = 999 });
|
|
}
|
|
catch (RpcException ex) when (ex.StatusCode == StatusCode.NotFound)
|
|
{
|
|
Console.WriteLine("User not found");
|
|
}
|
|
catch (RpcException ex) when (ex.StatusCode == StatusCode.InvalidArgument)
|
|
{
|
|
var status = ex.GetRpcStatus();
|
|
var badRequest = status.GetDetail<BadRequest>();
|
|
|
|
foreach (var violation in badRequest.FieldViolations)
|
|
{
|
|
Console.WriteLine($"{violation.Field}: {violation.Description}");
|
|
}
|
|
}
|
|
catch (RpcException ex)
|
|
{
|
|
Console.WriteLine($"gRPC error: {ex.Status}");
|
|
}
|
|
```
|
|
|
|
### With Deadlines
|
|
|
|
```csharp
|
|
var deadline = DateTime.UtcNow.AddSeconds(5);
|
|
|
|
var user = await queryClient.GetUserAsync(
|
|
new GetUserQuery { UserId = 42 },
|
|
deadline: deadline);
|
|
```
|
|
|
|
### With Metadata
|
|
|
|
```csharp
|
|
var metadata = new Metadata
|
|
{
|
|
{ "Authorization", "Bearer token..." },
|
|
{ "X-Request-ID", Guid.NewGuid().ToString() }
|
|
};
|
|
|
|
var user = await queryClient.GetUserAsync(
|
|
new GetUserQuery { UserId = 42 },
|
|
headers: metadata);
|
|
```
|
|
|
|
## TypeScript Client (grpc-web)
|
|
|
|
### Installation
|
|
|
|
```bash
|
|
npm install grpc-web
|
|
npm install google-protobuf
|
|
npm install --save-dev @types/google-protobuf
|
|
```
|
|
|
|
### Generate Code
|
|
|
|
```bash
|
|
protoc -I=. cqrs_services.proto \
|
|
--js_out=import_style=commonjs:. \
|
|
--grpc-web_out=import_style=typescript,mode=grpcwebtext:.
|
|
```
|
|
|
|
### Basic Client
|
|
|
|
```typescript
|
|
import { CommandServiceClient } from './cqrs_services_grpc_web_pb';
|
|
import { CreateUserCommand, GetUserQuery } from './cqrs_services_pb';
|
|
|
|
const client = new CommandServiceClient('http://localhost:5000');
|
|
|
|
// Create user
|
|
const createRequest = new CreateUserCommand();
|
|
createRequest.setName('John Doe');
|
|
createRequest.setEmail('john@example.com');
|
|
|
|
client.createUser(createRequest, {}, (err, response) => {
|
|
if (err) {
|
|
console.error('Error:', err.message);
|
|
return;
|
|
}
|
|
|
|
console.log('Created user:', response.getUserId());
|
|
});
|
|
|
|
// With promises
|
|
const createUser = async () => {
|
|
const request = new CreateUserCommand();
|
|
request.setName('Jane Doe');
|
|
request.setEmail('jane@example.com');
|
|
|
|
try {
|
|
const response = await client.createUser(request, {});
|
|
return response.getUserId();
|
|
} catch (error) {
|
|
console.error('Error:', error);
|
|
throw error;
|
|
}
|
|
};
|
|
```
|
|
|
|
### Server Configuration for grpc-web
|
|
|
|
```csharp
|
|
builder.Services.AddGrpc();
|
|
builder.Services.AddCors(options =>
|
|
{
|
|
options.AddPolicy("AllowGrpcWeb", policy =>
|
|
{
|
|
policy.WithOrigins("http://localhost:3000")
|
|
.AllowAnyHeader()
|
|
.AllowAnyMethod()
|
|
.WithExposedHeaders("Grpc-Status", "Grpc-Message", "Grpc-Encoding", "Grpc-Accept-Encoding");
|
|
});
|
|
});
|
|
|
|
var app = builder.Build();
|
|
|
|
app.UseGrpcWeb();
|
|
app.UseCors("AllowGrpcWeb");
|
|
|
|
app.MapGrpcService<CommandServiceImpl>().EnableGrpcWeb();
|
|
app.MapGrpcService<QueryServiceImpl>().EnableGrpcWeb();
|
|
```
|
|
|
|
## Go Client
|
|
|
|
### Generate Code
|
|
|
|
```bash
|
|
protoc --go_out=. --go-grpc_out=. cqrs_services.proto
|
|
```
|
|
|
|
### Basic Client
|
|
|
|
```go
|
|
package main
|
|
|
|
import (
|
|
"context"
|
|
"log"
|
|
"time"
|
|
|
|
"google.golang.org/grpc"
|
|
pb "myapp/grpc"
|
|
)
|
|
|
|
func main() {
|
|
conn, err := grpc.Dial("localhost:5001", grpc.WithInsecure())
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
defer conn.Close()
|
|
|
|
cmdClient := pb.NewCommandServiceClient(conn)
|
|
queryClient := pb.NewQueryServiceClient(conn)
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
|
|
defer cancel()
|
|
|
|
// Create user
|
|
createResp, err := cmdClient.CreateUser(ctx, &pb.CreateUserCommand{
|
|
Name: "John Doe",
|
|
Email: "john@example.com",
|
|
})
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
|
|
log.Printf("Created user: %d", createResp.UserId)
|
|
|
|
// Get user
|
|
user, err := queryClient.GetUser(ctx, &pb.GetUserQuery{
|
|
UserId: createResp.UserId,
|
|
})
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
|
|
log.Printf("User: %s, %s", user.Name, user.Email)
|
|
}
|
|
```
|
|
|
|
## Python Client
|
|
|
|
### Installation
|
|
|
|
```bash
|
|
pip install grpcio grpcio-tools
|
|
```
|
|
|
|
### Generate Code
|
|
|
|
```bash
|
|
python -m grpc_tools.protoc -I. --python_out=. --grpc_python_out=. cqrs_services.proto
|
|
```
|
|
|
|
### Basic Client
|
|
|
|
```python
|
|
import grpc
|
|
import cqrs_services_pb2
|
|
import cqrs_services_pb2_grpc
|
|
|
|
channel = grpc.insecure_channel('localhost:5001')
|
|
|
|
cmd_stub = cqrs_services_pb2_grpc.CommandServiceStub(channel)
|
|
query_stub = cqrs_services_pb2_grpc.QueryServiceStub(channel)
|
|
|
|
# Create user
|
|
create_response = cmd_stub.CreateUser(
|
|
cqrs_services_pb2.CreateUserCommand(
|
|
name='John Doe',
|
|
email='john@example.com'
|
|
)
|
|
)
|
|
|
|
print(f'Created user: {create_response.user_id}')
|
|
|
|
# Get user
|
|
user = query_stub.GetUser(
|
|
cqrs_services_pb2.GetUserQuery(user_id=create_response.user_id)
|
|
)
|
|
|
|
print(f'User: {user.name}, {user.email}')
|
|
|
|
channel.close()
|
|
```
|
|
|
|
## Connection Management
|
|
|
|
### Reusing Channels
|
|
|
|
```csharp
|
|
// ✅ Good - Reuse channel
|
|
public class GrpcClientFactory
|
|
{
|
|
private readonly GrpcChannel _channel;
|
|
|
|
public GrpcClientFactory(string address)
|
|
{
|
|
_channel = GrpcChannel.ForAddress(address);
|
|
}
|
|
|
|
public CommandService.CommandServiceClient CreateCommandClient()
|
|
{
|
|
return new CommandService.CommandServiceClient(_channel);
|
|
}
|
|
|
|
public QueryService.QueryServiceClient CreateQueryClient()
|
|
{
|
|
return new QueryService.QueryServiceClient(_channel);
|
|
}
|
|
|
|
public async Task ShutdownAsync()
|
|
{
|
|
await _channel.ShutdownAsync();
|
|
}
|
|
}
|
|
|
|
// ❌ Bad - New channel per call
|
|
var channel = GrpcChannel.ForAddress("https://localhost:5001");
|
|
var client = new CommandService.CommandServiceClient(channel);
|
|
await client.CreateUserAsync(command);
|
|
await channel.ShutdownAsync(); // Expensive!
|
|
```
|
|
|
|
### Dependency Injection
|
|
|
|
```csharp
|
|
builder.Services.AddGrpcClient<CommandService.CommandServiceClient>(options =>
|
|
{
|
|
options.Address = new Uri("https://localhost:5001");
|
|
});
|
|
|
|
builder.Services.AddGrpcClient<QueryService.QueryServiceClient>(options =>
|
|
{
|
|
options.Address = new Uri("https://localhost:5001");
|
|
});
|
|
|
|
// Usage in service
|
|
public class UserService
|
|
{
|
|
private readonly CommandService.CommandServiceClient _commandClient;
|
|
private readonly QueryService.QueryServiceClient _queryClient;
|
|
|
|
public UserService(
|
|
CommandService.CommandServiceClient commandClient,
|
|
QueryService.QueryServiceClient queryClient)
|
|
{
|
|
_commandClient = commandClient;
|
|
_queryClient = queryClient;
|
|
}
|
|
|
|
public async Task<int> CreateUserAsync(string name, string email)
|
|
{
|
|
var response = await _commandClient.CreateUserAsync(new CreateUserCommand
|
|
{
|
|
Name = name,
|
|
Email = email
|
|
});
|
|
|
|
return response.UserId;
|
|
}
|
|
}
|
|
```
|
|
|
|
## Best Practices
|
|
|
|
### ✅ DO
|
|
|
|
- Reuse GrpcChannel instances
|
|
- Use dependency injection for clients
|
|
- Set appropriate deadlines
|
|
- Handle errors appropriately
|
|
- Use metadata for tracing
|
|
- Close channels when done
|
|
- Use connection pooling
|
|
|
|
### ❌ DON'T
|
|
|
|
- Don't create new channels per request
|
|
- Don't ignore exceptions
|
|
- Don't skip deadlines
|
|
- Don't hardcode server addresses
|
|
- Don't forget to dispose channels
|
|
|
|
## See Also
|
|
|
|
- [gRPC Integration Overview](README.md)
|
|
- [Getting Started](getting-started-grpc.md)
|
|
- [gRPC Troubleshooting](grpc-troubleshooting.md)
|
|
- [gRPC .NET Documentation](https://learn.microsoft.com/en-us/aspnet/core/grpc/)
|