Last Updated: May 1, 2025
Core Event Patterns
| Item | Description |
|---|---|
Event Notification | Lightweight: 'something happened, here's the ID.' Consumer calls back for details. Decoupled but requires API availability. |
Event-Carried State Transfer | Heavyweight: event contains all data consumer needs. Consumer self-sufficient, no callback needed. Trade-off: larger payloads, potential staleness. |
Event Sourcing | Persist state as a sequence of immutable events (not current state). Current state = fold over all events. Enables time-travel debugging, audit logs, and CQRS. |
CQRS (Command Query Responsibility Segregation) | Separate read model from write model. Commands mutate state → produce events → read models updated asynchronously. Optimize reads and writes independently. |
Saga Pattern | Distributed transaction via orchestration or choreography of events. Each step emits event; compensating transactions undo steps on failure. See: distributed-transactions sheet. |
Message Broker Comparison
| Broker | Delivery Guarantee | Ordering | Throughput | Best For |
|---|---|---|---|---|
| Apache Kafka | At-least-once / Exactly-once | Per-partition strict | Extreme (1M+ msg/s) | Stream processing, log-based, replayable |
| RabbitMQ | At-least-once (ack) | Per-queue FIFO | High (50K msg/s) | Task queues, routing, complex topologies |
| AWS SQS | At-least-once | Best-effort (FIFO available) | Elastic (auto-scale) | Serverless, simple queueing, AWS ecosystem |
| Google Pub/Sub | At-least-once | Per-key ordering | Global scale | GCP-native, push subscriptions, auto-scaling |
| NATS / JetStream | At-least-once | Per-subject | Very high (low latency) | Edge/IoT, low-latency, lightweight |
| Azure Event Hubs | At-least-once | Per-partition | Very high | Azure ecosystem, big data pipelines |
Event Design Principles
| Item | Description |
|---|---|
Use past-tense naming | OrderPlaced, PaymentCaptured, ShipmentDelivered. Events are immutable facts. This prevents confusion with commands (PlaceOrder, CapturePayment). |
Schema evolution | Add fields, never remove or rename. Use Avro/Protobuf with a schema registry. Semantic versioning: breaking changes = new event type (OrderPlacedV2). |
Event identity | Every event needs a globally unique ID and a timestamp. Enables deduplication and ordering. Event ID ≠ entity ID (many events per entity). |
Partitioning key | Choose partition key that groups related events. OrderID ensures all events for one order process in order. Avoid hot partitions (key skew). |
Idempotent consumers | Every consumer must assume duplicate events. Use event ID + consumer name as dedup key in a processed_events table. |
Failure Handling
Dead Letter Queue (DLQ)Poison messages that can't be processed → routed to DLQ. Alert on DLQ depth > 0. Manually inspect, fix, and replay.
Retry with backoffExponential backoff: 1s, 2s, 4s, 8s, max 5min. Add jitter (±25%) to prevent thundering herd. Max retries: 3-5 before DLQ.
Circuit breaker on consumersIf downstream is down, stop consuming. Kafka: pause() the consumer. Prevents backpressure from killing the consumer group.
Outbox patternWrite event to outbox table in same DB transaction as state change. Separate process polls outbox and publishes to broker. Guarantees at-least-once publish.
Exactly-once with Kafkaenable.idempotence=true on producer. read_committed isolation on consumer. Store offsets + results in one transaction via Kafka Streams / kafka-transactions.
Pro Tip: Events describe facts that happened (past tense): OrderPlaced, not PlaceOrder. This semantic distinction prevents coupling — producers declare what happened, consumers decide what to do about it.