gRPC Service Specification Template (Protocol Buffers)
gRPC service spec: .proto contract, package versioning, field compatibility, google.rpc errors, deadlines, retries, streaming
Mode d'emploi: Use it before generating code for a new gRPC service or major version. Fill the .proto excerpt, package versioning and field numbering rules first, since field numbers are permanent. The main pitfall is calls without deadlines and retries without idempotency.
Aperçu
gRPC Service Specification: [package.ServiceName]
Specification of one gRPC service and its Protocol Buffers contract: purpose,
.protodefinitions, package and versioning, message and field conventions, compatibility rules, errors using thegoogle.rpcstatus codes, deadlines, retries, streaming, authentication, observability and load balancing. Based on the public gRPC and Protocol Buffers documentation (grpc.io, protobuf.dev); examples useproto3syntax, and notes cover Protobuf Editions (latest released edition: 2024). Replace every[bracketed]value and every row marked Example. Worked examples use a payments ledger service.
| Field | Value |
|---|---|
| Service | [payments.ledger.v1.LedgerService] |
| Proto repository / path | [git URL] proto/payments/ledger/v1/ |
| Spec version | [0.1] |
| Status | [Draft / In review / Approved / Deprecated] |
| Owning team | [@team-ledger] |
| Syntax | [proto3 / edition 2023 / edition 2024] |
| Endpoint(s) | [ledger.internal.example.com:443] |
| Last updated | YYYY-MM-DD |
1. Service purpose
[Example: LedgerService records money movements as immutable, double-entry journal entries and answers balance queries for accounts. It is called by Checkout, Refunds and Payouts. It does not talk to payment providers and does not calculate fees.]
| Consumer | Methods used | Traffic (peak) |
|---|---|---|
| Example: checkout-service | PostEntry, GetBalance | 1,200 rps |
| Example: payouts-worker | StreamEntries | 3 long-lived streams |
| [Consumer] | [Methods] | [Traffic] |
2. Protobuf definitions
The excerpt below is the contract for version 1 of the service.
syntax = "proto3";
package payments.ledger.v1;
option go_package = "example.com/gen/payments/ledger/v1;ledgerv1";
option java_multiple_files = true;
option java_package = "com.example.payments.ledger.v1";
import "google/protobuf/timestamp.proto";
import "google/type/money.proto";
// Records journal entries and answers balance queries.
service LedgerService {
// Posts a balanced journal entry. Idempotent on request_id.
rpc PostEntry(PostEntryRequest) returns (PostEntryResponse);
// Returns the current balance of one account.
rpc GetBalance(GetBalanceRequest) returns (GetBalanceResponse);
// Streams entries after a cursor, then follows new entries live.
rpc StreamEntries(StreamEntriesRequest) returns (stream JournalEntry);
}
message PostEntryRequest {
// Client-generated UUID; repeated calls with the same ID return the original result.
string request_id = 1;
repeated Posting postings = 2;
string description = 3;
map<string, string> metadata = 4;
}
message Posting {
string account_id = 1;
google.type.Money amount = 2;
Direction direction = 3;
}
enum Direction {
DIRECTION_UNSPECIFIED = 0;
DIRECTION_DEBIT = 1;
DIRECTION_CREDIT = 2;
}
message PostEntryResponse {
JournalEntry entry = 1;
}
message JournalEntry {
string entry_id = 1;
repeated Posting postings = 2;
google.protobuf.Timestamp posted_at = 3;
// Field 4 was "legacy_ref" and must not be reused.
reserved 4;
reserved "legacy_ref";
}
message GetBalanceRequest {
string account_id = 1;
}
message GetBalanceResponse {
google.type.Money balance = 1;
google.protobuf.Timestamp as_of = 2;
}
message StreamEntriesRequest {
string account_id = 1;
// Opaque cursor from a previous entry; empty starts from the beginning.
string after_cursor = 2;
}
3. Package and versioning
| Rule | Decision |
|---|---|
| Package naming | [company].[domain].[service].v[major], e.g. payments.ledger.v1 |
| Directory layout | Directory path mirrors the package |
| Major versions | New package v2; v1 and v2 served in parallel for at least [6 months] |
| Pre-release | v1alpha1, v1beta1 packages carry no compatibility promise |
| Generated code | Published as [internal package registry] artifacts per language; version follows the proto repository tag |
| Editions migration | [Stay on proto3 / Move to edition 2024 with prototiller after tooling support is confirmed for all client languages] |
4. Message and field conventions
- Messages and enums: PascalCase. Fields: lower_snake_case. Enum values: UPPER_SNAKE_CASE prefixed with the enum name.
- Every enum has a zero value
<ENUM>_UNSPECIFIED = 0that means "not set". - Every RPC has its own request and response messages (
<Method>Request,<Method>Response), even if they are small today. - Use well-known types:
google.protobuf.Timestamp,Duration,FieldMask; usegoogle.type.Moneyor a decimal string for money, neverfloatordouble. - Identifiers are strings, not integers, to allow format changes.
- Use
optional(proto3) or field presence (editions) only where "not set" and "default value" must be distinguished. - Partial updates take a
google.protobuf.FieldMask update_mask. - List methods use
page_size,page_tokenand returnnext_page_token. - Every service, RPC, message and field has a leading comment; it becomes the API reference.
5. Field numbering and compatibility
| Change | Compatible? | Rule |
|---|---|---|
| Add a new field with a new number | Yes | Old readers ignore unknown fields |
| Remove a field | Yes, if its number and name are reserved | Never reuse the number |
| Rename a field | Wire-compatible, breaks generated code and JSON | Treat as breaking |
| Change a field's type | No (except a few documented compatible pairs) | New field instead |
| Change a field number | No | Never |
| Add an enum value | Yes, if clients handle unknown values | Document in changelog |
Move a field into or out of a oneof | No | New field instead |
| Add or remove an RPC | Adding yes; removing is breaking | Deprecate first with option deprecated = true |
Field numbers 1 to 15 take one byte to encode; reserve them for frequently
set fields. Numbers 19000 to 19999 are reserved by Protocol Buffers.
Breaking change detection: [buf breaking / other tool] runs on every pull
request against the main branch, mode [WIRE_JSON].
6. Errors
| Code | Number | When this service returns it | Client action |
|---|---|---|---|
INVALID_ARGUMENT | 3 | Unbalanced entry, unknown direction, bad currency | Fix request; details in BadRequest |
DEADLINE_EXCEEDED | 4 | Deadline expired before completion | Retry with same request_id if idempotent |
NOT_FOUND | 5 | Account does not exist | Do not retry |
ALREADY_EXISTS | 6 | Not used: duplicates return the original result | n/a |
PERMISSION_DENIED | 7 | Caller not allowed to post to this account | Do not retry |
RESOURCE_EXHAUSTED | 8 | Per-client quota exceeded | Back off per RetryInfo |
FAILED_PRECONDITION | 9 | Account frozen or closed | Do not retry until state changes |
ABORTED | 10 | Concurrency conflict on balance update | Retry the whole operation |
UNIMPLEMENTED | 12 | Method not available in this version | Do not retry |
INTERNAL | 13 | Invariant broken; bug | Alert; do not retry blindly |
UNAVAILABLE | 14 | Transient: instance draining or overloaded | Retry with backoff |
UNAUTHENTICATED | 16 | Missing or invalid credentials | Refresh token, then retry |
Error details carry google.rpc.ErrorInfo with reason (for example
ACCOUNT_FROZEN) and domain (ledger.example.com). Messages are for
developers and MUST NOT contain account balances or personal data.
7. Deadlines, retries and limits
| RPC | Type | Recommended deadline | Idempotent | Retry policy | Max message size |
|---|---|---|---|---|---|
PostEntry | Unary | 2 s | Yes (request_id) | 3 attempts on UNAVAILABLE, backoff 100 ms to 1 s | 1 MB |
GetBalance | Unary | 300 ms | Yes | 3 attempts on UNAVAILABLE, DEADLINE_EXCEEDED | 64 KB |
StreamEntries | Server streaming | None (long-lived), keepalive 60 s | Resume via cursor | Reconnect with last cursor | 1 MB per message |
| [RPC] | [Type] | [Deadline] | [Yes / No] | [Policy] | [Size] |
Deadlines propagate: a server calling another service passes on the remaining time minus a margin. Retry budget: at most [10%] extra load from retries; hedging is [not used].
8. Streaming types
| Type | Used by | Notes |
|---|---|---|
| Unary | PostEntry, GetBalance | Default choice |
| Server streaming | StreamEntries | Ordered by posting time; each message carries a cursor for resumption |
| Client streaming | [Not used] | [Reason] |
| Bidirectional streaming | [Not used] | [Reason] |
The sequence shows how a stream consumer resumes after a disconnect.
9. Authentication and authorisation
| Layer | Mechanism |
|---|---|
| Transport | TLS 1.3; mutual TLS between services through [service mesh / SPIFFE identities] |
| Caller identity | Workload identity from mTLS certificate; end-user context as a JWT in authorization metadata |
| Authorisation | Per-method policy: PostEntry only for [checkout, refunds]; GetBalance for [listed callers] |
| Metadata | x-request-id propagated; no secrets in custom metadata keys |
10. Observability
- Metrics per method: request count by status code, latency histogram, in-flight streams, message sizes.
- Traces: OpenTelemetry gRPC instrumentation; trace context propagated in metadata.
- Logs: method, status code, duration, caller identity,
request_id; never message payloads with money amounts at INFO level. - Health: implements
grpc.health.v1.Health; readiness fails while draining. - Reflection: enabled in [non-production only].
11. Load balancing and deployment
| Topic | Decision |
|---|---|
| Balancing | [Client-side round_robin via DNS / xDS] or [L7 proxy (service mesh)] |
| Connection management | MAX_CONNECTION_AGE [5 min] on servers so clients rebalance |
| Keepalive | Client pings every [60 s] on idle streams; server permits it |
| Draining | Server sends GOAWAY and fails health checks before shutdown |
| Browser clients | [gRPC-Web through proxy / not supported] |
12. Changelog
| Date | Proto tag | Change | Compatible |
|---|---|---|---|
| YYYY-MM-DD | Example: v1.3.0 | Added metadata to PostEntryRequest | Yes |
| YYYY-MM-DD | [Tag] | [Change] | [Yes / No] |
Standard and licence note
Based on the public gRPC documentation (https://grpc.io/docs/) and the
Protocol Buffers language guides and Editions overview
(https://protobuf.dev/), including the canonical status codes and the
google.rpc error model. Examples use proto3 syntax; Protobuf Editions
2023 and 2024 are the released editions at the time of writing. All text and
examples are original to this collection. Template licence: MIT.
Modèles associés
Notification Service System Design Template (Reference Design)
Push, email, SMS and in-app: templates, preferences and opt-out, priority queues, caps, provider failover, retries, dedup
Architecture & Conception systèmeGraphQL Schema Specification Template
GraphQL API spec: types, queries, mutations, subscriptions, connections, nullability, errors, auth, cost limits, federation
Architecture & Conception systèmeMicroservice Specification Template (Service Canvas)
Service canvas: bounded context, owner, APIs and events, data owned, dependencies, SLOs, scaling, config, deploy, observability