Event Versioning and Compatibility: Additive Evolution, Semantic Breaks, and Migration Patterns
Learn Java API Contract Engineering, Event Contract Engineering & Schema Governance - Part 020
Event versioning and compatibility for Java event-driven systems: additive evolution, semantic breaks, consumer lag, replay compatibility, dual publish, upcasting, translation topics, and deprecation strategy.
Part 020 — Event Versioning and Compatibility: Additive Evolution, Semantic Breaks, and Migration Patterns
Tujuan Pembelajaran
Event versioning lebih sulit daripada API versioning karena consumer bisa:
- tertinggal jauh dari producer;
- membaca event lama saat replay;
- menyimpan local projection;
- melakukan side effect;
- tidak selalu terdaftar;
- memproses event secara asynchronous;
- membaca data dari topic retention/archive;
- menggunakan schema lama dan code lama;
- menerima duplicate/out-of-order events;
- bergantung pada semantics yang tidak tertulis.
Jika API /v2 saja sudah bisa menjadi contract debt, event v2 yang buruk bisa menjadi distributed data debt.
Setelah part ini, kamu harus mampu:
- membedakan schema version, event type version, envelope version, topic version, and semantic version;
- menentukan kapan event bisa berevolusi in-place;
- menentukan kapan butuh event type baru;
- mengelola consumer lag dan replay compatibility;
- memakai dual publish, upcasting, translation topics, and compatibility adapters;
- membedakan structural compatibility dan semantic compatibility;
- membuat deprecation/sunset plan untuk event;
- mendesain migration pattern yang aman untuk Java/Kafka/event systems;
- menghindari event versioning anti-pattern.
1. Core Principle: Event History Makes Compatibility Harder
HTTP API biasanya berurusan dengan current request/response. Event stream berurusan dengan history.
Producer bukan hanya harus kompatibel dengan consumers sekarang, tetapi juga:
- old events stored in topic/archive;
- old consumers reading new events;
- new consumers reading old events;
- replay of mixed schema versions;
- local projections built from historical data.
Therefore:
Event compatibility is time compatibility.
2. Version Vocabulary
| Version | Meaning |
|---|---|
schemaVersion | version of payload schema artifact |
eventVersion | version of event contract/type |
eventType version | encoded in event type name, e.g. CaseApprovedV2 |
envelopeVersion | version of common metadata/envelope |
topicVersion | topic/address version, e.g. case-events-v2 |
producerVersion | software deployment version |
consumerVersion | consumer software version |
aggregateVersion | domain state sequence number |
businessRuleVersion | version of decision/rule applied |
SDK version | consumer library version |
Do not mix them.
Example event:
{
"metadata": {
"eventType": "CaseApproved",
"eventVersion": "1.2",
"schemaRef": "case.CaseApproved:7",
"envelopeVersion": "1.0",
"aggregateVersion": 17
}
}
This does not mean topic or service is version 1.2.
3. Structural vs Semantic Compatibility
3.1 Structural Compatibility
Can old/new schemas parse/read messages?
Examples:
- Avro reader can read writer data;
- Protobuf old reader ignores new field;
- JSON Schema new optional field accepted by tolerant consumer.
3.2 Semantic Compatibility
Does meaning remain safe for consumer behavior?
Example structurally compatible but semantically breaking:
Old:
{
"eventType": "CustomerActivated",
"customerId": "cus_123"
}
Meaning:
customer may transact
New meaning:
customer profile is active but transaction access may still be blocked
Schema unchanged. Consumers break.
3.3 Compatibility Matrix
Most dangerous: structurally compatible + semantically unsafe.
4. Event Evolution Directions
Compatibility direction matters.
4.1 New Consumer Reads Old Events
Needed for replay/bootstrap.
This is backward compatibility in common schema-registry terms: new reader can read old data.
4.2 Old Consumer Reads New Events
Needed for independent producer deployment while old consumers still run.
This is forward compatibility: old reader can read new data.
4.3 Both Directions
Full compatibility.
4.4 Across All Versions
Transitive compatibility.
For event streams with long retention and unknown consumer lag, transitive compatibility is often important.
5. In-Place Additive Evolution
Best path when possible.
Old event:
{
"eventType": "CaseApproved",
"caseId": "case_123",
"approvedAt": "2026-06-29T05:00:00Z"
}
New event:
{
"eventType": "CaseApproved",
"caseId": "case_123",
"approvedAt": "2026-06-29T05:00:00Z",
"reasonCode": "EVIDENCE_COMPLETE"
}
If reasonCode optional/defaulted and old consumers ignore unknown fields, this is usually safe.
5.1 Additive Evolution Requirements
- new field optional for old data;
- old consumers tolerate unknown field;
- field absence semantics documented;
- event meaning unchanged;
- schema registry compatibility passes;
- generated clients do not break;
- examples updated;
- consumer tests include unknown fields if possible.
5.2 Additive But Dangerous
Adding enum value is structurally small but semantically dangerous.
Adding reasonCode might be safe structurally but consumer may begin relying on it before it is stable.
Mark field lifecycle if needed:
reasonCode:
lifecycle: experimental
stability: do-not-use-for-automated-decisions-yet
6. When to Create New Event Type
Create new event type when old event meaning cannot remain true.
Old:
CustomerActivated
Meaning overloaded.
Need split:
CustomerLifecycleActivated
CustomerTransactionAccessGranted
KycVerificationCompleted
Do not keep publishing CustomerActivated with changed meaning.
6.1 New Event Type Conditions
Use new event type if:
- business fact changed;
- authority changed;
- event lifecycle point changed;
- required consumer action changes;
- payload model fundamentally different;
- old event name is misleading;
- old event cannot carry new semantics additively;
- replay of old and new event under one type would be ambiguous;
- compatibility adapter would lie.
6.2 Example
Bad in-place semantic change:
PaymentAuthorized now means authorization requested, not completed.
Better:
PaymentAuthorizationRequested
PaymentAuthorized
PaymentAuthorizationFailed
7. Event Type Versioning
Options:
7.1 Version in Event Type Name
CaseApprovedV2
Pros:
- explicit;
- old/new consumers can filter;
- clear schema separation.
Cons:
- event type proliferation;
- semantics often unclear;
- consumers must subscribe to both;
- migration burden;
V2says nothing about meaning.
7.2 Version in Metadata
{
"eventType": "CaseApproved",
"eventVersion": "2.0"
}
Pros:
- stable event type;
- version readable;
- can route by eventType+version.
Cons:
- consumers must branch inside handler;
- multi-version schema per event type;
- easy to forget version handling.
7.3 Version in Schema Reference
{
"eventType": "CaseApproved",
"schemaRef": "case.CaseApproved:8"
}
Pros:
- aligns with registry;
- avoids manual version field.
Cons:
- schema version may not equal semantic version;
- consumers may not inspect it;
- not enough for semantic changes.
7.4 Version in Topic
case-events-v2
Pros:
- strong isolation;
- consumers opt into new stream;
- clean ACL/config.
Cons:
- topic duplication;
- ordering/replay split;
- migration complexity;
- old/new events separated physically.
7.5 Recommended
Prefer in-place compatible evolution. Use new event type for new fact semantics. Use topic version only for major stream-level migration. Avoid reflex V2 suffix when additive change is enough.
8. Dual Publish Pattern
Producer publishes both old and new events during migration.
Use when:
- old consumers need time;
- new semantics differ;
- old event can still be truthfully produced;
- producer can maintain mapping;
- duplicate side effects are controlled.
8.1 Risks
- consumers may process both and duplicate effects;
- ordering between old/new events may matter;
- metrics double count;
- producer complexity;
- old event may become lie if semantics no longer map;
- deprecation may never finish.
8.2 Contract Requirements
dualPublish:
oldEvent: CustomerActivated
newEvent: CustomerLifecycleActivated
startDate: 2026-06-29
targetEndDate: 2027-03-31
consumerGuidance: Consumers must not subscribe to both for same side effect unless deduplicating by aggregateId+transition.
9. Upcasting Pattern
Upcasting transforms old event shape into newer in-memory representation for consumers.
Old stored event:
{
"eventType": "CaseApproved",
"caseId": "case_123"
}
New handler expects:
{
"eventType": "CaseApproved",
"caseId": "case_123",
"reasonCode": "UNKNOWN"
}
Upcaster:
public JsonNode upcast(JsonNode oldEvent) {
ObjectNode copy = oldEvent.deepCopy();
ObjectNode payload = (ObjectNode) copy.get("payload");
if (!payload.has("reasonCode")) {
payload.put("reasonCode", "UNKNOWN");
}
copy.withObject("/metadata").put("schemaRef", "case.CaseApproved:2");
return copy;
}
9.1 Use Cases
- event sourcing;
- projection rebuild;
- consumers reading historical events;
- migration from old schema to new internal model.
9.2 Risks
- upcaster invents data;
- default may be semantically false;
- chain of upcasters becomes complex;
- performance overhead;
- hidden compatibility logic;
- must be tested with old fixtures.
Rule:
Upcasting is acceptable when missing data can be safely defaulted or derived. It is dangerous when it fabricates business facts.
10. Translation Topic Pattern
Create new topic populated by translator from old event stream.
Use when:
- new topic contract needed;
- old producer cannot change quickly;
- many consumers need normalized stream;
- major schema/topic migration;
- backfill required.
10.1 Risks
- translator becomes critical infrastructure;
- latency added;
- ordering may change;
- failure/DLQ complexity;
- semantic translation may be lossy;
- ownership unclear.
10.2 Contract Requirements
translation:
sourceTopic: case-events-v1
targetTopic: case-events-v2
orderingPreserved: per-case
keyMapping: source.key -> target.key
backfillSupported: true
semanticLoss: none
ownerTeam: event-platform
11. Compatibility Adapter in Consumer
Consumer supports multiple versions.
public void handle(EventEnvelope<JsonNode> event) {
switch (event.metadata().schemaRef()) {
case "case.CaseApproved:1" -> handleV1(mapV1(event));
case "case.CaseApproved:2" -> handleV2(mapV2(event));
default -> quarantine(event, "UNSUPPORTED_SCHEMA");
}
}
Pros:
- consumer controls migration;
- producer simpler;
- useful for replay.
Cons:
- consumer complexity;
- every consumer repeats logic;
- risk of inconsistent interpretation.
For many consumers, prefer shared SDK/adapter library.
12. Schema Registry Compatibility Modes
Registry compatibility modes such as backward, forward, full, and transitive are structural guardrails.
They answer:
Can schemas read each other's data under registry rules?
They do not answer:
Did event meaning remain safe?
Was Kafka key changed?
Was topic retention changed?
Did side-effect consumers break?
12.1 Backward
New schema can read old data. Good for replay with new consumers.
12.2 Forward
Old schema can read new data. Good for old consumers while producers upgrade.
12.3 Full
Both backward and forward.
12.4 Transitive
Checks against all previous versions, not just latest. Important for long-lived event history.
12.5 Governance Rule
For stable event streams:
compatibility:
schema: backward-transitive
semanticReviewRequired: true
kafkaContractReviewRequired: true
13. Consumer Lag
Consumers may be behind.
Event versioning must account for:
- consumer not deployed for weeks;
- consumer group lag;
- paused consumer;
- replay from earliest;
- local schema cache old;
- generated model old;
- old SDK version;
- disaster recovery consumer reading archive.
Producer cannot assume all consumers upgrade immediately.
13.1 Lag-Aware Release
Before incompatible event change:
- identify consumers;
- measure consumer lag;
- check schema versions used;
- verify unknown field handling;
- publish new schema;
- deploy producer compatibility mode;
- monitor consumer errors;
- migrate consumers;
- only retire old event after lag cleared and consumers upgraded.
14. Replay Compatibility
Replay is stricter than live consumption.
A new consumer in July may replay January events.
Questions:
- are old schemas still available?
- can new code read old events?
- are old reference data/rules available?
- are old event semantics documented?
- are upcasters available?
- are tombstones/corrections included?
- are old event types still understood?
- can side effects be disabled?
- are historical PII policies respected?
14.1 Replay Test
CI should include old fixtures.
@Test
void projectionCanReplayCaseApprovedV1V2V3() {
List<EventEnvelope<JsonNode>> events = fixtures.load(
"CaseSubmitted-v1.json",
"CaseApproved-v1.json",
"CaseApproved-v2.json",
"CaseClosed-v3.json"
);
Projection projection = replay(events);
assertThat(projection.status()).isEqualTo("CLOSED");
}
15. Event Deprecation
Deprecation means event is still published but should not be used by new consumers.
Deprecation record:
eventType: CustomerActivated
deprecatedSince: 2026-06-29
replacement:
- CustomerLifecycleActivated
- CustomerTransactionAccessGranted
reason: CustomerActivated overloaded lifecycle and access semantics.
producer: customer-service
knownConsumers:
- onboarding-service
- crm-sync
- notification-service
sunsetTarget: 2027-03-31
removalCondition: No live consumers and replay archive translation available.
15.1 Deprecation Communication
- AsyncAPI updated;
- catalog marks deprecated;
- schema registry metadata updated;
- consumer teams notified;
- migration guide published;
- examples updated;
- metrics dashboard created;
- sunset conditions defined.
16. Event Retirement
Retiring an event means producer stops publishing it.
Preconditions:
- consumer inventory complete;
- no live consumers or approved exceptions;
- topic usage metrics show no dependency;
- replay story exists;
- old schema archived;
- docs/catalog updated;
- DLQ/retry references removed;
- tests updated;
- rollback plan exists.
Warning:
Event retirement can break passive consumers you do not know about. Discovery and access governance matter.
17. Event Field Lifecycle
Field lifecycle:
Field can be experimental even in stable event, but this must be explicit.
Example metadata:
fields:
payload.riskBand:
lifecycle: experimental
consumerGuidance: Do not use for automated rejection decisions.
Without lifecycle, consumers may hard-depend.
18. Semantic Change Decision Tree
Principle:
Do not mutate event meaning in place.
19. Versioning Examples
19.1 Add Optional Field
Old:
{
"eventType": "CaseApproved",
"payload": {
"caseId": "case_123"
}
}
New:
{
"eventType": "CaseApproved",
"payload": {
"caseId": "case_123",
"reasonCode": "EVIDENCE_COMPLETE"
}
}
Compatible if optional/default and semantics unchanged.
19.2 Rename Field
Old:
"status": "ACTIVE"
New:
"lifecycleStatus": "ACTIVE"
Safer:
{
"status": "ACTIVE",
"lifecycleStatus": "ACTIVE"
}
Deprecate old.
19.3 Split Event
Old:
CustomerActivated
New:
CustomerLifecycleActivated
CustomerTransactionAccessGranted
Use dual publish if old event still truthfully derived.
19.4 Change Event Timing
Old CaseApproved emitted when approval requested. New emitted after approval committed.
This is semantic breaking. Old event was misnamed. Create:
CaseApprovalRequested
CaseApproved
Migrate.
19.5 Change Topic Key
Old key = customerId. New key = caseId.
Even if schema unchanged, ordering/partitioning contract changes. Treat as breaking/dangerous.
20. Topic Versioning
Topic versioning:
case-events-v1
case-events-v2
Use when:
- key/partitioning changes;
- retention/compaction model changes;
- security classification changes;
- event type set changes radically;
- schema format changes;
- old and new stream must run independently.
Avoid for small schema additions.
20.1 Migration Plan
- create v2 topic;
- publish v1 and v2 or translate v1 to v2;
- onboard new consumers to v2;
- migrate old consumers;
- monitor lag and usage;
- freeze v1 for new consumers;
- retire after conditions met.
21. Envelope Versioning
Envelope changes affect all events.
Safe:
- add optional metadata field;
- add extension object;
- add optional governance classification.
Breaking:
- rename
metadata; - rename
payload; - remove
eventId; - change
occurredAtformat; - change correlation semantics;
- change eventType location;
- make previously optional metadata required for old messages without default.
Strategy:
- evolve envelope very slowly;
- version envelope separately;
- use common library;
- validate old fixtures;
- support multiple envelope versions if needed.
22. Event Schema Version in Payload?
Do you include schemaVersion field inside payload?
Usually better in metadata:
{
"metadata": {
"schemaRef": "case.CaseApproved:7"
}
}
Avoid scattering version fields inside payload unless business domain needs them.
Payload version can confuse consumers:
{
"payload": {
"version": 7
}
}
Is this schema version, aggregate version, or business document version?
Name precisely.
23. Consumer Capability Negotiation for Events
Unlike HTTP, events are broadcast. Consumer capability negotiation is harder.
Options:
- separate topics;
- separate consumer groups with filtering;
- per-consumer projection topics;
- registry of consumer capabilities;
- producer conditional publish by consumer is usually anti-pattern for broadcast events.
For events, prefer:
- compatible evolution;
- dual publish;
- translation topics;
- consumer adapters.
Do not make producer emit different event semantics per consumer unless using explicit targeted command/notification channel.
24. Migration Pattern Matrix
| Pattern | Use when | Main risk |
|---|---|---|
| Additive evolution | field addition, compatible schema | unknown consumer strictness |
| Dual field | rename/split simple field | inconsistency |
| New event type | new fact semantics | consumers process both/migration |
| Dual publish | old/new consumers coexist | duplicate side effects |
| Upcasting | replay old events into new model | fabricated semantics |
| Translation topic | major stream migration | translator ownership/latency |
| Topic v2 | key/security/format changes | operational duplication |
| Consumer adapter | few consumers, complex mapping | repeated logic |
| Snapshot backfill | projection rebuild | loses event history |
| Deprecation only | discourage new use | no actual migration if not enforced |
25. Java Implementation: Versioned Handler
public interface EventHandler {
void handle(EventEnvelope<JsonNode> event);
}
Version dispatch:
public final class CaseApprovedDispatcher implements EventHandler {
private final CaseApprovedV1Handler v1Handler;
private final CaseApprovedV2Handler v2Handler;
@Override
public void handle(EventEnvelope<JsonNode> event) {
String schemaRef = event.metadata().schemaRef();
switch (schemaRef) {
case "case.CaseApproved:1" ->
v1Handler.handle(mapV1(event));
case "case.CaseApproved:2" ->
v2Handler.handle(mapV2(event));
default ->
throw new UnsupportedEventSchemaException(schemaRef);
}
}
}
Better: normalize to internal canonical model.
CaseApproved canonical = upcaster.upcast(event);
caseApprovedHandler.handle(canonical);
26. Java Upcaster Chain
public interface EventUpcaster {
boolean supports(String eventType, String schemaRef);
EventEnvelope<JsonNode> upcast(EventEnvelope<JsonNode> event);
}
Chain:
public final class UpcasterChain {
private final List<EventUpcaster> upcasters;
public EventEnvelope<JsonNode> upcastToLatest(EventEnvelope<JsonNode> event) {
EventEnvelope<JsonNode> current = event;
boolean changed;
do {
changed = false;
for (EventUpcaster upcaster : upcasters) {
if (upcaster.supports(
current.metadata().eventType(),
current.metadata().schemaRef()
)) {
current = upcaster.upcast(current);
changed = true;
break;
}
}
} while (changed);
return current;
}
}
Governance:
- upcasters versioned;
- old fixtures tested;
- no silent data fabrication without marker;
- performance measured;
- deprecation timeline clear.
27. Java Dual Publish Mapper
public void publishCustomerActivation(CustomerActivatedDomainEvent domainEvent) {
CustomerActivated legacy = legacyMapper.toCustomerActivated(domainEvent);
CustomerLifecycleActivated lifecycle = newMapper.toCustomerLifecycleActivated(domainEvent);
eventPublisher.publish("customer-events", legacy.metadata().aggregateId(), legacy);
eventPublisher.publish("customer-events", lifecycle.metadata().aggregateId(), lifecycle);
}
Safety:
- same correlationId;
- different eventId for different facts or same causation ID depending semantics;
- docs warn consumers;
- metrics track both;
- sunset plan exists.
Do not reuse same eventId for two different event types unless your dedup semantics explicitly require grouping and consumers understand it.
28. Contract Tests for Event Evolution
28.1 Old Event Replay Test
@Test
void latestProjectionCanReplayAllHistoricalCaseApprovedVersions() {
List<EventEnvelope<JsonNode>> events = fixtureLoader.loadAllVersions("CaseApproved");
Projection projection = projectionRebuilder.rebuild(events);
assertThat(projection).isConsistent();
}
28.2 Old Consumer Reads New Event
If forward compatibility required:
@Test
void oldConsumerIgnoresNewOptionalField() {
EventEnvelope<JsonNode> newEvent = fixture("CaseApproved-v2-with-reason.json");
oldConsumer.handle(newEvent);
assertThat(oldConsumer.errors()).isEmpty();
}
28.3 Semantic Regression Test
@Test
void customerActivatedStillMeansLifecycleActivatedNotAccessGranted() {
CustomerActivated event = produceCustomerActivated();
assertThat(event.payload().lifecycleStatus()).isEqualTo("ACTIVE");
assertThat(event.payload()).doesNotHaveFieldOrProperty("transactionAccessStatus");
}
Better: if meaning split, stop relying on old event.
28.4 Kafka Contract Change Test
Validate key unchanged:
@Test
void caseApprovedKeyRemainsCaseId() {
ProducerRecord<String, ?> record = produceCaseApproved();
assertThat(record.key()).isEqualTo(record.value().metadata().aggregateId());
}
Schema compatibility test alone will not catch this.
29. Governance Checklist
29.1 Version Identity
- What version is changing: schema, event type, envelope, topic, key, or semantics?
- Is aggregateVersion confused with schemaVersion?
- Is eventVersion meaningful or redundant?
29.2 Compatibility
- Can new consumers read old events?
- Can old consumers read new events?
- Is transitive compatibility required?
- Does registry compatibility pass?
- Does semantic review pass?
29.3 Consumer Impact
- Who consumes this event?
- What schema versions are active?
- Are consumers lagging?
- Are side-effect consumers affected?
- Are projections replayed from history?
29.4 Migration
- Additive evolution possible?
- New event type needed?
- Dual publish needed?
- Upcaster needed?
- Translation topic needed?
- Topic v2 needed?
- Migration guide written?
29.5 Retirement
- Is old event deprecated?
- Is sunset date defined?
- Are usage metrics available?
- Are old schemas archived?
- Is replay compatibility preserved?
- Is rollback possible?
30. Anti-Patterns
30.1 EventTypeV2 Reflex
Creating SomethingV2 for every field addition.
30.2 Semantic Mutation In Place
Same event name, different meaning.
30.3 Schema Version Equals Business Version
Confusing registry version with domain fact version.
30.4 No Old Fixture Tests
Replay breaks silently.
30.5 Dual Publish Forever
Temporary migration becomes permanent debt.
30.6 New Topic for Small Additive Change
Operational duplication without need.
30.7 Upcaster Fabricates Facts
Default value falsely implies known business reason.
30.8 Retiring Event Without Consumer Inventory
Passive consumers break.
30.9 Compatibility Check Only
Registry passes, consumers still break due to semantics/key/order.
30.10 Version Hidden in Java Class Only
Runtime event does not carry schema identity.
30.11 Unknown Consumers Ignored
Internal broadcast topics often have more consumers than producer knows.
30.12 Changing Topic Key as “Implementation Detail”
It is not implementation detail if ordering/partitioning matters.
31. Practice Lab
Lab 1 — Field Addition
Add reasonCode to CaseApproved. Design:
- schema change;
- default/optional semantics;
- old consumer behavior;
- replay test;
- docs update.
Lab 2 — Semantic Split
Old event CustomerActivated means both lifecycle active and transaction access allowed. Split safely.
Design:
- new event types;
- dual publish strategy;
- deprecation record;
- consumer migration guide;
- retirement condition.
Lab 3 — Upcaster
Old event lacks caseVersion. New projection requires it. Decide whether upcasting is safe. If not, propose alternative.
Lab 4 — Topic V2
Topic key changes from customerId to accountId. Design migration to account-events-v2.
Lab 5 — Compatibility Classification
Classify:
- add optional field;
- add required field;
- remove field;
- rename field with alias;
- add enum value;
- change event timing;
- change Kafka key;
- change topic retention from 90 days to 7 days;
- change event source authority;
- add new event type;
- stop publishing old event;
- change envelope field name.
32. Senior Engineer Heuristics
- Event compatibility is time compatibility.
- Schema version is not event meaning version.
- Aggregate version is not schema version.
- In-place evolution is best only when semantics remain true.
- New event type is better than lying with old event name.
- Dual publish must have an end date.
- Upcasting must not fabricate business facts.
- Replay is the harshest compatibility test.
- Consumer lag makes forward compatibility important.
- Transitive compatibility matters for long-lived streams.
- Topic key changes are contract changes.
- Registry compatibility is necessary but not sufficient.
- Old fixtures are governance assets.
- Deprecation without telemetry is hope.
- Never mutate event semantics silently.
33. Summary
Event versioning requires thinking across time, consumers, replay, schemas, topics, keys, and semantics. The safest path is compatible additive evolution. When meaning changes, create new event types or migration streams rather than mutating old facts.
Main takeaways:
- event streams preserve history, so compatibility is harder than request/response;
- distinguish schemaVersion, eventVersion, envelopeVersion, topicVersion, and aggregateVersion;
- structural compatibility and semantic compatibility are different;
- new consumers must often read old events;
- old consumers may continue receiving new events;
- replay requires old schemas, old semantics, and old fixtures;
- dual publish, upcasting, translation topics, and topic v2 solve different problems;
- schema registry compatibility does not catch Kafka key/order/retention changes;
- event deprecation needs consumer inventory and telemetry;
- never change event meaning in place.
Part berikutnya membahas event contract testing: producer tests, consumer tests, schema registry gates, golden event samples, replay tests, Testcontainers, and false confidence traps.
You just completed lesson 20 in deepen practice. Use the series map if you want to review the broader track, or continue directly into the next lesson while the context is still warm.
Keep the momentum while the lesson is still fresh. Move backward for review or continue forward into the next concept.