C.W.K.
Stream
Lesson 03 of 06 · published

Data Contracts Between Teams

~11 min · contracts, production, process

Level 0Curious Reader
0 XP0/47 lessons0/11 achievements
0/120 XP to next level120 XP to go0% complete

The problem contracts solve

Producer team ships a schema change. Consumer team's pipeline breaks at 6 AM. Consumer team is angry. Producer team didn't know anyone was reading their table. This is the most common cross-team data outage in any company that has at least two data-producing teams. The fix is a data contract.

What a contract has

  • Schema — column names, types, nullability, allowed values.
  • Freshness — "available by 9 AM, 99.5% of days."
  • Ownership — name and on-call channel of the producing team.
  • Breaking-change policy — how much notice before a column is removed; how column-rename migrations are handled.
  • Versioning — additive vs breaking changes; whether downstream consumers must opt in.

What a contract is not

A contract is not a wiki page that nobody reads. It's an artifact enforced by code: schema validation at the producer's write step, automated freshness checks, CI tests that compare current schema to the declared contract on every PR. A contract in a doc is wishful thinking; a contract in CI is a load-bearing wall.

Code

A YAML contract checked into the producer's repo·yaml
# contracts/orders_v1.yaml
name: orders
version: 1
owner:
  team: orders-eng
  oncall: '#orders-data'
  email: orders-eng@example.com
freshness:
  sla_hours: 24
  measured_by: max(ingested_at)
schema:
  - name: order_id
    type: string
    required: true
    unique: true
    pattern: '^O\d{6}$'
  - name: customer_id
    type: string
    required: true
  - name: amount_usd
    type: number
    required: true
    minimum: 0
  - name: status
    type: string
    enum: [pending, completed, cancelled]
breaking_change_policy:
  notice_days: 30
  migration_pattern: |
    Add new column alongside, populate both for the notice window,
    drop the old column at the end of the window.
CI check that compares the producer's actual schema to the declared contract·python
import yaml
import pyarrow.parquet as pq

def check_contract(parquet_path: str, contract_path: str) -> list[str]:
    '''Compare the actual Parquet schema to the contract. Return list of violations.'''
    contract = yaml.safe_load(open(contract_path))
    actual = pq.read_schema(parquet_path)
    actual_cols = {f.name: str(f.type) for f in actual}
    declared = {c['name']: c['type'] for c in contract['schema']}

    issues = []
    for name in declared.keys() - actual_cols.keys():
        issues.append(f'missing required column: {name}')
    for name in actual_cols.keys() - declared.keys():
        issues.append(f'unexpected column: {name} (add to contract or remove)')
    return issues

External links

Exercise

Pick one data product you produce that another team consumes. Draft a one-page YAML contract: schema, freshness, ownership, breaking-change policy. Share the draft with the consuming team for review. The exercise of writing the contract is often more valuable than the document itself — it surfaces every assumption that wasn't obvious to both sides.

Progress

Progress is local-only — sign in to sync across devices.
Spotted a bug or have feedback on this page?Report an Issue

Comments 0

🔔 Reply notifications (sign in)
Sign inPlease sign in to comment.

No comments yet — be the first.