Services and the four RPC kinds

gRPC · Essentials · 10 min

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 XxxRequest and XxxResponse messages, even if they look trivial now. You can add fields later without breaking the signature; reusing User as a request type paints you into a corner.
  • Version the package: package billing.v1;. Breaking changes go into v2 alongside v1.
  • Use google.protobuf.Empty sparingly 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.

Check yourself

  1. Which RPC kind fits uploading a large file in chunks and getting one summary back?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: grpc.io/docs/what-is-grpc/core-concepts/