Choose the RPC pattern that matches the interaction shape, not the convenience of the client. Use unary for a single request and single response, client streaming when the server needs to aggregate multiple client messages, and server streaming when one request should produce a sequence of updates. The protobuf contract should make that choice explicit so clients and servers stay aligned.
Matching RPC shape to the job the service actually does
Unary, client-streaming, and server-streaming RPCs are not style choices, they are contract choices. Unary fits a single request that naturally produces a single answer, such as a lookup or command. Client streaming fits cases where the server must absorb a series of inputs before it can decide or aggregate. server streaming fits one request that should yield multiple ordered results or progressive updates.
The practical test is whether the protocol needs a one-to-one exchange, a client-driven batch, or a server-driven feed. That distinction affects how callers handle latency, retry behaviour, buffering, and partial progress. If the interaction shape is wrong, the API may still function, but it will force awkward client logic and brittle server semantics.
Another useful way to think about it is contract clarity. The protobuf service definition should make the interaction model obvious, so consumers do not have to infer whether they are supposed to poll, batch, or subscribe. A clear RPC shape reduces accidental misuse and makes later code review, test design, and versioning decisions much easier.
How to choose the RPC pattern without overengineering
Start from the data flow, not from implementation convenience. Use unary when the operation is fundamentally discrete and the caller needs a single completion signal. Use client streaming when the server can only produce a meaningful result after it has seen multiple messages, such as telemetry aggregation, upload processing, or incremental validation. Use server streaming when the server owns the progression of results, such as search results, event feeds, or status updates.
The main tradeoff is simplicity versus interaction fit. Unary is easiest to reason about, test, and retry, but it becomes awkward when the payload is really a sequence. Streaming can improve efficiency and responsiveness, but it also introduces statefulness, flow control, and message ordering concerns that unary does not have. Teams should only take on that complexity when the conversation naturally needs it.
Resist the temptation to choose streaming just because it feels more modern, or unary just because it is familiar. In gRPC, the RPC type is part of the API meaning. If the service is really collecting items, emitting a sequence, or producing updates over time, the contract should say so directly rather than hiding that behaviour behind repeated unary calls.
Standards & Framework Alignment
This section maps relevant standards and security frameworks to the operational risks and controls described in this guidance.
CIS Controls v8 and NIST CSF 2.0 set the governance and control requirements practitioners need to meet.
| Framework | Control / Reference | Relevance |
|---|---|---|
| CIS Controls v8 | CIS Control 16 — Application Software Security | API RPC design affects interface clarity and misuse resistance. |
| Recommendation — Define RPC contracts clearly and review them for misuse-prone interface patterns. | ||
| NIST CSF 2.0 | GV.1 — Cybersecurity Risk Management Strategy | RPC type selection is an architecture decision that shapes service risk and maintainability. |
| Recommendation — Use a governance review to align RPC patterns with service risk and operational goals. | ||
Practitioner Guidance
What to verify: Confirm whether the server needs the whole input set before it can compute a result, or whether results are naturally available incrementally. If the answer changes when you imagine retries, cancellation, or partial failure, the RPC shape probably matters more than the first implementation draft suggests.
Decision rule: Choose unary for isolated actions and lookups, client streaming for client-produced batches, and server streaming for server-produced sequences. If you find yourself adding ad hoc polling, batching wrappers, or repeated calls to simulate one of those patterns, the RPC type is likely mismatched.
What good looks like: The service definition tells a new consumer what kind of conversation to expect without extra documentation, and the implementation can handle that conversation cleanly without hidden control flow.
Practitioner takeaway: Pick the RPC shape that matches the semantics of the exchange, because the right contract prevents accidental complexity in both client code and server behaviour.
Related resources from NHI Mgmt Group
- What is the difference between unary gRPC calls and server streaming in a service design?
- How should teams choose between static generation, server-side rendering, and client-side fetching in Next.js?
- How should security teams choose between API keys, Device Flow, and Client Credentials for CLI apps?
- How should security teams choose between OAuth flows for different client types?
Deepen Your Knowledge
Reviewed and updated by the NHIMG editorial team on September 23, 2026.
NHI Mgmt Group — the #1 independent authority on Non-Human Identity, IAM, and Agentic AI security. nhimg.org