A column appears in a table. A producer publishes a renamed field. A consumer still receives the old messages. On paper, the change may look small. In practice, it can interrupt synchronization, make a process silently wrong or send incomplete data until someone notices the gap.
The question is not simply whether a schema has changed. The real questions are whether the change alters a contract, who can detect it, what guarantees apply to consumers and in what order teams should update their systems. A dependable approach combines three safeguards: a versioned data contract, a Change Data Capture stream—CDC, meaning the capture of changes made to data—and automated checks that run before merging and again before deployment.
The Real Risk Is Not Change but Surprise
Data systems are always evolving. One team adds an attribute for a new business need. Another replaces a numeric identifier with a text value. A reporting pipeline starts reading a table directly, even though its owner believed only a stable view was exposed. Each decision may be reasonable in isolation. The break occurs when the assumptions held by other teams remain invisible.
Two ideas are often confused here. A schema evolution concerns the declared shape of data: its fields, their types, whether they are present and the structure of nested objects. A contract change affects the promise made to consumers. It may alter the meaning of a field, the quality expected from it, the publication frequency, its owner or the way a missing value should be interpreted. Adding an optional column may be a compatible evolution. Reusing an existing column to express a different meaning is a contract change, even if the schema file remains valid.
That distinction explains why syntax validation alone is not enough. A document can follow the expected grammar and still introduce a functional ambiguity. Conversely, a technically visible change may have no effect on consumers if it respects the agreed guarantees. Detection therefore needs to answer three questions: what changed, who depends on the data and which versions can coexist?
The risk grows when several mechanisms are layered together. A relational database changes, a CDC tool turns its rows into events, a registry checks the message schema and a sink flattens the events before writing them elsewhere. At every stage, information can be renamed, lost or interpreted differently. The control chain must follow the same path as the data, while keeping each responsibility clear.
The first principle is straightforward: do not wait for a consumer to fail before discovering that a contract existed. Make the contract explicit, comparable and testable.
A Data Contract Describes a Promise, Not Just Fields
A data contract gives producers, transformers and consumers a shared reference for a dataset. The Open Data Contract Standard, described in its documentation as a contract represented in a YAML file, can bring together structure, semantics, quality, ownership and version information. This approach is useful because it changes the discussion. Teams are no longer comparing only two technical definitions; they are comparing two published promises.
A sufficiently explicit contract should answer questions such as:
- What structure is exposed, and what type does each field have?
- What does each value mean, and which unit or convention applies?
- What quality rules are expected?
- Who owns the data, and who can approve its evolution?
- Which version is being consumed, and how long will the previous version remain available?
Versioning is not just a number added to a filename. It should make the contract’s trajectory understandable. A comparison between two versions should show an added field, a strengthened constraint, a changed type or a revised definition. The ODCS documentation describes ways to validate the contract structure, compare two versions with a changelog command and test conformity against a real source. These operations give review teams something concrete to examine before discussing a production date.
Conformity with a real source is especially important because it prevents the contract from becoming theoretical. A schema may say that a field is always populated while the produced data contains missing values. It may also declare a range or format that the flows regularly fail to respect. This check does not replace business analysis, but it exposes the gap between the promise and observed behaviour.
Formalisation should not become a second bureaucracy. A good contract does not attempt to describe every internal detail of the producer. It describes what consumers can reasonably depend on. This is where ownership becomes practical: someone must be able to explain a field’s meaning, approve an evolution and organise its deprecation.
The contract must also be usable by tools. If it lives only on a page that is difficult to compare, it cannot block a merge or trigger a test. A machine-readable file becomes a development artefact: versioned alongside the code or in a contract repository, reviewed by the team and used as a reference by the CI/CD pipeline.
CDC Exposes Change but Does Not Judge Its Severity Alone
Change Data Capture observes changes made to a source and publishes them as events. Debezium’s documentation describes events that include the operation performed, source metadata and the state before and after the change. For a team investigating a break, that envelope is valuable. It helps distinguish a creation, update or deletion and places the event back in the context of its origin.
CDC answers a different question from the contract. The contract says what should be accepted. CDC shows what actually happened. One formalises an expectation; the other provides observations. Bringing them together can reveal a discrepancy—for example, a field becoming absent in events even though the contract declares it required, or a structure changing without a corresponding contract version.
Depending on the connector, the flow may also expose dedicated events for schema changes. These should not be treated as ordinary messages. Their purpose is to signal that a data structure, table or relationship has evolved. They can feed an alerting chain, trigger a comparison with the last accepted version or open a validation step before consumers receive the new shape.
One precaution deserves particular attention: do not discard the envelope too early. Flattening CDC events produces simpler messages for some consumers, but it can remove or hide part of the original context. A “new record state” representation may suit a sink whose main purpose is to obtain the latest value. It is less suitable for diagnosis, where the team needs to know which operation took place, when it happened and what state preceded it.
A cautious architecture therefore keeps complete events in the Kafka flow and applies flattening at the sink when that choice is necessary. Final consumers receive a representation suited to their use, while the team retains the material needed to investigate a break. This separation avoids sacrificing observability in order to simplify an interface.
CDC does not guarantee compatibility, however. It can capture and transport a change that some consumers cannot process. It does not replace a schema registry, a versioned contract or a consumer test. Its value appears when it becomes a signal connected to those controls: a change is detected, the contract is compared, known consumers are identified and the migration is followed.
Choosing Compatibility Means Choosing a Migration Order
In a Kafka flow using a schema registry, the compatibility policy defines which versions can coexist. This is not an abstract registry setting. It implicitly indicates which part of the system should move first.
Backward compatibility guarantees that a new schema can be read with data produced by the old schema, according to the rules covered by the policy. In this mode, Confluent’s documentation indicates that consumers should be upgraded before producers begin publishing new events. Consumers must therefore know how to handle both the old and new forms before the latter arrives.
Forward compatibility reverses that logic: the old consumer must be able to read new events. Producers are upgraded before consumers. This strategy can help when a team cannot deploy all its readers at once, provided that the guarantee matches the actual behaviour of the serializers and applications involved.
Full compatibility seeks to cover both directions. Producers and consumers can evolve more independently, subject to the guarantees actually covered by the policy. The transitive versions of these modes extend comparison across several earlier versions rather than only the immediately previous one. That difference matters when multiple versions remain active during a migration.
There is no universally best policy. The choice depends on the flow topology, how long versions must coexist and whether consumers can be deployed together. A team that enables a strict policy without adapting its process may block legitimate changes. A team that permits everything may keep the registry consistent while breaking an application that relies on an unstated convention.
In practice, additive changes are a safer starting point: add an optional field, preserve existing fields and do not reinterpret a value that is already being consumed. But “additive” does not automatically mean “risk-free”. A field declared optional may become essential to a new consumer’s logic. A default value may conceal a production defect. The contract must therefore explain what absence means, and tests must cover real uses.
A useful decision rule is to write down the expected order before modifying the schema. If consumers must be deployed first, the pipeline should verify that they can read both versions. If the producer moves first, the team must demonstrate that existing readers tolerate the new shape. The registry provides a safeguard, but the operational sequence turns that safeguard into real protection.
Checks Must Block Before the Merge, Not Only Alert Afterwards
An effective detection chain starts in the repository. When a contract or schema changes, the CI/CD pipeline compares the new version with a known reference. It validates the contract structure, produces a readable changelog and runs the selected compatibility rules. If the change is incompatible, the merge should be blocked or made subject to an explicitly documented approval.
This first barrier checks the declared shape and guarantees. It should be complemented by representative test data and, where the contract permits it, a conformity check against a real source. The aim is not to claim complete coverage. It is to detect the most expensive discrepancies while they are still cheap to correct—before they reach the registry or the flow.
The second barrier concerns the interactions that are actually consumed. Consumer-driven contract tests, such as those documented by Pact, do not merely ask whether two schemas are identical. They verify exchanges and the fields a consumer truly uses. This distinction prevents the team from treating every reader as though it depended on the full surface of a message.
A schema compatibility test may accept an evolution that remains problematic for an application. Conversely, a consumer contract test may show that a seemingly secondary field is essential to one specific interaction. The two controls do not replace each other. The first protects a declared interface; the second protects an observed use.
For Protobuf schemas, a tool such as Buf can compare a current version with an earlier reference and detect incompatible changes according to configured rules. The value is not the tool itself but the principle: make comparison deterministic and run it when the change is still inexpensive to fix.
The pipeline should produce a result that a human team can understand. “Compatibility check failed” is too vague. Reviewers should be able to see which field was removed, which type changed, which constraint was strengthened and which consumer is affected, when that information is available. The decision can then be both technical and operational: correct the change, publish a new version or accept a planned break with a migration plan.
Finally, the check must exist at two moments. Before the merge, it protects the repository and prevents an incompatible schema from becoming normal. Before deployment, it protects the target environment against a divergence between the version tested and the version actually published. These barriers cannot guarantee that no incident will occur. They move discovery to a point where the team can still choose what to do.
Deploying Without Breakage: Coexistence, Observation and Retirement
A safe migration begins with a change that can coexist with the old version. The clearest case is an optional field whose meaning, format and absent-value behaviour are documented. The producer can then start populating it while existing consumers continue to work. This additive step does not remove the need for testing; it creates a transition zone.
When the evolution is deeper, dual writing or dual reading can provide a migration mechanism. With dual writing, the producer temporarily publishes both the old and new formats. With dual reading, the consumer can read both representations and may compare their results. These techniques add complexity and should not be introduced automatically. They are appropriate when the cost of a break is greater than the cost of a controlled coexistence period.
The sequence depends on the chosen compatibility policy. With backward compatibility, consumers are first made capable of reading the new version, and production of new events follows. With forward compatibility, the producer is upgraded first so that old consumers can continue reading the events. With full compatibility, the systems have more independence, but that independence remains subject to the defined rules and to consumers that are missing from official inventories.
CDC helps the team observe this transition. Metrics can track the presence of old and new forms, deserialization errors, rejected messages and schema-change events. Complete events preserve the context needed to connect an anomaly with an operation and its source. A useful alert does not merely say that a flow changed. It indicates which version was published, which contract was expected and which population of consumers remains active.
Deprecation should be treated as a step, not an intention. Before removing the old version, the team checks that known consumers have migrated and that no active reader still depends on the previous form. Tools do not always reveal indirect uses or undeclared access. The decision should therefore combine registry signals, tests, pipeline metrics and the teams’ knowledge.
Retirement can then proceed gradually: reduce production of the old form, maintain an observation period and remove compatibility only once coexistence is no longer needed. This discipline avoids the familiar situation in which an old version remains officially supported without limit, eventually making every evolution difficult.
An Operating Method That Connects Contracts, CDC and CI/CD
The method depends less on one particular tool than on continuity between the stages. It can guide a team that must change a schema without knowing precisely who all its consumers are.
- Describe the promise. Create or update the machine-readable contract: structure, meaning, quality, ownership, version and rules for absence or default values. Do not stop at field names.
- Compare with the existing version. Produce the changelog between the proposed version and the previous reference. Classify every modification as additive, behavioural, incompatible or merely documentary.
- Choose the guarantee. Define backward, forward, full or a transitive variant according to the possible deployment order and the required coexistence period. Write that order into the migration plan.
- Test actual uses. Run schema compatibility checks, then producer-consumer contract tests against the interactions that are truly used. Add conformity testing against real data when the source and rules make it possible.
- Publish compatibly. Prefer an additive change. If that is not possible, plan for dual reading or dual writing, with a defined coexistence period and a clearly identified owner.
- Observe the CDC flow. Keep complete events when diagnosis and history matter. Connect schema-change events, consumption errors and conformity metrics to the contract version.
- Retire with evidence. Verify that no active consumer still uses the old version, reduce its use progressively and remove compatibility only after the planned observation period.
This procedure also reveals blind spots. If nobody knows who consumes a dataset, the problem is not only a missing test; it is a weakness in governance and inventory. If consumers are known but cannot be tested, the interface may be too implicit. If CDC contains the context but the sink removes it, the diagnostic chain is incomplete.
There is a temptation to multiply controls until every change becomes painful. That would be the wrong response. The better question is: what guarantee does this particular flow need, and what evidence can be produced at a reasonable cost? A critical, shared flow deserves a strict policy, consumer tests and detailed observation. A flow internal to one service may use a lighter process, provided its boundaries are genuinely controlled.
Contracts, CDC and CI/CD therefore operate at three different levels. The contract names the interface. CDC exposes the facts. CI/CD turns the rules into a decision before delivery. Resilience comes from connecting these layers, not from the isolated sophistication of any one of them.
Conclusion
A schema change becomes dangerous when it arrives without context, versioning or a migration order. Reliable detection starts with a data contract that describes not only structure but also meaning, quality, ownership and the lifetime of a version. It continues with CDC that shows the changes actually produced, without discarding the envelope that makes those changes understandable too early.
Backward, forward and full policies then provide a framework for coexistence, but they do not choose the strategy for the teams. They indicate which versions can read one another and, as a result, who must be deployed first. Compatibility tests and tests centred on consumer interactions complete that protection before merging and before deployment.
The most pragmatic method remains gradual: version, compare, test, publish an additive change where possible, temporarily maintain two forms when necessary, observe CDC events and remove the old version only after verification. This is not a promise that incidents will disappear entirely. It is better than that: a way to make breaks more visible, decisions more reversible and migrations less dependent on discovering the problem in production.
Sources
- Schema Evolution and Compatibility Types for Confluent Platform — Confluent
- Debezium Documentation — Debezium
- New Record State Extraction — Debezium
- Open Data Contract Standard — Data Contract CLI
- Introduction — Pact
- Detecting breaking changes — Buf
Daymain Team