Course outline

Software Architecture / Foundations: promises, structure, and ownership / Lesson 5

Specify contracts that survive independent change

Describe request meaning, compatibility, errors, and uncertain outcomes beyond a successful response schema.

About 4 min readContent reviewed
On this page

Lesson goals

  • Distinguish syntax compatibility from preserved behavior.
  • Define additive evolution, validation boundaries, and error semantics.

A contract includes more than field names

An interface contract describes what a caller may request, what outcomes mean, and what the provider guarantees under stated conditions. For reservations, units, identity, duplicate handling, authorization scope, and time representation matter as much as JSON field types. A response with status confirmed must correspond to the authoritative business state, not merely acceptance into a queue. Document whether the caller can cancel, query, or retry an uncertain operation. A schema validator can check structure but cannot establish that the implementation honors these semantic promises.

Make ambiguous values explicit

The fixture uses an opaque workshop identifier, an integer seat count, and a stable client request identifier. It avoids accepting a client-supplied price as authoritative. A real implementation still needs length limits, allowed ranges, identity binding, and an authorization decision. If the same request identifier arrives with different business input, rejecting the conflict is safer than silently reusing an unrelated result. State the retention window for deduplication records so a caller knows when retrying an old identity no longer carries the same guarantee.

json A small reservation command
{"request_id":"req-17","workshop_id":"ws-42","seats":1}

This request is syntactically valid JSON. Its business validity depends on the provider contract; the identifier does not replace authentication or authorize access to workshop ws-42.

Separate rejection, failure, and uncertainty

A known business rejection such as capacity exhausted differs from an internal server failure and from a connection timeout after an unknown commit. Give callers stable machine-readable error codes and useful safe descriptions. Do not require clients to parse human prose, database exceptions, or translated text. Define which failures are retryable and under what identity and deadline. HTTP status codes communicate broad protocol meaning; the domain error supplies additional context. A transport success carrying an unexplained error flag can confuse monitoring and client behavior unless deliberately specified.

Treat optional fields and enum growth carefully

Adding a field can be compatible when old consumers ignore unknown fields and new consumers tolerate its absence. That is an assumption to test, not a universal property of JSON. Adding an enum value can break exhaustive client logic; changing a field from absent to null may alter meaning. The proposed response adds an optional display label while preserving identifiers and confirmed semantics. Test an older consumer against the new response and a newer consumer against the old response when rolling deployments create both combinations.

json An additive response candidate
{"reservation_id":"r-8","status":"confirmed","display_label":"Workshop booking"}

Compatibility depends on consumer behavior. display_label is illustrative optional presentation data; reservation_id and status retain the earlier meaning, and no consumer test is implied by this document.

Evolve the contract with migration evidence

Version a contract when a breaking change cannot be introduced safely through compatible stages. Publish the replacement behavior, support overlap, migration checks, and retirement ownership. Distinguish interface version from application release version: a new build need not require a new public API. Observe actual consumer use where authorized and provide a rollback path that respects data already written in the new form. A deprecation notice alone does not prove every consumer has migrated, especially when offline clients or scheduled jobs call infrequently.

Common errors and fixes

Clients parse a translated error sentence.

Why it happens
Presentation text became the machine contract.
How to fix it
Add stable error codes and keep localized text separate.
Prevent it next time
Test errors through code and semantics, not wording.

A new enum value crashes old consumers.

Why it happens
Additive schema changes were assumed behaviorally harmless.
How to fix it
Define unknown-value handling or introduce a compatible migration.
Prevent it next time
Include old/new consumer combinations in contract review.

A timeout is reported as a definite rejection.

Why it happens
Transport evidence was confused with business outcome.
How to fix it
Expose an outcome query or safe idempotent retry mechanism.
Prevent it next time
Model uncertain outcomes explicitly in client workflows.

Check your understanding

1. Can a request_id field alone make reservations idempotent?

Show worked solution

No. The provider must bind the identity to the relevant caller and request content, store its outcome atomically with the reservation, and define concurrency and retention behavior. The client must reuse the identity for the same operation. Merely accepting a string leaves duplicate execution and conflicting payloads possible.

2. A developer renames confirmed to accepted without changing the schema type. Is this compatible?

Show worked solution

It may break consumers that match the original value and can alter the promise from a completed reservation to an accepted request. Compare both syntax and meaning. Preserve the old value during a migration or explicitly version a breaking semantic change, and provide tests that demonstrate the chosen transition.

Key takeaways

  • Compatibility is a property of interacting implementations and meanings.
  • A contract must explain failures and uncertainty as carefully as success.

Official references