Event sourcing makes your event stream the system of record. The current state of any entity is derived by replaying the events that describe what happened to it. This architecture has well-understood benefits: complete audit history, temporal queries, easy reconstruction of state at any point in time, natural integration with CQRS. It also has a constraint that most database-centric architectures do not have to worry about: the events you write today will be replayed in the future, possibly years from now. A bad event written today is a permanent bad event.
In a traditional database architecture, you can correct a mistake by updating the record. The history of the mistake disappears. In event sourcing, you can append a corrective event, but the original bad event remains in the log. Depending on how your projections and read models are built, they may need to handle both the bad event and the correction. This is manageable but adds complexity and requires every future consumer of that stream to be aware of the data quality history.
The implication is direct: schema contracts in event-sourced architectures are not a nice-to-have governance practice. They are a data integrity requirement. The event log is your database. Dirty writes into a database are serious. Dirty writes into an immutable event log are more serious because there is no UPDATE to clean them up.
Why Schema Contracts Are Different in Event Sourcing
In a standard Kafka pipeline (not event-sourced), a schema violation typically affects real-time processing. Fix the producer, fix the consumer, re-process the last hour of events from the DLQ, done. The bad events are in a bounded window.
In an event-sourced architecture using Kafka with long retention (or infinite retention via log compaction or tiered storage), the bad events are permanent. A projection built six months from now will replay the entire log, including the window where the schema violation occurred. That projection needs to handle the bad data, and the logic to handle it needs to be maintained indefinitely.
Consider a concrete case: a financial application using event sourcing to track account balances. An AccountCredited event schema has an amount field. A producer bug causes 200 events to be written with amount as a string ("1500") instead of a decimal (1500.00). These events pass structural schema validation if the field is typed as a union of string and decimal (which is a common defensive typing pattern). Downstream projection code that casts the amount to a decimal parses "1500" as the string "1500" and throws an exception.
In event sourcing, you cannot delete those 200 events. You can write 200 compensation events that correct the bad credits. But every projection that reads account history now needs to understand the compensation events and why they exist. Six months later when someone writes a new report that reads the event log, they need to know about this, or their numbers will be wrong.
What a Schema Contract Looks Like in Practice
A schema contract for an event-sourced stream is more than a registered Avro schema. It is an explicit specification of what a valid event looks like across three dimensions:
Structural validity: fields present, types correct, required fields non-null. This is what a schema registry enforces. It is table stakes.
Value validity: numeric fields within expected domain ranges, string fields containing expected patterns or enum values, timestamp fields in expected epoch ranges. A schema registry does not enforce this. Application-level validation in the producer can enforce some of it, but not the cases where values shift gradually or context-dependently.
Business rule consistency: cross-field and cross-event constraints. An OrderShipped event should have a corresponding OrderCreated event with the same order ID earlier in the log. An AccountCredited event should have a non-negative amount. These are domain-specific invariants that cannot be expressed in a schema definition but are just as important for event log integrity.
Most teams implementing event sourcing focus on structural validity and address value validity and business rule consistency only after an incident forces the issue. We are not saying this is wrong as a starting point. Building full contract validation from scratch is significant engineering work. But the cost of discovering violations after the fact in an event-sourced architecture is higher than in a mutable-state architecture, which argues for earlier investment in the validation layer.
The Replay Problem: Why Contracts Matter More Over Time
One of the most common surprises teams encounter after adopting event sourcing at scale is the replay problem. Everything works fine for the first year. Then you need to build a new projection, or migrate to a new read model, or audit the historical event log for a compliance investigation. You replay the full event log and discover that events from 18 months ago have a slightly different schema than current events because the producer changed three times since then.
If those historical schema variations were documented and intentional, you can write a projection that handles all three versions. If they were undocumented behavioral drifts that nobody noticed at the time, you have an archaeology problem: figuring out what each version of the schema actually was and when the transitions happened.
Schema contracts, maintained properly, solve this problem in advance. Every version of a topic's behavioral contract should be documented with the date range it applied. This is a requirement of treating the event log as a durable system of record, not just a temporary message queue.
Enforcing Contracts at the Stream Level
There are three enforcement points for schema contracts in an event-sourced architecture, and they complement each other.
At produce time: schema registry enforcement for structural validity, plus application-level validation in the producer service for value validity and business rules. This is your first line of defense. It prevents violations from entering the log.
At stream time: behavioral monitoring that watches the live stream and flags deviations from the expected behavioral contract. This catches the violations that production-time enforcement misses: edge cases that did not appear in testing, environmental variables that differ in production, gradual value drift that passes one-at-a-time validation but is statistically anomalous at scale.
At read time: projection code that handles known historical schema variations explicitly. This is a fallback, not a primary defense. A projection that needs to handle multiple historical schema versions is a sign that earlier enforcement was incomplete.
The stream-time monitoring layer is where Streamforge fits in this picture. For event-sourced topics specifically, we recommend enabling the "durable stream mode" configuration, which maintains a longer behavioral baseline (rolling 90-day window instead of the default 30-day) and enables schema version tracking that correlates behavioral shifts with specific produce timestamps. This creates an audit trail of when the event schema's behavioral profile changed, which is exactly what you need when replaying historical events.
Corrective Events vs Log Compaction: Handling Violations That Did Slip Through
When a schema violation does make it into the event log, you have two options: accept it as part of the history and handle it in all projections, or use corrective compensation events to signal the correction.
Compensation events are the standard event sourcing approach. You append an EventCorrected or domain-specific compensation event that carries the corrected values. Projections that care about the correction can check for compensation events before computing state. This preserves the immutability of the log while enabling correction.
The thing to be aware of: compensation events are not a substitute for prevention. Every compensation event you write creates a projection complexity that lives with your codebase permanently. Prevention through schema contracts is always cheaper than post-hoc compensation, even if the compensation event pattern is technically sound.
We built the monitoring layer we wished we had before writing our first compensation event. Getting alerted within minutes of a bad event window entering the stream, before it has generated significant log volume, means the quarantine and correction process is manageable. Discovering it two weeks later during a projection rebuild is not.