PrepZone Logo
PrepZone

Temporal and Spatial Coupling

When events decouple teams and when hidden coupling via schemas creates distributed monoliths.

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.
Loose (preferred)
ProducerPublishes domain event
Broker
Unknown consumers
Tight (risky)
ProducerCalls consumer API
ConsumerSync dependency
Loose coupling via events; tight coupling when consumers depend on producer internals.

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 TransferCompleted consumers 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.status enum 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.

Java
// 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.

Quick recall

Everything you need if you only revisit this box.

  1. Async removes temporal coupling, not schema coupling — govern schemas explicitly.
  2. BACKWARD-compatible additive evolution and consumer contract tests in CI.
  3. 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.