Data contracts are having a moment, and most of the discussion is stuck on the wrong axis. The interesting part isn’t the file format. Avro, Protobuf, JSON Schema — they’re all fine. Pick one. The interesting part is what changes about how product and data teams talk to each other once a contract exists at all.

The technical contract is the easy half

A data contract, in its simplest form, is a typed, versioned description of an event or table that some upstream system promises to emit and some downstream system relies on. A checkout_completed event has fields. Those fields have types. Those types have constraints. Required vs optional is explicit. PII is tagged. Owners are named. Breaking changes are versioned.

In Avro, that looks roughly like:

{
  "type": "record",
  "name": "CheckoutCompleted",
  "namespace": "com.acme.commerce.v2",
  "fields": [
    { "name": "order_id",   "type": "string", "doc": "ULID, immutable" },
    { "name": "user_id",    "type": "string", "doc": "PII: pseudonymous" },
    { "name": "total_cents","type": "long",   "doc": "Always > 0" },
    { "name": "currency",   "type": { "type": "enum", "name": "Currency", "symbols": ["USD","EUR","GBP"] } },
    { "name": "promo_code", "type": ["null", "string"], "default": null }
  ]
}

In Protobuf the same shape is a message with field numbers — and the field numbers are the contract. Renumbering breaks every consumer that ever shipped. JSON Schema gives you the same constraints in a more verbose form, with the advantage that it round-trips cleanly through OpenAPI for surface-area APIs.

Format is a tooling decision driven by transport. Kafka topics tend toward Avro because schema registries are mature there. gRPC services use Protobuf because that’s the wire format. Web APIs lean JSON Schema because that’s what their tooling already speaks. The decision matters operationally; it does not matter philosophically. Any of the three will hold the line if you actually enforce them.

The social contract is the hard half — and the valuable one

Schema enforcement at the broker is table stakes. The reason most data platforms don’t have working contracts isn’t that registries are hard to install. It’s that nobody has agreed who owns the producer side, who owns the consumer side, and what the workflow looks like when someone needs to change something.

A working contract has four pieces of social plumbing:

  1. A producer team that is named on the schema and on call for breakage. Not “the platform team.” Not “data eng.” The product squad whose service emits the event.
  2. A consumer registry. Every team consuming the contract is listed. When someone wants to change it, the change request fans out to that list. No silent drift.
  3. A versioning rule everyone has internalized. Additive changes are minor. Removing or retyping a field is major. Major versions ship side-by-side and have a deprecation window measured in sprints, not days.
  4. An escalation path when producer and consumer disagree. Usually a 30-minute meeting with both leads and a data-platform rep. Not a Jira ticket that ages for six weeks.

Without those four pieces, the schema file is decoration. With them, the schema file is the artifact a real conversation crystallizes around.

What changes at sprint planning

This is where the forcing function shows up. Before contracts, the conversation at planning looks like: “We’re refactoring the checkout service this sprint.” Everyone nods. Three weeks later the marketing dashboard is wrong, attribution is broken for two days, and the data team is reverse-engineering what changed from a Slack thread.

After contracts, the conversation looks different. Someone on the product squad opens the planning doc and sees that ticket, and the next question — without prompting, because the workflow trains them to ask it — is: “Does this touch any of the contracts we own?” If the answer is yes, three things happen in the same hour:

  • A schema PR is opened against the contract repo with the proposed change.
  • The consumer registry auto-comments on the PR with the four downstream teams who depend on it.
  • One of those teams immediately responds: “we have a finance close-of-month dashboard that breaks if total_cents becomes nullable; can we ship a v3 instead and give us two sprints to migrate?”

That’s the forcing function. The argument that used to happen in a war room after a metrics outage now happens in a PR thread before the work starts. The cost shifts from incident response to ten minutes of coordination, and the coordination happens between the people who actually have the context.

It also changes who feels ownership for data quality. When the producer team’s name is on the contract and their CI fails on a breaking-change attempt, “the data is wrong” stops being a complaint lobbed over a fence at the analytics team. The producer fixes it because they shipped it.

What this is not

Contracts are not a substitute for observability — pipelines still break for reasons schemas can’t catch. They are not a substitute for data modeling — a well-typed event stream can still describe the wrong business process. And they are not free. A real contract program adds review overhead, registry tooling, and a deprecation discipline that some orgs don’t yet have the muscle for.

But every org that has shipped contracts seriously reports the same thing: the meetings get shorter, the dashboards stay green longer, and the conversation between product and data finally has a shared object to point at. That object happens to look like a schema. Its real job is to make two teams talk to each other before the breaking change ships.