skip to content

How would you make an API contract or configuration schema the single source of truth across server, clients and documentation, and how do you prove in CI that nothing has drifted from it?

level: middleimportance: should knowfreq 30%

answer

  1. one editable schema, everything else generated
  2. schema-first XOR code-first, never both
  3. regenerate + diff in CI = enforcement
  4. deterministic generation, pinned generator
  5. compat diff blocks breaking changes

basics

~20 s

Keep one machine-readable schema file as the definition, generate server stubs, client code, validation and docs from it, never hand-edit the generated output, and have CI regenerate and fail the build if the result differs from what is committed.

solid answer

~50 s

Pick one machine-readable definition — an interface definition language (IDL) file, an OpenAPI/JSON-Schema document, a Protobuf `.proto`, a typed config schema — and make it the artifact humans edit. Everything else is generated from it in the build: server request/response types and validation, client SDKs for each language, mock servers, reference documentation, even test fixtures. Enforcement has three parts. **Generated output is read-only**: a `GENERATED — DO NOT EDIT` header plus a sanctioned extension seam (separate files, subclasses, adapters) so nobody needs to edit it. **Regenerate-and-diff in CI**: the pipeline runs the generator and fails if the working tree changes, which makes a hand-edit or a stale artifact unmergeable. **Compatibility checks**: a schema-diff step classifies changes as backward-compatible or breaking and blocks breaking ones without an explicit version bump. Optionally add runtime contract tests so the deployed server is validated against the same schema, catching drift that generation alone cannot (behavioural differences, hand-written handlers).

code

pseudocode · 5 lines
pseudocode
# CI gate: the schema is authoritative, artifacts are derived.
run: generate --from api/schema.yaml --out ./generated   # pinned generator version
run: fail-if-dirty ./generated   # stale or hand-edited output cannot merge
run: schema-diff --base origin/main --head HEAD --fail-on breaking
run: contract-test --server http://localhost:8080 --schema api/schema.yaml

go deeper

for a junior

Say the schema file is the definition, the server and client code and docs are generated from it, and you never edit generated files.

for a middle

Add the enforcement: DO-NOT-EDIT headers, CI regenerate-and-diff, and a versioned published artifact instead of consumers copying the schema.

for a senior

Distinguish schema-first from code-first and insist on picking one; add compatibility diffing to block breaking changes and runtime contract tests because generation checks types, not behaviour.

for a principal

Treat the contract as a governed inter-team asset: registry with versioning and deprecation policy, consumer-driven contract verification in provider pipelines, generator pinning as supply-chain hygiene, and a migration path for legacy undocumented endpoints.

## Terms - **Machine-readable schema / IDL**: a file describing data shapes and operations in a format tools can consume — OpenAPI, JSON Schema, Protobuf, Avro, GraphQL SDL, a typed config schema. - **Code generation**: producing source artifacts mechanically from that file. - **Drift**: the committed generated artifact, or a hand-written implementation, no longer matching the schema. - **Contract test**: a test that checks a running implementation against the schema (or against consumer expectations) rather than checking generated source. ## Why the contract is the classic SSoT case An API's shape is knowledge held in at least five places: server types, server validation, client types, documentation, and the mental model of every consumer team. Maintaining those by hand guarantees the docs are wrong first, the mobile client second. Making one file authoritative and deriving the rest collapses N hand-maintained representations into one edited artifact plus N generated ones. ## Choosing the authoritative direction There are two legitimate arrangements, and mixing them is where teams get hurt: 1. **Schema-first (design-first)**: the schema file is edited by humans; server and clients are generated. Best when multiple consumers exist, when the contract is negotiated across teams, and when you want the contract reviewable in a pull request before implementation. 2. **Code-first**: annotated server code is authoritative; the schema is *generated* from it and published. Perfectly SSoT-compliant — the owner is the code — but the schema must then be a build output, never hand-edited, and consumers must consume the published artifact rather than a copy. The anti-pattern is *both*: hand-written annotations **and** a hand-maintained schema file, which is two authorities and guaranteed divergence. ## Enforcement mechanics **1. Regenerate-and-diff.** The single highest-value check: ``` generate if git-status-is-dirty: fail "generated artifacts are stale or hand-edited" ``` This works only if generation is **deterministic** — stable ordering, no timestamps, no absolute paths, pinned generator version. Pin the generator in the toolchain (lockfile/container digest) or the diff check becomes flaky and gets disabled. **2. Do-not-edit plus an extension seam.** People edit generated files because they need something the generator does not produce. Give them a legitimate place: partial classes, subclassing, decorators, or a hand-written adapter layer that wraps generated types. Without a seam, the header is ignored. **3. Compatibility gating.** Run a schema diff between the merge base and the branch and classify: added optional field (compatible), removed field or narrowed type or renamed enum value (breaking). Block breaking changes unless the version/major is bumped, or require a deprecation window. Avro/Protobuf tooling and OpenAPI diff tools do this off the shelf. **4. Runtime contract tests.** Generation guarantees *types* match; it does not guarantee the server *behaves* per the schema — hand-written handlers can return an undocumented error shape or omit a documented field. Validate real responses against the schema in integration tests, or use consumer-driven contract testing where each consumer publishes expectations that the provider's pipeline verifies. **5. Publish, don't copy.** Consumers should pull a versioned artifact (a package, a registry entry) rather than vendoring a copy of the schema file into their repo. A vendored copy is a second editable representation and drifts the moment someone tweaks it locally. ## The same shape for configuration A typed config schema (defaults, types, required-ness, constraints) generates: the parsing/validation code, the documented reference table, the environment-variable list, and the deployment templates. The failure it prevents is the classic "the value in the deploy manifest, the value in the sample `.env`, and the default in code are three different numbers". ## Edge cases - **Generator does not support a needed feature**: extend via the seam or a post-processing step in the build, not by editing output. - **Multiple languages, different generator maturity**: accept a thin hand-written adapter per language, kept small and tested, rather than hand-writing the whole client. - **Legacy endpoints not in the schema**: they are outside the SSoT and will drift; track them as debt and add a check that fails when an undocumented route is served. - **Docs prose vs generated reference**: generated reference is derived; hand-written narrative guides are separate knowledge and should not restate field lists — link to the generated reference instead.

  • Is code-first (generating the schema from annotated server code) a violation of Single Source of Truth?
    No — it just relocates the owner to the code. It stays compliant as long as the schema is only ever a build output, is published as a versioned artifact, and is never hand-edited alongside the annotations. The violation is maintaining both by hand.
  • Why does the CI regenerate-and-diff check require deterministic generation?
    Because any nondeterminism — timestamps, unstable ordering, absolute paths, an unpinned generator version — makes the diff fail on unrelated builds. Flaky gates get bypassed or deleted, and the enforcement disappears.

A blueprint and the drawings derived from it: electricians and plumbers work from prints reissued whenever the blueprint changes. Nobody marks up their own print and expects the building to match — and if they do, the site inspection catches it.

context