Engineering Playbook
System Patterns

Event-Driven Architecture

Broker vs Log, Choreography vs Orchestration.

Event-Driven Architecture (EDA)

In standard REST, System A tells System B to do something ("Command"). In EDA, System A shouts "Something happened!" ("Event") and doesn't care who is listening. This creates loose coupling.

The Topologies: Broker vs. Log

Not all message queues are the same. Know the difference.

FeatureMessage Broker (RabbitMQ, SQS)Event Log (Kafka, Kinesis)
Concept"Smart Broker, Dumb Consumer""Dumb Broker, Smart Consumer"
StateEphemeral. Deleted once consumed.Persistent. Retained for days/years.
ScaleGood.Massive.
PatternTask distribution, Job queues.Data streaming, Event Sourcing.

Choreography vs. Orchestration

Who manages the business process?

1. Choreography (The Dance)

Services react to events. No central coordinator.

  • Flow: Order Service emits OrderPlaced -> Inventory Service listens and emits StockReserved -> Payment Service listens and emits PaymentProcessed.
  • Pros: Highly decoupled.
  • Cons: Hard to visualize. "Who actually owns the Order process?"

Choreography Implementation Example

Order Service - emits events:

  • Creates order and saves to database
  • Publishes OrderPlaced event with order details
  • Doesn't know or care who processes the event
  • Simple, focused responsibility

Inventory Service - reacts to events:

  • Listens for OrderPlaced events
  • Reserves stock for ordered items
  • Publishes StockReserved or StockReservationFailed event
  • Operates independently, no direct coupling

Key Benefits:

  • Services can be developed and deployed independently
  • New services can be added without changing existing ones
  • Natural resilience - if one service fails, others continue operating

2. Orchestration (The Conductor)

A central service commands others.

  • Flow: Order Service tells Inventory "Reserve Stock". Waits for reply. Tells Payment "Charge Card".
  • Pros: Clear flow, easy error handling.
  • Cons: Tighter coupling, single point of failure logic.

Orchestration Implementation Example

Central orchestrator controls the flow:

  • Coordinates sequential calls to each service
  • Handles business logic and error conditions
  • Manages compensation transactions when steps fail
  • Provides single point of visibility for the entire process

Typical Flow:

  1. Create order in Order Service
  2. Reserve inventory in Inventory Service
  3. Process payment in Payment Service
  4. If any step fails, execute compensations in reverse
  5. Return final result to client

Key Benefits:

  • Clear, visible business process
  • Centralized error handling and logging
  • Complex branching logic managed in one place
  • Easier to implement transactional behavior

Event Payloads

What goes inside the JSON?

Notification (Thin)

Contains only IDs. `{'orderId': 123}`. The consumer must call the API to get details. Ensures data freshness but hammers the API.

Carried State (Fat)

Contains changed data. `{'orderId': 123, 'status': 'PAID'}`. Consumer needs no API call. Fast, but data might be stale.

Payload Examples

Thin Payload (Notification Style)

Structure: Minimal data - just identifiers and metadata

  • Fields: orderId, customerId, timestamp
  • Consumer must make additional API calls to get details
  • Ensures data freshness but increases API load
  • Better when data changes frequently

Consumer Implementation:

  • Listen for OrderPlaced events
  • Extract orderId from event
  • Call Order Service API to get full order details
  • Process order items and other data
  • Handle API failures gracefully

Fat Payload (Carried State Style)

Structure: Includes all relevant data at time of event

  • Fields: orderId, customerId, timestamp, plus:
    • Full items array with product details
    • totalAmount for convenience
    • shippingAddress for processing
  • Consumer can process immediately without additional calls
  • Faster processing but data might become stale

Consumer Implementation:

  • Listen for OrderPlaced events
  • Extract all needed data from event payload
  • Process items directly without API calls
  • Handle potential data staleness issues
  • Validate data integrity before processing

Real-World Message Patterns

1. Request-Reply with Correlation ID

Pattern Overview:

  • Request message includes unique requestId
  • Response message includes same requestId for matching
  • Requester maintains map of pending requests
  • Timeout handling prevents hanging requests

Request Message Structure:

  • Unique correlation identifier
  • Request payload (items to check, query parameters)
  • Timestamp for request tracking
  • Optional priority level

Response Message Structure:

  • Matching correlation identifier
  • Response data (availability status, results)
  • Processing time and status codes
  • Error details if applicable

Requester Implementation Logic:

  1. Generate unique request ID
  2. Store request promise with resolver in pending map
  3. Publish request message to event bus
  4. Set timeout for request expiration
  5. Wait for response or timeout

Response Handler Logic:

  1. Extract correlation ID from response
  2. Find pending request in map
  3. Resolve or reject promise with response data
  4. Clean up pending request entry
  5. Handle unmatched responses

Use Cases:

  • Inventory availability checks
  • Price calculations
  • Fraud scoring
  • Any synchronous-appearing operation in async system

Idempotency is Mandatory

In EDA, you usually get At-Least-Once delivery. Your consumers will see the same message twice. You must handle this (e.g., checking a processed_message_ids table) or data will be corrupted.

Idempotency Implementation Example

Key Components:

  • Unique message ID for each event
  • Processed message tracking table/cache
  • Check-before-process logic
  • Mark-as-successful only after completion
  • Retry mechanism for failed messages

Implementation Strategy:

  1. Extract unique message ID from event
  2. Check if message already processed
  3. If processed, skip and acknowledge
  4. If not processed, execute business logic
  5. On success, mark message as processed
  6. On failure, don't mark (allows retry)

Storage Options:

  • Database table for persistence
  • Redis cache for speed
  • DynamoDB for serverless applications
  • Message broker's built-in deduplication features

Error Handling:

  • Don't mark failed events as processed
  • Implement retry logic with exponential backoff
  • Move repeated failures to dead-letter queue
  • Monitor and alert on high failure rates

Performance Considerations:

  • Use efficient lookups (indexed queries)
  • Implement TTL for old processed message records
  • Batch insert processed message IDs
  • Consider memory vs. persistence trade-offs