# 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/)