What Makes a Good Kafka Event Contract
A good Kafka event contract has five properties: stable semantics, clear ownership, an explicit schema, a versioning strategy, and a documented compatibility policy. Stable semantics means the event represents a business fact that does not change meaning over time. For example, OrderCreated should always mean the same thing, regardless of how the system evolves. If the meaning changes, it should be a new event type, not a modification of the existing one. Clear ownership means there is a single team responsible for the event: they define the schema, approve changes, and handle incidents. Without ownership, events drift, schemas conflict, and consumers break. An explicit schema means the event has a defined structure, usually in Avro, Protobuf, or JSON Schema, stored in a schema registry. The schema is the contract; it should be versioned and enforced. A versioning strategy means there is a clear way to evolve the schema over time, with compatibility rules that prevent breaking changes. A documented compatibility policy means the team has decided whether the topic uses backward, forward, or full compatibility, and this is enforced by the registry. The trade-off is between flexibility and stability. A loose contract lets teams move fast but creates fragility; a strict contract slows changes but protects consumers. For a shared platform, strict is usually the right choice.
The mechanism for a good event contract has several parts. First, the event name should be domain-oriented: OrderCreated, PaymentFailed, UserRegistered. It should be a past-tense fact, not a command (CreateOrder) or a state (OrderStatus). Second, the event should carry a unique event ID, a timestamp, an aggregate ID, and the event payload. The event ID is used for deduplication; the aggregate ID is used for partitioning and ordering; the timestamp is used for event-time processing. Third, the event should be self-contained: consumers should not need to call back to the producer to understand the event. Fourth, the event should be immutable: once published, it should not be changed. If the data changes, a new event should be published. Fifth, the event should be versioned: the schema registry should track versions and enforce compatibility. The trade-off is between richness and size. A richer event is more self-contained but larger; a leaner event is smaller but may require consumers to fetch additional data. For most platforms, a moderate richness is best: include the fields that consumers need, but not the entire aggregate. Version note: schema registries support Avro, Protobuf, and JSON Schema. The choice depends on the ecosystem and the team's preferences. Avro is common in Kafka-native shops; Protobuf is common in gRPC-heavy shops; JSON Schema is common when human readability is important. Regardless of the format, the contract should be explicit and versioned.
A common mistake is to define events as commands or as database rows. Commands are not facts; they can be rejected, so they are not good events. Database rows are internal representations; they couple consumers to the database schema. Another mistake is to change the meaning of an event without changing its name, which breaks consumers that rely on the old meaning. A third mistake is to skip the schema registry and rely on an implicit JSON contract, which breaks when a field is added or removed. The trade-off is between the freedom to change and the stability of the contract. A good contract is stable but evolvable: it can add optional fields and deprecate old ones, but it does not change the meaning of existing fields. Version note: Confluent Schema Registry, AWS Glue Schema Registry, and Apicurio are common choices. They differ in features and cost, but the principles are the same: explicit schema, versioning, compatibility enforcement. For a multi-team platform, the registry should be integrated with the CI/CD pipeline so that schema changes are checked before deployment.
Five properties: stable semantics, clear ownership, explicit schema, versioning, and compatibility policy.
Event name should be a past-tense business fact, not a command or a state.
Include eventId, occurredAt, and aggregateId in every event.
Events should be self-contained and immutable.
Use a schema registry to enforce compatibility and track versions.
Avoid commands and database rows as events.
Integrate schema compatibility checks into CI/CD.
FULL_TRANSITIVE is safest for shared topics.
0-2 years experience
2-5 years experience
5-8 years experience