19.2 OpenAPI, gRPC, AsyncAPI, and Contract Evolution
A contract turns paths, messages, fields, errors, and compatibility promises into artifacts that teams can review, generate code from, and test. It is not a pretty auto-generated documentation page, but a shared boundary that both producers and consumers rely on.
Choose a Contract Format
| Scenario | Common Contract | Focus Areas |
|---|---|---|
| HTTP requests/responses | OpenAPI | Paths, methods, parameters, schema, security, and responses |
| High-performance RPC/streaming services | Protocol Buffers + gRPC | Methods, message field numbers, deadlines, status codes, and stream semantics |
| Message and event APIs | AsyncAPI | Channels, operations, messages, protocol bindings, and schema |
The choice of protocol depends on the client ecosystem, browser requirements, streaming interactions, performance, governance, and long-term compatibility. No single protocol is optimal for all use cases or boundaries.
OpenAPI Describes HTTP Contracts
This minimal contract queries public tournaments. security: [] explicitly allows anonymous reads, and IDs use the decimal strings from the previous lesson. openapi identifies the specification version; info.version identifies this API version. Confirm that generators, linters, and gateways support 3.2.0 before using it. Only the success body is defined here; production error bodies and other operations need additional contracts.
openapi: 3.2.0
info:
title: Tournament API
version: 1.4.0
paths:
/tournaments/{id}:
get:
operationId: getTournament
security: []
parameters:
- name: id
in: path
required: true
schema: { type: string, pattern: '^[0-9]+$' }
responses:
'200':
description: Public tournament found
content:
application/json:
schema:
type: object
required: [id, name]
properties:
id: { type: string, pattern: '^[0-9]+$' }
name: { type: string, minLength: 1 }
example: { id: '8472', name: 'Autumn Cup' }
'400':
description: Invalid identifier
'404':
description: Public tournament not foundThe contract should be validated during review and in CI/CD: unique operationId, authentication claims, all status responses, pagination and idempotency requirements, and examples must pass schema validation.
Automatically generated clients can reduce manual errors, but generated code cannot replace semantic design. SDKs must also handle retries, deadlines, authentication, observability, and versioning policies.
gRPC Contracts Protect Field Numbers
Save the following as scoring.proto. It includes the syntax, package, and request/response types. Generating a client does not implement settlement, authorization, or idempotency on the server.
syntax = "proto3";
package tournament.scoring.v1;
service ScoringService {
rpc SettleMatch(SettleMatchRequest) returns (SettleMatchResponse);
}
message SettleMatchRequest {
string match_id = 1;
string winner_id = 2;
string idempotency_key = 3;
}
message SettleMatchResponse {
string settlement_id = 1;
}Protocol Buffers field numbers identify fields in the binary wire format. The fragment below replaces the original request definition after winner_id is removed; do not include both definitions in one file. Reserve the old number and name so they cannot be reused with new meaning.
message SettleMatchRequest {
reserved 2;
reserved "winner_id";
string match_id = 1;
string idempotency_key = 3;
}The new service needs another trusted source of the winner and a compatible migration before removing that field. Reservation prevents reuse; it does not supply missing business meaning. Ordinary proto3 scalar fields read as defaults when absent, and an empty string is not a valid supplied ID. Use explicit presence, such as optional, when absence must be distinguished from a default, and validate business inputs.
Clients should set an explicit deadline. DEADLINE_EXCEEDED on a write can mean the server completed the operation before the response arrived. Use an idempotency key or status query to resolve the outcome.
gRPC status code should distinguish between invalid parameters, failed preconditions, concurrency conflicts, unauthenticated, unauthorized, and temporary unavailability; do not map all exceptions to UNKNOWN or INTERNAL.
AsyncAPI Description of Message Interactions
A message contract goes beyond just defining the payload and should also specify:
- The channel or topic and the associated publish/subscribe operations;
- Fields such as event ID, aggregate ID, version, and timestamp;
- Partition key and ordering scope;
- Schema compatibility policies;
- Security, protocol bindings, and retry or dead-letter queue conventions;
- Ownership of producers and consumers.
In AsyncAPI 3.x, an operation uses action: send or receive from the described application's perspective. Do not copy the 2.x publish/subscribe structure into a 3.x document.
While AsyncAPI can describe the structural aspects of message interactions, end-to-end delivery, idempotency, and compensation mechanisms still require dedicated design and testing.
"Adding Fields" Might Also Break Consumers
Structurally backward-compatible changes can still be semantically incompatible:
- Adding an enum value can break client-side exhaustive branches;
- A previously optional field becomes a business-critical required field;
- A numeric unit changes from seconds to milliseconds;
- The sorting rules for lists are altered;
- Arrays that were previously guaranteed to contain elements now return empty;
- The meaning of error codes or retry recommendations changes.
Compatibility must be checked across four layers: wire, source, behavior, and data.
Version Strategy
Prioritize compatibility evolution: add optional new capabilities, preserve existing semantics, and provide deprecation windows. Introduce new major versions or new resources only when compatibility is impossible.
Versioning can be placed in paths, headers, media types, or schema/topic names. The choice of where to place the version is less important than answering these key questions:
- How long will two versions coexist?
- How does the client discover deprecation notices and migration guidance?
- How does the producer know which systems still use the older version?
- When and by whom is support officially ended?
- Are old and new data formats compatible during rollback?
Contract Testing's Three-Gate System
- Static compatibility checks: OpenAPI/Proto/AsyncAPI diffing and linting;
- Provider validation: Whether the implementation adheres to the contract and provided examples;
- Consumer scenarios: Whether key consumer-dependent fields and interactions remain valid.
Consumer-driven contracts are effective at capturing real-world dependencies, but they should not freeze the provider's internal design. Contracts must center on public behavior and avoid asserting details about unrelated fields or call counts.
Governance Is Not a Central Approval Queue
Effective governance delivers reusable automated workflows: templates, linting, compatibility diffs, generation, testing, an API catalog, and deprecation dashboards. Platform teams establish shared security and operability rules, while domain teams retain ownership of business semantics.
Every contract must include: a steward, stability level, authentication method, SLOs, change history, deprecation policy, and support channels.
References
- OpenAPI Initiative, OpenAPI Specification 3.2.0
- Protocol Buffers, Proto3 language guide
- gRPC, Core concepts
- gRPC, Status codes
- AsyncAPI Initiative, AsyncAPI Specification