# gRPC Service Specification: [package.ServiceName]

> Specification of one gRPC service and its Protocol Buffers contract:
> purpose, `.proto` definitions, package and versioning, message and field
> conventions, compatibility rules, errors using the `google.rpc` status
> codes, deadlines, retries, streaming, authentication, observability and
> load balancing. Based on the public gRPC and Protocol Buffers
> documentation (grpc.io, protobuf.dev); examples use `proto3` syntax, 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.

<!--
Guidance: How to use this template
- The .proto files in the repository are the contract. This document
  explains intent, rules and operational behaviour that a .proto file
  cannot express (deadlines, retries, errors per method, limits).
- Fill sections 1 to 4 before code generation starts; field numbers are
  permanent once a client ships.
- Run breaking change detection on .proto files in CI and record the tool
  in section 5.
- Keep one table row per RPC in section 6 and section 7; reviewers check
  every method against them.
-->

| 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

<!-- Guidance: What the service does, for whom, and what it deliberately does not do. Two paragraphs at most. -->

[*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

<!-- Guidance: Show the service and its key messages. Paste the authoritative file or a faithful excerpt and link the full file. -->

The excerpt below is the contract for version 1 of the service.

```proto
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

<!-- Guidance: The package name carries the major version. A breaking change means a new package (v2) served side by side, never an edit to v1. -->

| 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

<!-- Guidance: Consistent style keeps generated code readable in every language. Most rules can be enforced with a linter. -->

- 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 = 0` that 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`; use `google.type.Money` or a decimal string for money, never `float` or `double`.
- 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_token` and return `next_page_token`.
- Every service, RPC, message and field has a leading comment; it becomes the API reference.

## 5. Field numbering and compatibility

<!-- Guidance: Field numbers, not names, identify fields on the wire. Changing or reusing them silently corrupts data between old and new clients. -->

| 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

<!-- Guidance: Use the canonical gRPC status codes consistently and attach structured details from google.rpc (ErrorInfo, BadRequest, RetryInfo, ResourceInfo) so clients can act without parsing messages. -->

| 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

<!-- Guidance: Every call needs a deadline; without one, a slow dependency ties up resources indefinitely. Define retries in the service config so they are consistent across clients. -->

| 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

<!-- Guidance: State which of the four RPC types each method uses and how flow control, ordering and resumption work. -->

| 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.

```mermaid
sequenceDiagram
  participant P as payouts-worker
  participant L as LedgerService
  P->>L: StreamEntries(after_cursor = "")
  L-->>P: JournalEntry (cursor c1)
  L-->>P: JournalEntry (cursor c2)
  Note over P,L: connection drops (UNAVAILABLE)
  P->>L: StreamEntries(after_cursor = "c2")
  L-->>P: JournalEntry (cursor c3)
```

## 9. Authentication and authorisation

<!-- Guidance: Transport security plus caller identity plus per-method 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

<!-- Guidance: What every call emits so incidents can be diagnosed from the caller and the server side. -->

- 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

<!-- Guidance: gRPC uses long-lived HTTP/2 connections, so connection-level (L4) balancing spreads load poorly. State how requests are balanced. -->

| 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

<!-- Guidance: Summarise contract changes so consumers can see what changed between tags. -->

| 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.
