gRPC API Reference
Service: orders.OrderService Server: order-api Port: 5002 (HTTP/2-only,
cluster-internal — order-api’s separate 5001 is HTTP/1.1-only, /healthz alone) Protocol:
gRPC over HTTP/2 with protobuf encoding Client:
gateway-api (uses Grpc.Net.Client) — see
the REST API reference for the browser-facing side of the
same request.
Proto definition: src/proto/orders.proto (single source of truth; both order-api and gateway-api
reference it via a relative <Protobuf Include> rather than each keeping their own copy)
Service definition
syntax = "proto3";
package orders;
service OrderService {
rpc CreateOrder (CreateOrderRequest) returns (CreateOrderResponse);
rpc GetOrdersByProject (GetOrdersByProjectRequest) returns (stream OrderResponse);
rpc GetOrder (GetOrderRequest) returns (OrderResponse);
}
RPC: CreateOrder
Persists the order and an outbox row to
PostgreSQL in one transaction, then returns — it does not publish to RabbitMQ itself.
OutboxRelayWorker picks up the outbox row on its next poll (≤5s later) and publishes the
order.created event from there, with W3C traceparent (captured at write time) in the message
headers. See
order-api.md for the
full outbox flow and its trace-shape implications.
Request
message CreateOrderRequest {
int32 project_id = 1; // Must be > 0
string description = 2; // Non-empty, ≤ 500 chars
double amount = 3; // > 0 and ≤ 999999.99
string idempotency_key = 4; // Optional. Same key on a retried call replays the
// original order instead of duplicating it.
}
Response
message CreateOrderResponse {
int32 order_id = 1; // ID of the created order
string status = 2; // "Created"
}
Error codes
| gRPC status | Condition |
|---|---|
INVALID_ARGUMENT | project_id ≤ 0, amount out of range, or description empty/too long |
INTERNAL | Database write (order + outbox row) failed |
UNAVAILABLE | PostgreSQL unreachable |
A RabbitMQ outage does not surface as an error here — publish happens later, out-of-band, in
OutboxRelayWorker. CreateOrder only touches PostgreSQL.
RPC: GetOrdersByProject
Server-streaming RPC. Streams all orders for a given project, ordered by created_at descending.
Rows are streamed directly from the PostgreSQL cursor using AsAsyncEnumerable() — memory usage is
O(1) regardless of result set size.
Request
message GetOrdersByProjectRequest {
int32 project_id = 1; // Must be > 0
}
Response stream
message OrderResponse {
int32 id = 1;
int32 project_id = 2;
string description = 3;
double amount = 4;
string status = 5; // Created | Processing | Completed | Failed
string created_at = 6; // ISO 8601 UTC
}
Behaviour
- Returns zero messages if no orders exist for the project (not an error)
- Respects client cancellation: if the gRPC client disconnects, the DB cursor is cancelled via
CancellationToken - Error codes:
INVALID_ARGUMENT(project_id ≤ 0),INTERNAL(DB error)
RPC: GetOrder
Get a single order by ID.
Request
message GetOrderRequest {
int32 order_id = 1;
}
Response
OrderResponse (same message as above).
Error codes
| gRPC status | Condition |
|---|---|
NOT_FOUND | No order with that ID |
INVALID_ARGUMENT | order_id ≤ 0 |
Trace propagation
gRPC metadata is the carrier for W3C traceparent. gateway-api’s AddGrpcClientInstrumentation()
injects this automatically in outbound calls.
gateway-api (gRPC client)
→ Outbound metadata: traceparent=00-4bf92f...-00f067...-01
order-api (gRPC server)
← AddAspNetCoreInstrumentation() reads metadata
← Creates HTTP server span as child of gateway-api's gRPC client span
Custom span attributes set by order-api on the order.create internal span:
order.id— after DB insertorder.project_id— from requestorder.amount— from request
These attributes appear in Jaeger and can be used as search filters.
OTel span kinds
| RPC | Span created by gateway-api | Span created by order-api |
|---|---|---|
CreateOrder | gRPC CLIENT span (rpc.method=CreateOrder) | gRPC SERVER span + order.create INTERNAL span |
GetOrdersByProject | gRPC CLIENT span (streaming) | gRPC SERVER span (streaming) |
GetOrder | gRPC CLIENT span | gRPC SERVER span |
order.publish is not an inline child of order.create: CreateOrder persists the order/outbox
row and returns, while OutboxRelayWorker publishes later. The worker restores the persisted
request context as outbox.relay’s parent and adds an ActivityLink; order.publish is the relay
span’s child. The whole chain keeps the original trace ID, while retry attempts remain visible as
separate asynchronous work. notification-svc then creates a linked CONSUMER span from the same
stored traceparent. See OutboxRelayWorker.cs and OrderPublisher.cs for the implementation.
Generating gRPC client code
The proto file is included in each .NET project’s .csproj:
<ItemGroup>
<Protobuf Include="..\proto\orders.proto" GrpcServices="Server" /> <!-- order-api -->
<Protobuf Include="..\proto\orders.proto" GrpcServices="Client" /> <!-- gateway-api -->
</ItemGroup>
Code generation runs at build time. The generated client (OrderService.OrderServiceClient) and
server base class (OrderService.OrderServiceBase) are available in the Orders namespace.
Any schema change to orders.proto requires rebuilding both order-api and gateway-api,
re-importing images, and redeploying.