4.9 KiB
Contract And Representation Semantics
Define domain vocabulary before transport names. State the authoritative schema and owner, identifier scope, mutability, lifecycle, and invariants for every exposed representation. A mapping layer must not quietly become the authority.
Representation Questions
- Does absence mean unknown, inapplicable, withheld, or an omitted default? Is
nulldistinct from absence in requests and responses? - Are defaults applied by the server, client, transport, or generated SDK? Can a future default change behavior?
- Are enum and union values open or closed for each consumer? Specify unknown-value handling rather than assuming an added value is safe.
- Define time zone/offset, precision, clock meaning, duration format, units, rounding, locale, currency, and identifier comparison rules where relevant.
- State which fields are stable identifiers, opaque tokens, display values, or derived/cached views.
HTTP Contract Semantics
For REST/HTTP, make the protocol behavior part of the domain contract:
- Choose a method from its RFC 9110 semantics, not a CRUD-name chart.
GETandHEADare safe;PUT,DELETE, and safe methods are idempotent in intended effect, but responses and incidental logging can differ.POSTandPATCHcan be made retryable only through an explicit application contract. - Define the success, redirection, client-error, and server-error statuses that consumers act on.
202 Acceptedmeans processing was accepted, not completed; give a status/result mechanism when completion is asynchronous.204 No Contentcarries no response content. Do not wrap every outcome in200 OKand an application flag. - State request and response media types, character/serialization rules, content negotiation, language where applicable, and
Varybehavior. A new representation or default can change caches and generated clients. - Define cacheability, freshness, validators such as
ETagorLast-Modified, conditional requests, and invalidation for reads where caching matters. Do not use an ETag as a write precondition unless its representation and validator semantics support that contract. - Keep resource identifiers stable and opaque where consumers should not infer structure. URI naming is a consistency decision, not a universal plural-noun rule; avoid exposing storage topology as domain authority.
Collections
Document filter grammar, supported fields/operators, escaping, case/locale behavior, sort keys and directions, ties, default ordering, search consistency, sparse-field semantics, and resource-cost constraints. Reject or constrain inputs by the published contract; do not claim a universal maximum.
For pagination, choose offset, keyset, cursor, or another model based on data shape and consumer need. A cursor is not inherently snapshot-stable or idempotent. State:
- sort keys and deterministic tie breaking;
- whether the token is opaque and integrity protected;
- direction, page-size interaction, expiry, and resume behavior;
- behavior when records change or disappear between pages; and
- expected duplicate, skipped-item, or snapshot guarantees, if any.
Keyset pagination can use any stable ordered key or tuple; it is not limited to sequential IDs. Provide contract examples and negative examples for invalid filters, expired cursors, and incompatible combinations.
Contract Formats And Tooling
Use OpenAPI 3.2.0 for HTTP contracts when its feature set and the chosen toolchain support it. Its JSON Schema dialect is defined by the selected OAS version; do not assume a generator, validator, gateway, or documentation renderer supports every 3.2 feature. Pin the tool versions, validate generated server/client behavior, and use a compatible subset or another published contract version when needed.
A useful OpenAPI contract includes operation identity and ownership; parameters and serialization; request and response media types; every consumer-relevant status; schemas with required/null/default/read-only/write-only semantics; security requirements; reusable components; and validated positive and negative examples. Add callbacks, webhooks, links, streaming content, or external references only when the selected OAS version and deployed tools preserve their meaning. Linting proves rules, not domain correctness. Resolve references from the actual entry document, and test that examples and generated artifacts conform to the same dialect.
Code generators can turn optionality, unions, enums, defaults, integer widths, dates, and polymorphism into stricter language models than the wire schema suggests. Generate representative clients in CI or treat them as explicit consumers in compatibility review.
For GraphQL, express field ownership, nullability, pagination, mutation payloads, errors, query-cost controls, and deprecation in the schema. For RPC, make commands, input/output messages, deadlines, and application errors explicit. Generated code is a consumer with its own strictness and upgrade behavior.