Why this matters
- Teams confuse "async" with "fully decoupled" while sharing brittle protobuf schemas that break three consumers on every deploy.
- Consumer-driven contracts and schema registry compatibility checks catch breaking changes before production.
- ShardPay uses Confluent Schema Registry with BACKWARD compatibility on settlement topics — producers evolve, old consumers keep working.
- Interviewers ask what couplings remain in event-driven architecture and how you govern shared contracts.
Coupling types in event-driven systems
Temporal coupling: caller blocks until callee responds — synchronous REST. Spatial coupling: caller knows host/port of callee. Schema coupling: producer and consumers share event structure; change the schema, break consumers if unmanaged.
ShardPay's TransferCompleted event removes temporal coupling — fraud scores when ready, notifications fan out independently. Spatial coupling is reduced via Kafka bootstrap URLs and service discovery. Schema coupling remains: every consumer deserializes the same Avro record.
Walkthrough: the breaking field rename
A developer renames amountCents to amountMinorUnits without compatibility check. Schema registry rejects in CI — good. An earlier incident before registry adoption deployed rename; three consumers failed deser; settlement pipeline stalled 40 minutes. ShardPay now requires additive-only changes (new optional field) with BACKWARD mode and consumer contract tests in CI.
Key points
- Temporal coupling — caller waits for callee completion; events decouple time by buffering in broker. ShardPay's transfer API returns 202 after ledger commit while
TransferCompletedconsumers process over the next seconds without blocking the user. - Spatial coupling — dependency on network location of service instance; Kafka topic names replace direct URLs but consumers still depend on topic contract and cluster address. ShardPay abstracts cluster via internal DNS; teams depend on topic names in service catalog, not pod IPs.
- Schema coupling — shared structure of payload fields, enums, and semantics.
TransferCompleted.statusenum change affects fraud, settlement, and CRM — governed by registry compatibility and design review for enum extensions only. - Consumer-driven contract — consumers publish expected schema fixtures; CI verifies producer compatibility. ShardPay's fraud team owns contract test for
TransferCompleted; ledger PRs must pass fraud's fixture suite before merge. - Event notification vs event-carried state transfer — thin events (ID only) reduce schema coupling but increase temporal/API coupling via callback lookups. ShardPay uses event-carried state for settlement amounts to keep projectors autonomous during fraud API outages.
Versioning and compatibility rules
ShardPay topic naming: shardpay.transfer.completed.v1 — bump major topic version for breaking changes, run dual-publish migration window. Within v1 topic, Avro BACKWARD compatibility: new fields optional with defaults; never remove or retype fields.
Consumer teams pin reader schema version; producers register writer schema. Incompatible evolution blocks deploy — fix forward by adding field, not renaming.
Walkthrough: adding fraud score to event
Product wants riskScore on TransferCompleted. Producer adds optional int riskScore with default null. Registry validates BACKWARD. Old settlement consumer ignores unknown field; new fraud analytics consumer reads it. No coordinated flag day deploy — schema evolution without downtime.
// ShardPay schema-safe event evolution (Avro-generated Java 17)
@AvroGenerated
public class TransferCompleted extends SpecificRecordBase {
// v1 fields — never removed
private String transferId;
private String merchantAccountId;
private long amountCents;
private String status;
// v1.1 additive — optional with default null in Avro schema
private Integer riskScore;
public OptionalInt riskScore() {
return riskScore == null ? OptionalInt.empty() : OptionalInt.of(riskScore);
}
}
// Consumer ignores fields it does not need — forward compatible
public void onTransferCompleted(TransferCompleted event) {
settlementService.credit(event.getMerchantAccountId(), event.getAmountCents());
// does not depend on riskScore — safe across schema versions
}
Avoiding the distributed monolith
Async messaging without boundaries creates a different monolith — every team blocked on shared event blob changing weekly. ShardPay practices: bounded context per topic family; thin integration events at context boundaries; avoid "god event" with 40 fields for every consumer.
Prefer duplicate purpose-specific projections over one mega-schema serving fraud, CRM, and data lake with conflicting evolution needs.
Kafka trackSee schema compatibility in Kafka
Quick recall
Everything you need if you only revisit this box.
- Async removes temporal coupling, not schema coupling — govern schemas explicitly.
- BACKWARD-compatible additive evolution and consumer contract tests in CI.
- Avoid god events; bounded topics and event-carried state tradeoffs per use case.
Test yourself
Answer these before moving on — recall is what makes it stick.