# 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
```
### 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();
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().EnableGrpcWeb();
app.MapGrpcService().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(options =>
{
options.Address = new Uri("https://localhost:5001");
});
builder.Services.AddGrpcClient(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 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/)