Designing Service Contracts
You'll learn to
- -Structure a multi-service proto definition with clear service boundaries
- -Use protobuf's well-known types instead of reinventing common shapes like timestamps
A real gRPC system is rarely one giant service - it's usually several focused services, each owning a clear domain, calling each other where needed. Designing these boundaries well is the gRPC equivalent of the resource-modeling discipline from the REST modules, just applied to service responsibilities instead of URL paths.
Multi-Service Design
service OrderService {
rpc CreateOrder (CreateOrderRequest) returns (Order);
rpc GetOrder (GetOrderRequest) returns (Order);
}
service InventoryService {
rpc CheckStock (CheckStockRequest) returns (StockLevel);
rpc ReserveStock (ReserveStockRequest) returns (ReservationResult);
}Splitting `OrderService` and `InventoryService` rather than one combined `CommerceService` mirrors the same single-responsibility instinct from LLD Fundamentals - each service can be owned, versioned, scaled, and deployed independently by whichever team owns that domain, and a client depending only on order operations doesn't need to know inventory-specific message types exist at all.
Empty Messages: Explicit, Not Absent
service HealthService {
rpc Check (HealthCheckRequest) returns (HealthCheckResponse);
}
message HealthCheckRequest {} // deliberately empty - but still its own type
message HealthCheckResponse {
bool healthy = 1;
}Even an RPC that takes no meaningful input still gets a dedicated (if empty) request message type, rather than protobuf allowing a method to take literally no argument - this keeps every RPC's signature consistent and, importantly, gives room to add a field to that request later (say, an optional `include_details` flag on the health check) without changing the method's signature itself, the same forward-compatibility reasoning behind using input types for GraphQL mutations.
Well-Known Types: Don't Reinvent Timestamp
import "google/protobuf/timestamp.proto";
message Order {
int64 id = 1;
google.protobuf.Timestamp placed_at = 2; // standardized, not a raw int64 or string
}Protobuf ships a set of "well-known types" for exactly these common, easy-to-get-wrong shapes - `Timestamp`, `Duration`, `Empty`, wrapper types for nullable primitives - and using them instead of reinventing a bespoke `int64 placed_at_unix_seconds = 2` field means every service that needs a timestamp represents it identically, with generated code in every language handling the conversion to/from that language's native date/time type consistently.
Reaching for a well-known type before inventing a custom representation for a common shape (time, duration, an empty request) is the protobuf equivalent of using a standard library data structure instead of hand-rolling your own - it is more likely to be correct, and every other engineer who touches the schema already understands it.
Interview Signal is part of Pro
See a real weak answer next to a real strong one for this exact topic.
Quiz is part of Pro
Test what you just read with a short quiz, and bank the XP.
Design Service Contracts in the API Design Lab's gRPC & Event-Driven act.