--- name: grpc-standards description: "Use when working with gRPC services, protobuf definitions, Protocol Buffers, streaming RPCs, interceptors, or gRPC client configuration in .NET. Provides domain-specific rules layered on top of base architectural standards." --- # Skill: gRPC Service Standards ## Identity | Field | Value | |---|---| | **Name** | gRPC Service Standards | | **Domain** | API, Messaging, RPC | | **Level** | Feature | | **Tags** | `grpc`, `protobuf`, `streaming`, `interceptors`, `rpc` | ## When to Apply Activate this skill when the task involves: - Defining or editing `.proto` files - Implementing gRPC service classes - Server or client streaming patterns - gRPC interceptors (logging, auth, error mapping) - Deadline and timeout configuration - gRPC client registration and channel management ## Rules These rules layer on top of base architectural standards. On conflict, these win. # gRPC — Service Standards Apply these rules in addition to `_base.md` for projects using gRPC services. --- ## Protobuf Conventions - One `.proto` file per service — file name matches service name: `order_service.proto`. - Use `snake_case` for field names, `PascalCase` for message and service names (protobuf convention). - Set `option csharp_namespace` explicitly to control generated C# namespace. - Version APIs via package name: `package myapp.orders.v1;` — never reuse package names for breaking changes. - Every RPC method has a dedicated `Request` and `Response` message — never reuse messages across RPCs. - Use `google.protobuf.Timestamp` for dates, `google.protobuf.Duration` for time spans, `google.protobuf.StringValue` for nullable strings. ## Service Implementation - Inherit from generated `Base` class: `public class OrderService : Orders.OrdersBase`. - Keep service methods thin — delegate to Application layer handlers (same as Minimal API handlers). - Inject dependencies via constructor DI — gRPC services are registered as scoped by default. - Always accept `ServerCallContext` and propagate its `CancellationToken` to downstream calls. - Return domain errors via `RpcException` with appropriate `StatusCode` — never throw raw exceptions. ## Streaming Patterns - **Server streaming**: use `async` + `yield return` pattern or write to `IServerStreamWriter` in a loop. - **Client streaming**: read from `IAsyncStreamReader` until `MoveNext()` returns `false`. - **Bidirectional**: use `Task.WhenAll` to read and write concurrently, not sequentially. - Always check `context.CancellationToken` in streaming loops — abort cleanly on client disconnect. - Set reasonable `MaxReceiveMessageSize` and `MaxSendMessageSize` (default 4MB is often too small for file transfers). ## Deadlines & Timeouts - Every client call must set a deadline: `client.GetOrderAsync(req, deadline: DateTime.UtcNow.AddSeconds(5))`. - Propagate incoming deadlines to downstream calls — use `context.Deadline` for cascading timeouts. - Handle `StatusCode.DeadlineExceeded` explicitly in client error handling. - Set global channel options for default deadline: `GrpcChannelOptions.MaxReceiveMessageSize`. ## Error Handling - Map domain errors to gRPC status codes: | Domain Error | gRPC StatusCode | When | |---|---|---| | Not found | `NotFound` | Resource doesn't exist | | Validation failure | `InvalidArgument` | Bad request data | | Already exists | `AlreadyExists` | Duplicate create | | Permission denied | `PermissionDenied` | Auth check failed | | Unauthorized | `Unauthenticated` | No/invalid credentials | | Business rule violation | `FailedPrecondition` | State doesn't allow operation | | Timeout | `DeadlineExceeded` | Operation took too long | | Internal error | `Internal` | Unexpected server error | - Include detail metadata in `RpcException` for debugging — never expose stack traces. - Use interceptors for global error mapping — don't repeat `try/catch` in every RPC method. ## Interceptors - Use server interceptors for cross-cutting concerns: logging, metrics, auth, error mapping. - One concern per interceptor — same single-responsibility as middleware. - Register interceptors in `AddGrpc(options => options.Interceptors.Add())`. - Client interceptors for: retry, deadline propagation, auth token injection. ## Client Configuration - Use `GrpcChannel.ForAddress` with `IHttpClientFactory` for connection management. - Register typed clients via `AddGrpcClient()` in DI. - Enable retry via `ServiceConfig` — gRPC has built-in retry policy support. - Use `CallInvoker` for testing — mock at the channel level, not the service level. ## Common Anti-Patterns | Anti-Pattern | Fix | |---|---| | No deadline on client calls | Always set `deadline:` parameter | | Throwing `Exception` from service method | Return `RpcException(new Status(StatusCode.X, "msg"))` | | Large messages without streaming | Use server streaming for collections > 100 items | | Blocking in streaming loop | Use `async`/`await` with `CancellationToken` | | Reusing request/response messages across RPCs | One dedicated message pair per RPC method | | `new GrpcChannel()` per call | Use DI with `AddGrpcClient()` | | Missing `option csharp_namespace` in .proto | Generates messy namespace — always specify | | Sequential read/write in bidirectional stream | Use `Task.WhenAll` for concurrent read and write | --- ## See Also - `api-design.md` — RESTful conventions, HTTP methods, pagination, error responses - `resilience.md` — Polly v8 resilience pipelines, retry, circuit breaker, timeout - `auth.md` — Authentication, authorization, OAuth/OIDC, secrets management - `result-error-handling.md` — Result object pattern, error constants, exception boundaries