Designing Data Contracts
When to use
- An upstream (service, event, API, file) feeds downstream pipelines and a change
could break them silently.
- Defining the interface between a producing team/system and the warehouse.
- Adding schema/quality enforcement at the ingestion boundary.
- Do NOT use for internal model-to-model changes within one dbt project (use
tests +
handling-schema-evolution).
What a contract specifies
- Schema: fields, types, nullability, and allowed values.
- Semantics: what each field means and its unit/grain.
- Guarantees: freshness/SLA, volume expectations, uniqueness of keys.
- Ownership: who produces it and who to contact.
- Versioning + change policy: how breaking changes are communicated.
Workflow
- [ ] Write the contract as a versioned, checked-in schema (not tribal knowledge)
- [ ] Enforce it at the ingestion boundary (validate on arrival)
- [ ] Classify changes: additive (safe) vs breaking (needs a new version)
- [ ] On violation, reject/quarantine and alert the producer
- [ ] Version and communicate breaking changes ahead of time
- Make it explicit and versioned. Store the contract as code (JSON Schema,
Avro/Protobuf schema, or a YAML spec) next to the pipeline, reviewed like any
API.
- Enforce at the boundary. Validate incoming data against the contract on
arrival; reject or quarantine violations instead of loading them.
- Classify changes. Additive/optional fields = backward compatible. Removing
fields, renaming, tightening types/nullability = breaking → new version.
- Fail loudly to the producer, not silently downstream.
Patterns
Contract as JSON Schema (enforced on ingest):
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"required": ["order_id", "amount", "ordered_at"],
"properties": {
"order_id": { "type": "string" },
"amount": { "type": "number", "minimum": 0 },
"ordered_at": { "type": "string", "format": "date-time" },
"coupon": { "type": ["string", "null"] }
},
"additionalProperties": false
}
Enforcement point — validate each record on ingest; route failures to a
quarantine location with the reason, and alert the producing team. This turns a
silent downstream break into an immediate, owned signal at the source.
Schema registry (Kafka/Avro) — enforce compatibility (BACKWARD) at publish
time so producers cannot ship an incompatible schema.
Common pitfalls
- Contract as documentation only — if it isn't enforced in code, it drifts and
breaks silently.
- Enforcing deep in the warehouse — catch violations at the boundary, before
bad data spreads.
- No versioning — every change becomes an emergency; version and deprecate
gracefully.
additionalProperties unrestricted when you need strictness — unexpected
fields slip through; set it false where appropriate.
- No owner — a rejected batch with no one to call stalls the pipeline.
- Breaking changes with no lead time — coordinate producer/consumer via
versioned schemas and a deprecation window.
1---2name: designing-data-contracts3description: Define and enforce data contracts between producers and consumers — explicit schema, semantics, ownership, SLAs, and versioning — to prevent silent upstream changes from breaking downstream pipelines. Use when a producer schema change could break consumers, defining an interface between teams/services and the warehouse, or adding schema enforcement at ingestion.4---56# Designing Data Contracts78## When to use910- An upstream (service, event, API, file) feeds downstream pipelines and a change11 could break them silently.12- Defining the interface between a producing team/system and the warehouse.13- Adding schema/quality enforcement at the ingestion boundary.14- Do NOT use for internal model-to-model changes within one dbt project (use15 tests + `handling-schema-evolution`).1617## What a contract specifies1819- **Schema**: fields, types, nullability, and allowed values.20- **Semantics**: what each field means and its unit/grain.21- **Guarantees**: freshness/SLA, volume expectations, uniqueness of keys.22- **Ownership**: who produces it and who to contact.23- **Versioning + change policy**: how breaking changes are communicated.2425## Workflow2627```28- [ ] Write the contract as a versioned, checked-in schema (not tribal knowledge)29- [ ] Enforce it at the ingestion boundary (validate on arrival)30- [ ] Classify changes: additive (safe) vs breaking (needs a new version)31- [ ] On violation, reject/quarantine and alert the producer32- [ ] Version and communicate breaking changes ahead of time33```34351. **Make it explicit and versioned.** Store the contract as code (JSON Schema,36 Avro/Protobuf schema, or a YAML spec) next to the pipeline, reviewed like any37 API.382. **Enforce at the boundary.** Validate incoming data against the contract on39 arrival; reject or quarantine violations instead of loading them.403. **Classify changes.** Additive/optional fields = backward compatible. Removing41 fields, renaming, tightening types/nullability = breaking → new version.424. **Fail loudly to the producer**, not silently downstream.4344## Patterns4546**Contract as JSON Schema (enforced on ingest):**4748```json49{50 "$schema": "https://json-schema.org/draft/2020-12/schema",51 "type": "object",52 "required": ["order_id", "amount", "ordered_at"],53 "properties": {54 "order_id": { "type": "string" },55 "amount": { "type": "number", "minimum": 0 },56 "ordered_at": { "type": "string", "format": "date-time" },57 "coupon": { "type": ["string", "null"] }58 },59 "additionalProperties": false60}61```6263**Enforcement point** — validate each record on ingest; route failures to a64`quarantine` location with the reason, and alert the producing team. This turns a65silent downstream break into an immediate, owned signal at the source.6667**Schema registry** (Kafka/Avro) — enforce compatibility (`BACKWARD`) at publish68time so producers cannot ship an incompatible schema.6970## Common pitfalls7172- **Contract as documentation only** — if it isn't enforced in code, it drifts and73 breaks silently.74- **Enforcing deep in the warehouse** — catch violations at the boundary, before75 bad data spreads.76- **No versioning** — every change becomes an emergency; version and deprecate77 gracefully.78- **`additionalProperties` unrestricted** when you need strictness — unexpected79 fields slip through; set it false where appropriate.80- **No owner** — a rejected batch with no one to call stalls the pipeline.81- **Breaking changes with no lead time** — coordinate producer/consumer via82 versioned schemas and a deprecation window.