See it as a diagram
Everything below, as a diagram you can edit. Describe yours and see it in seconds.
No account needed · Editable canvas, not a picture
The short answer
A data contract is a versioned, machine-readable agreement between the team that produces a dataset and the teams that consume it: the schema (field names, types, nullability), the meaning of each field, the service level for freshness and completeness, and the rule for what counts as a breaking change. You automate it with three checks in one pipeline. A lint step validates the contract file itself. A compatibility step diffs the contract in the pull request against the version on the main branch and exits non-zero when a field is dropped, renamed, retyped, or tightened, which fails the build before the merge. A test step runs the quality and freshness rules against the landed data on a schedule, because a shape that still matches says nothing about a table that stopped loading. Adding an optional field is backward compatible and passes; dropping one is breaking and needs a major version plus a deprecation window. Streaming teams get the first two from a schema registry compatibility mode set per subject, warehouse and batch teams from the datacontract CLI, dbt contracts, or buf. The check only enforces anything once it is a required status check on the branch.
1. How do you automate data contract checks in CI/CD?
Three checks, three different failure modes, and they are not interchangeable. Run all three or accept that one class of incident stays invisible until a consumer reports it.
| Check | Catches | Runs on | Fails |
|---|---|---|---|
| Lint | A malformed contract: unknown key, missing owner, invalid ISO 8601 duration | Every PR touching contracts/ | The build, in under a second |
| Compatibility diff | A dropped, renamed, retyped or newly required field without a major version bump | Every PR, against the file on main | The build, before merge |
| Data test | Data that no longer matches a contract nobody changed: nulls, ranges, stale loads | A schedule, against the landed table | An alert to the owner |
The failure the diff exists to stop
Backend team renames a column. Pipeline breaks at 3 AM. The data team spends hours finding out why. Without a check on the pull request, every schema change is a surprise: producers do not know who reads their data, and consumers do not know when it will change. The diff turns that into a red build at review time, when the person who made the change is still looking at it.
A GitHub Actions job that runs all three
.github/workflows/contract-check.yml
name: Data Contract Validation
on:
pull_request:
paths:
- 'contracts/**'
schedule:
- cron: '0 6 * * *' # the data test, once a day
jobs:
validate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0 # the diff needs the base commit, not a shallow clone
- name: Install the datacontract CLI
run: pip install datacontract-cli
- name: Lint
run: datacontract lint contracts/orders.yaml
- name: Fetch the version currently on main
run: git show origin/main:contracts/orders.yaml > /tmp/orders-main.yaml
- name: Fail on a breaking change
run: datacontract breaking /tmp/orders-main.yaml contracts/orders.yaml
- name: Human-readable diff in the job log
if: always()
run: datacontract changelog /tmp/orders-main.yaml contracts/orders.yaml
- name: Test the contract against live data
run: datacontract test contracts/orders.yaml --server production
env:
DATACONTRACT_SNOWFLAKE_USERNAME: ${{ secrets.SNOWFLAKE_USERNAME }}
DATACONTRACT_SNOWFLAKE_PASSWORD: ${{ secrets.SNOWFLAKE_PASSWORD }}Two details decide whether this job is enforcement or decoration. fetch-depth: 0 is required, because the default shallow clone has no base commit to diff against and the step will pass on a repository where it cannot see the old file. And the job has to be a required status check in branch protection. A job that reports red on a mergeable pull request teaches the team to merge red. If you already run docs checks in the pipeline, the contract check belongs in the same required set; docs checks in CI covers how teams keep that set fast enough to stay required.
Where enforcement sits, in order of how early it catches
CI/CD pipeline (shift-left)
Validate contract changes in the pull request. Reject breaking changes without a version bump. Check schema compatibility before merge. Diagram where these gates sit in your CI/CD pipeline so producers know exactly what blocks a merge.
Tools: datacontract CLI, buf (Protobuf), schema-registry compatibility check
Schema registry (deploy and publish time)
Centralized schema storage. Producers register schemas. Consumers fetch compatible versions. Rejects incompatible writes.
Tools: Confluent Schema Registry, AWS Glue, Hive Metastore
Ingestion validation (runtime)
Validate data at the ingestion point. Reject or quarantine non-compliant records. Log violations for producer feedback.
Tools: Great Expectations, Soda, custom validators
SLA monitoring (after the fact)
Track freshness, availability and quality metrics. Alert when SLAs are breached. Dashboard for visibility.
Tools: Monte Carlo, elementary, custom dashboards
Before writing your first contract, know your flows. A data pipeline diagram shows which producer-consumer interfaces break most often, and those are the ones worth putting a contract on first.
2. Which schema compatibility mode should the check enforce?
The compatibility mode is the single setting that decides what your check actually refuses. Most teams never change it, run on the default, and are surprised when the default turns out to permit exactly the change that broke them.
| Mode | Guarantee | Allows | Pick it when |
|---|---|---|---|
| BACKWARD | A consumer on the new schema can read data written with the previous one | Delete a field, add an optional field | Consumers upgrade first. This is the Confluent default |
| FORWARD | A consumer on the previous schema can read data written with the new one | Add a field, delete an optional field | Producers upgrade first and consumers lag |
| FULL | Both directions, against the previous version | Add or delete optional fields only | Neither side can be made to deploy first |
| *_TRANSITIVE | The same guarantee against every earlier version, not only the latest | Same as the base mode | Consumers may sit several versions behind, or replay old data |
| NONE | No guarantee | Anything | Prototypes, and topics with a single consumer you own |
The trap is replay. BACKWARD only compares against the version immediately before, so a field deleted in v2 and a different field deleted in v3 both pass, and a consumer re-reading a topic from the beginning hits data neither schema describes. Any dataset that gets replayed, or that has consumers you do not control, wants a TRANSITIVE variant. The mode is set per subject, not per cluster, so a single noisy topic does not force the whole registry into a strict mode. The compatibility rules and what a registry structurally cannot check are laid out in data contract vs schema registry.
3. What counts as a breaking change?
This is the list the compatibility check encodes. Anything on the left needs a major version and a deprecation window; anything on the right ships in a minor or patch release and merges the same day.
Breaking (major version)
- • Removing a field
- • Changing a field's data type
- • Renaming a field
- • Making an optional field required
- • Changing field semantics
- • Removing enum values
Non-breaking (minor or patch)
- • Adding a new optional field
- • Adding new enum values
- • Relaxing constraints (required to optional)
- • Updating descriptions
- • Adding default values
- • Improving SLAs
One row on that list is invisible to every automated check: changing field semantics. If status = 3 stops meaning shipped and starts meaning partially shipped, the schema is byte-identical and every compatibility mode passes, while every downstream aggregate is quietly wrong. That is why the contract carries a semantics block and why a semantic change is treated as a major version by policy rather than by tooling.
Versioning strategies
Semantic versioning (recommended)
MAJOR.MINOR.PATCH. Major = breaking, minor = new features, patch = fixes. Clear, well-understood, widely adopted.
Date-based versioning
YYYY-MM-DD format. Good for APIs, less common for data. Makes age obvious.
Schema evolution (Avro, Protobuf)
No explicit version number on the contract. The registry compatibility mode decides what may change, and the subject version increments on its own.
Example: change management process
# Deprecation workflow for breaking changes 1. ANNOUNCE (Day 0) - Create deprecation notice in contract - Notify consumers via #data-announcements - Update changelog 2. DUAL-WRITE (Day 0-60) - Keep old field + add new field - Both populated during transition - Monitor which consumers migrate 3. WARN (Day 60-90) - Log warnings when old field accessed - Direct outreach to remaining consumers - Offer migration support 4. REMOVE (Day 90+) - Remove deprecated field - Bump major version - Old versions archived but accessible
The 90-day rule
Give consumers at least 90 days notice for breaking changes. This seems long, but data pipelines have long release cycles, and consumers need time to update downstream logic, tests, and dashboards.
4. How do you wire contracts into Kafka and Confluent Schema Registry?
On a streaming stack the compatibility half of the contract is already available: the registry does it. Three settings turn it from a schema store into an enforcement point.
The subject naming strategy
The default, TopicNameStrategy, registers one subject per topic and value, named orders-value. RecordNameStrategy keys the subject on the record type instead, so the same event type carries one contract across every topic it appears on. Pick before you have a hundred subjects, because migrating the naming strategy later means re-registering all of them.
The compatibility level, set per subject
Global default plus a per-subject override. Read it before you rely on it: a subject created during a migration is often sitting on NONE.
Broker-side validation
On Confluent Server, the topic config confluent.value.schema.validation=true makes the broker reject records whose schema id is not registered for the subject. Without it, a producer that bypasses the serializer can still write whatever it likes.
Compatibility test in the producer build, before the deploy
<!-- pom.xml: the candidate schema is checked against the registered subject -->
<plugin>
<groupId>io.confluent</groupId>
<artifactId>kafka-schema-registry-maven-plugin</artifactId>
<version>7.6.0</version>
<configuration>
<schemaRegistryUrls>
<param>https://schema-registry.internal:8081</param>
</schemaRegistryUrls>
<subjects>
<orders-value>src/main/avro/order.avsc</orders-value>
</subjects>
</configuration>
</plugin>
# In CI, on the pull request:
mvn io.confluent:kafka-schema-registry-maven-plugin:test-compatibility
# On merge to main, once the build is green:
mvn io.confluent:kafka-schema-registry-maven-plugin:registerConfluent also ships a feature named Data Contracts on top of the registry: schema and field level metadata, rulesets with domain validation rules written in CEL, migration rules using JSONata, and tags that drive downstream policy. It closes the value validation gap for streaming, which the schema alone cannot cover. What it does not change is the boundary: it applies to messages on topics, at publish time, for producers using that registry. The warehouse table analysts query, the nightly vendor file, and the freshness of both stay outside it, which is why teams running Confluent Data Contracts still keep contract files for the landed datasets. The reference material is in the Confluent Schema Registry documentation. For the topology itself, a Kafka streaming architecture diagram is the fastest way to show which subjects sit between which services.
5. What is the difference between a data contract and a schema?
A schema describes shape: field names, types, nullability. A data contract wraps that schema in commitments the shape cannot express, and the schema ends up as one section of it, usually the first. The useful mental model is the API contract, which nobody would reduce to a request body.
API contract
- • OpenAPI/Swagger spec
- • Request/response schema
- • Versioned endpoints
- • Rate limits & SLAs
- • Breaking change policy
Data contract
- • Schema definition (fields, types)
- • Semantic meaning of fields
- • Freshness & availability SLAs
- • Quality rules & expectations
- • Versioning & change policy
A schema registry is a tool that stores and validates schemas at publish time. A data contract is a practice that may use one. The registry cannot see a change in the meaning of a field, a shift in value distribution, a pipeline that stopped running, or anything in a warehouse table with no registry in front of it, which is the case for most analytical consumption. The full comparison, including which of the four gaps Confluent closes, is in data contract vs schema registry.
Key insight
Data contracts shift the responsibility for data quality left, to the producer. Instead of data teams cleaning up messes downstream, producers guarantee quality at the source. This is the shift-left movement in data.
6. What should a data contract specification include?
Seven blocks. Not every contract needs all of them, but a contract missing the owner or the deprecation policy will not survive its first disagreement.
Metadata
Name, version, owner, description, tags. Who owns this? What is it for?
Schema
Fields, data types, constraints (not null, unique). The structural definition.
Semantics
Business meaning of each field. What does "status" actually mean? What are valid values?
SLAs
Freshness (data no older than X), availability (uptime), latency (processing time).
Quality Rules
Expectations beyond schema: ranges, patterns, relationships, null rates.
Versioning
How changes are made, communicated, and deprecated. Breaking vs non-breaking.
Alerting
Who gets notified when contracts are violated? How? Where?
Example: complete data contract (YAML)
# contracts/orders.yaml apiVersion: v1 kind: DataContract metadata: name: orders version: 2.1.0 owner: [email protected] description: Order transactions from all sales channels tags: [commerce, transactions, pii] schema: type: object properties: order_id: type: string format: uuid description: Unique order identifier pii: false customer_id: type: string description: Customer who placed the order pii: true order_date: type: string format: date-time description: When the order was placed (UTC) status: type: string enum: [pending, confirmed, shipped, delivered, cancelled] description: Current order status total_amount: type: number minimum: 0 description: Total order value in USD required: [order_id, customer_id, order_date, status, total_amount] sla: freshness: PT1H # Data no older than 1 hour (ISO 8601) availability: 99.9% # Uptime target updateFrequency: PT15M # Updates every 15 minutes quality: rules: - name: unique_order_id type: unique column: order_id - name: valid_total type: range column: total_amount min: 0 max: 1000000 - name: no_future_orders type: custom sql: "order_date <= CURRENT_TIMESTAMP" versioning: strategy: semantic # major.minor.patch deprecationPolicy: 90d # 90 days notice for breaking changes changeLog: https://wiki.company.com/data/orders/changelog alerts: slack: "#data-alerts" pagerduty: commerce-oncall email: [[email protected]]
Using the Open Data Contract Standard instead of your own keys
ODCS is a YAML standard maintained by the Bitol project under the Linux Foundation, and it covers the same seven blocks with fixed key names: apiVersion, kind, id, name, version and status at the top, field definitions under schema, connection details under servers, the freshness and availability promises under slaProperties, and ownership under team and roles. The gain is not elegance, it is tooling: a standard file is something a linter, a catalog, and someone else's importer already understand. The datacontract CLI reads and writes it, and exports the same contract to Avro, JSON Schema, dbt, SQL DDL or Great Expectations, so one file can feed the registry and the warehouse tests. The section-by-section layout is in what is in a data contract.
7. Which schema language should the contract use?
The schema is the structural foundation of your contract. It defines what fields exist, their types, and basic constraints. Choose a schema language that your ecosystem supports, because the checker you get for free is the one that matters.
| Format | Best For | Tooling Support |
|---|---|---|
| JSON Schema | APIs, JSON data, broad compatibility | Excellent (many validators) |
| Avro | Kafka, streaming, schema evolution | Excellent (Confluent Registry) |
| Protobuf | gRPC, high-performance systems | Good (buf.build) |
| dbt YAML | Transformation layer, SQL models | Good (dbt ecosystem) |
| Data Contract Spec | Full contracts (schema + SLA + quality) | Growing (datacontract CLI) |
In the warehouse the equivalent of a registry is the dbt model contract: contract: { enforced: true } on the model config makes the run fail when the model output does not match the column names and data types declared in the YAML, which puts the same gate on a table that a registry puts on a topic. It checks shape only, so the quality rules still need Soda, Great Expectations, or dbt tests next to it.
What to include in the schema
Always include
- • Field names and data types
- • Required vs optional fields
- • Primary key / unique identifiers
- • Enum values for categorical fields
- • Field descriptions
Consider adding
- • PII/sensitive data flags
- • Format patterns (email, UUID)
- • Min/max for numeric fields
- • Default values
- • Deprecation markers
Example: JSON Schema for orders
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "Order",
"type": "object",
"properties": {
"order_id": {
"type": "string",
"format": "uuid",
"description": "Unique order identifier"
},
"customer_id": {
"type": "string",
"pattern": "^CUS-[A-Z0-9]{8}$",
"description": "Customer ID in format CUS-XXXXXXXX"
},
"order_date": {
"type": "string",
"format": "date-time"
},
"status": {
"type": "string",
"enum": ["pending", "confirmed", "shipped", "delivered", "cancelled"]
},
"items": {
"type": "array",
"items": { "$ref": "#/definitions/OrderItem" },
"minItems": 1
},
"total_amount": {
"type": "number",
"minimum": 0,
"exclusiveMaximum": 1000000
}
},
"required": ["order_id", "customer_id", "order_date", "status", "items", "total_amount"],
"additionalProperties": false
}8. How do you put SLAs and quality rules in the contract?
Schema defines structure. SLAs and quality rules define behavior and reliability. These are the promises that make contracts meaningful, and the ones the compatibility check never looks at.
A promise nobody measures is a comment. Freshness and volume SLAs need a monitoring layer that watches arrival times and row counts without being told what to expect, which is what data observability adds on top of explicit tests. Consumers also have to be able to find the contract before they depend on the table, so it belongs next to the dataset in the catalog; data catalog best practices covers how to keep that registration automatic instead of a launch-day chore.
Service level agreements
Freshness
How recent must the data be? "Data no older than 1 hour" means consumers can trust it's current.
freshness: PT1H (ISO 8601 duration)Availability
What uptime do you guarantee? 99.9% means about 8.7 hours of downtime per year at most.
availability: 99.9%Update frequency
How often is data refreshed? Real-time, hourly, daily?
updateFrequency: PT15M (every 15 min)Latency
Time from event occurring to data being available. Critical for real-time use cases.
latency: PT5M (5 min max delay)Quality rules (beyond schema)
Uniqueness
Primary keys must be unique. No duplicates.
unique(order_id)Completeness
Null rate thresholds. "email null rate < 5%"
null_rate(email) < 0.05Referential integrity
Foreign keys must exist in parent table.
exists(customer_id, dim_customers.id)Range checks
Values within expected bounds.
total_amount BETWEEN 0 AND 1000000Pattern matching
Values match expected format.
email MATCHES /^[a-z]+@[a-z]+\.[a-z]+$/Consistency
Calculated fields match components.
total = subtotal + tax - discountPro tip
Start with SLOs, graduate to SLAs
SLOs (objectives) are targets you aim for. SLAs (agreements) are commitments with consequences. Start by tracking metrics as SLOs. Once you are consistently hitting them, formalize as SLAs with stakeholder agreement.
9. How do you evolve a schema without breaking downstream consumers?
Expand, then contract. Every safe migration is the same four moves: add the new field alongside the old one, populate both, wait for consumers to move, remove the old one in a major version. The 90-day deprecation workflow above is that pattern with dates attached. Nothing in it is hard. The part teams get wrong is knowing when the wait is over.
Find out who actually reads the dataset
Query logs give you the honest answer that a wiki page of consumers does not: who selected that column in the last 90 days. Column-level lineage turns a proposed diff into a list of affected models and dashboards. Without one of the two, the deprecation window is a guess and the removal is a gamble. Data lineage best practices covers how to get that list.
Alert in three waves, not one
The registry or CI compatibility check fires on the pull request, before any deploy. The catalog turns the diff into named consumers and notifies their owners. A freshness and volume monitor on the landed table catches whatever slipped through. Teams that only have the third one find out from a dashboard, which is the same as finding out from the business.
Never reuse a name with a new meaning
Repurposing an existing column is the one change that passes every automated check and breaks every consumer. Add amount_cents next to amount rather than changing what the old one holds.
Where the picture goes stale is the diagram of who produces and who consumes. Datadef connects read-only to the GitHub, GitLab, or Azure DevOps repository that produces the dataset and redraws the architecture diagram and an architecture.md every day, so the producer-consumer map a reviewer opens during a contract change is the one that shipped rather than a drawing from two quarters ago. A commit that changes nothing structural does not redraw the diagram, and nodes moved by hand keep their position. The diagram embeds as a live image in the README next to the contract file, so the two are read together. See repository sync for what it reads.
10. What to do when a contract check fails
A message along the lines of "the data received does not meet the data contract expectations" means one thing: a validator compared a payload against the registered contract and found a mismatch. The message rarely says which of the three causes it is, and they have different owners.
| What the failure says | What actually happened | Who fixes it |
|---|---|---|
| Unexpected or missing field | The producer shipped a schema change without a version bump, or deployed ahead of the contract | Producer: revert or bump the major version |
| Type or enum violation | A value drifted: a new status code, a currency in cents where it used to be units | Producer, and the enum belongs in the contract |
| Null in a required field | Usually not the producer: an upstream join lost rows, or a backfill ran partial | Pipeline owner, before anyone edits the contract |
Rejected records go to a quarantine table or a dead letter queue keyed by the run id, never silently dropped and never waved through into the sink. Quarantine keeps the failure debuggable: you can count how many records failed, see the offending values, and replay them once the producer ships a fix. The one response to avoid is editing the contract to match the data. That is not a fix, it is a rename of the incident, and it removes the only record of what was promised.
Pro tip
Fail closed vs fail open
For critical data: fail closed (reject bad data). For non-critical: fail open (accept but log). Start fail-open to learn what violations exist, then tighten to fail-closed once producers fix issues.
11. What tools track schema versions, compatibility, and lineage?
Three layers, and rarely one product. A registry or buf versions the schema and answers the compatibility question. A contract runner (datacontract CLI, dbt contracts, Soda, Great Expectations) runs the checks in CI and against landed data. A catalog holds the lineage that says which consumers a change would reach. Here is what teams are using in 2026.
| Tool | Layer | Best For | Licensing |
|---|---|---|---|
| datacontract CLI | Contract runner | lint, breaking, changelog, test in one binary | Open source |
| Confluent Schema Registry | Schema versions | Kafka, per-subject compatibility modes | Open source and commercial |
| buf | Schema versions | Protobuf breaking-change detection in CI | Open source and commercial |
| Soda | Contract runner | Quality rules as checks against warehouse tables | Open source and commercial |
| Great Expectations | Contract runner | Comprehensive expectation suites | Open source |
| dbt contracts | Contract runner | Enforcing model output shape at build time | Open source (dbt Core) |
| DataHub / Atlan / OpenMetadata | Catalog and lineage | Finding the consumers a change would reach | Open source and commercial |
| Monte Carlo / elementary | Monitoring | Freshness and volume anomalies nobody wrote a rule for | Commercial / open source |
The gap most stacks have is the third layer. Versioning and testing are solved by the first two rows and the answer is boring; knowing that dropping order_channel hits four dbt models and a finance dashboard is what stops the deprecation from becoming an incident, and that needs lineage down to the column. Data lineage tools compares the options for that layer.
12. How do you roll contracts out across teams?
Consumer-driven, or it does not get written. Producers rarely know which of their hundred columns matter, so a contract they author alone documents everything and commits to nothing. Ask each consuming team to write down the fields and guarantees it actually depends on, merge those into one contract, and leave anything nobody claimed out of scope. Then run the check in the producer repository, so the failure lands on the person who can fix it.
Who is involved
Producer
The team that generates the data. Backend, application, or upstream data team. They commit to the contract.
Consumer
The team that uses the data. Analytics, ML, downstream pipelines. They rely on the contract.
Platform
The team that provides tooling. Schema registry, validation, monitoring. They enforce the contract.
Implementation checklist
Start with high-value datasets
Pick 2-3 critical datasets that cause the most pain. Prove value before scaling.
Get producer buy-in first
Contracts require producers to commit. Without their ownership, contracts become stale documentation.
Automate from day one
Manual contract enforcement does not scale. Set up CI/CD validation before writing your first contract.
Make contracts discoverable
Store contracts in a catalog or registry. If consumers cannot find them, they will not use them.
Start with SLOs, not SLAs
Track metrics as objectives first. Formalize as agreements once you are consistently meeting them.
Define your breaking change policy
How much notice? Who approves? What is the migration path? Document this upfront.
From experience
The hardest part is not the tooling, it is the culture change. Producers need to think of their data as a product with customers. That mindset shift takes time, executive support, and visible wins from early adopters.
13. Frequently asked questions
How do you enforce data contracts automatically?
Enforcement is three automated gates. A CI job lints the contract file and diffs it against the version on the main branch, failing the pull request on a breaking change without a major version bump. A schema registry or a dbt contract rejects an incompatible producer at deploy time. A scheduled test runs the quality and freshness rules against the landed data and pages the owner when one fails.
How do you run schema compatibility checks in a CI/CD pipeline?
Add a job that triggers on pull requests touching the contract directory. It installs the checker (the datacontract CLI, buf for Protobuf, or the Confluent Maven plugin), retrieves the previous contract with git show origin/main:contracts/orders.yaml, and compares the two. The command exits non-zero on an incompatible change, which fails the build. Keep it under a minute so it can be a required status check.
Which schema compatibility mode should the CI check use?
BACKWARD, the Confluent default, lets a new consumer read old data and permits deleting a field or adding an optional one. FORWARD lets an old consumer read new data. FULL is both. The TRANSITIVE variants check every earlier version instead of only the latest, which is what a dataset with many independent consumers needs. NONE disables the check.
What is the difference between a data contract and a schema?
A schema describes shape: field names, types, nullability. A data contract wraps that schema in commitments the shape cannot express: who owns the dataset, what each field means in business terms, the quality rules that must pass, the freshness target, and the notice period before a breaking change. The schema is one section of the contract, usually the first.
What is the difference between data contracts and a schema registry?
A schema registry stores and validates schemas at publish time, one subject per topic, and rejects a producer whose new schema violates the subject compatibility rule. A data contract is broader: it adds ownership, semantics, quality rules, freshness, and the change policy, and it covers batch tables that have no registry in front of them. Most streaming teams run both.
What should a data contract specification include?
Seven blocks: metadata (name, version, owner, tags), the schema with types and constraints, the semantics of each field, SLAs for freshness, availability and update frequency, quality rules beyond the schema, a versioning policy carrying the deprecation window, and the alerting route for a violation. The Open Data Contract Standard covers the same ground with fixed key names.
What is a data contract?
A data contract is a versioned, machine-readable agreement between the team that produces a dataset and the teams that consume it. It pins down the schema, the meaning of each field, the service level for freshness and completeness, and the rule for what counts as a breaking change. It lives in version control next to the producing code so a CI job can enforce it.
How do you adopt the Open Data Contract Standard in your team?
ODCS is a YAML standard maintained by the Bitol project under the Linux Foundation. Adopt it by converting one existing contract: keep apiVersion, kind, id, name, version and status at the top, move field definitions under schema, connection details under servers, and the freshness and availability promises under slaProperties. Then point the CI linter at the file so the shape stays valid.
What does it mean when the data received does not meet the data contract expectations?
A runtime validator compared an incoming payload against the registered contract and found a mismatch. Triage in this order: an unexpected or missing field means the producer shipped a schema change without a version bump, a type or enum failure means a value drifted, and a null in a required field usually means an upstream join lost rows. Rejected records belong in a quarantine table, not in the sink.
How do you roll out consumer-driven data contracts between teams?
Start from the consumer side. Each consuming team writes down the fields and guarantees it actually depends on, the producer merges those into one contract, and anything nobody claimed stays out of scope. Run the contract check in the producer repository so the producer sees the failure, not the consumer. Take two or three painful interfaces first, then widen once the checks are trusted.
What tools track schema versions, lineage, and compatibility?
Three layers, rarely one product. A schema registry (Confluent, AWS Glue) or buf versions the schema and answers the compatibility question. The datacontract CLI, dbt contracts, Soda or Great Expectations run the checks in CI and against landed data. A catalog such as DataHub, Atlan or OpenMetadata holds the lineage that tells you which consumers a change would reach.
How do you get schema change alerts before they cause downstream failures?
Three signals, in increasing lateness. The registry or CI compatibility check fires on the pull request, before any deploy. A catalog with column-level lineage turns that diff into a list of affected consumers and notifies their owners. A freshness and volume monitor on the landed table catches whatever slipped through, which is the last chance before a dashboard shows wrong numbers.
Diagram the interfaces you are about to put under contract
Describe the pipeline in a sentence and get an editable diagram of producers, topics, sinks and consumers, with the boundaries a contract has to cover marked.
Related Guides
Data Contract vs Schema Registry
Different boundary, different job, and why most teams need both
What Is in a Data Contract
The section-by-section layout of the file
Data Quality Best Practices
Validate and monitor data quality at scale
Data Lineage Best Practices
Track data from source to dashboard
Data Pipeline Documentation
Document pipelines that new hires can debug on day one
Keeping Data Documentation in Sync
Automation strategies for up-to-date documentation