The method shape determines how client and server code interact.
A service groups RPC methods. Each method takes exactly one request message type and returns one response message type; the stream keyword turns either side into a sequence:
- Unary:
rpc GetUser(GetUserRequest) returns (User);one request, one response. The common case. - Server streaming:
returns (stream Event): the client sends one request, the server sends many messages (e.g. a large result set, live updates). - Client streaming:
rpc Upload(stream Chunk) returns (UploadSummary): the client sends many, the server answers once (uploads, batching). - Bidirectional streaming: both sides stream independently over the same call (chat, real-time sync).
Conventions from Google's API design guide that most teams follow:
- Give every method its own
XxxRequestandXxxResponsemessages, even if they look trivial now. You can add fields later without breaking the signature; reusingUseras a request type paints you into a corner. - Version the package:
package billing.v1;. Breaking changes go intov2alongsidev1. - Use
google.protobuf.Emptysparingly for the same reason.
The full method name on the wire is /package.Service/Method, e.g. /billing.v1.Invoices/GetInvoice; you will see it in logs, interceptors and proxies.
Example
syntax = "proto3";
package chat.v1;
service ChatService {
// unary
rpc GetRoom(GetRoomRequest) returns (GetRoomResponse);
// server streaming: one request, many messages
rpc WatchRoom(WatchRoomRequest) returns (stream RoomEvent);
// client streaming: many requests, one response
rpc UploadHistory(stream HistoryChunk) returns (UploadHistoryResponse);
// bidirectional streaming
rpc Chat(stream ChatMessage) returns (stream ChatMessage);
}
message GetRoomRequest { string room_id = 1; }
message GetRoomResponse { string name = 1; int32 members = 2; }
message WatchRoomRequest { string room_id = 1; }
message RoomEvent { string text = 1; }
message HistoryChunk { bytes data = 1; }
message UploadHistoryResponse { int64 bytes_received = 1; }
message ChatMessage { string from = 1; string text = 2; }Remember
- Four shapes: unary, server, client and bidirectional streaming
- Dedicated Request/Response messages per method
- Version packages (
foo.v1) and add v2 for breaking changes
Watch out for
- Reusing a domain message as a request type, which blocks adding request-only fields later.