Skip to content

19.2 OpenAPI, gRPC, AsyncAPI, and Contract Evolution

A contract turns paths, messages, fields, errors, and compatibility promises into reviewable, generatable, and testable artifacts. It is not a pretty auto-generated documentation page, but a shared boundary that both producers and consumers rely on.

Choose a Contract Format

ScenarioCommon ContractFocus Areas
HTTP requests/responsesOpenAPIPaths, methods, parameters, schema, security, and responses
High-performance RPC/streaming servicesProtocol Buffers + gRPCMethods, message field numbers, deadlines, status codes, and stream semantics
Message and event APIsAsyncAPIChannels, 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 Description of HTTP Contracts

yaml
openapi: 3.2.0
info:
  title: Tournament API
  version: 1.4.0
paths:
  /tournaments/{id}:
    get:
      operationId: getTournament
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string, format: uuid }
      responses:
        '200':
          description: Tournament found
        '404':
          description: Tournament not found

The 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 Contract Protection Field Number

proto
service ScoringService {
  rpc SettleMatch(SettleMatchRequest) returns (SettleMatchResponse);
}

message SettleMatchRequest {
  string match_id = 1;
  string winner_id = 2;
  string idempotency_key = 3;
}

Protocol Buffers field numbers are online identities. After a field is deleted, the original number and name should be reserved and the number should not be reassigned to new semantics.

proto
message SettleMatchRequest {
  reserved 2;
  reserved "winner_id";
  string match_id = 1;
  string idempotency_key = 3;
}

Clients should explicitly set a deadline. DEADLINE_EXCEEDED For write requests, the server may still consider the operation complete, and must be handled in conjunction with idempotency keys or status queries.

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.

While AsyncAPI can effectively 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 previously never returned null 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

  1. Static compatibility checks: OpenAPI/Proto/AsyncAPI diffing and linting;
  2. Provider validation: Whether the implementation adheres to the contract and provided examples;
  3. 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 an automated paved road: templates, linting, compatibility diffs, generation, testing, directory structure, 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

Built with VitePress | Software Systems Atlas