Messaging Patterns
Messaging decouples services in time and deployment. A queue is better suited to handing a job to one consumer; an event stream stores events so multiple consumers can process them at their own pace.
Quick Decision
| Need | Pattern | Watch out |
|---|---|---|
| Hand work to workers | Queue + competing consumers | Acknowledgment and duplicates |
| Notify many consumers | Pub/Sub or event stream | Schema ownership and replay |
| Wait for a message result | Request-reply | Timeout and correlation ID |
| Long-lived data flow | Partitioned event stream | Ordering key and consumer lag |
Production Checklist
- Problem: Does messaging solve coupling, burst absorption, or replay?
- Solution: Are delivery, acknowledgment, ordering, retry, DLQ, schema, and ownership clear?
- Trade-off: Async systems decouple services but add eventual consistency, duplicates, and operational cost.
- Failure mode: Handle poison messages, consumer lag, broker outages, out-of-order events, and retry storms.
- Measurement: Track queue depth, oldest message age, lag, processing latency, retries, and DLQ rate.
- Security/cost: Manage topic/queue access, encryption, retention, payload PII, and broker storage cost.
Queue and Event Stream
| Model | Typical technology | Semantics |
|---|---|---|
| Broker queue | RabbitMQ, AWS SQS, Azure Service Bus, Google Pub/Sub | A message usually leaves after successful acknowledgment |
| Event stream | Kafka, AWS Kinesis, Azure Event Hubs, NATS JetStream | Replay is possible through retention and offsets |
Choose using ordering, retention, consumer count, managed operations, replay, cross-region needs, and cost—not throughput alone.
Message Patterns
Request-reply
The request carries a correlationId and reply destination. Bound the wait with a deadline; a late reply must not accidentally apply a business write twice.
Publish-subscribe
The producer publishes to a topic and each consumer reads through its subscription. Topic-based routing is simple and observable; content-based filtering can reduce consumers but adds broker filtering cost and schema coupling. Fan-out needs consumer isolation and a retention plan.
Competing consumers
Workers sharing one queue divide the work. If ordering matters, route one aggregate's messages to the same partition/consumer. Worker concurrency must stay below downstream connection-pool and database capacity.
Event Routing and Event-Driven Services
Route with a topic exchange, partition key, subject, or content filter. An unstable routing key creates hot partitions or ordering violations. The producer should publish a domain fact; each consumer owns its projection or side effect.
Choreography can be simple when one event triggers several services. For critical workflows, orchestration makes observability and compensation more explicit. Catalog the producer, consumers, schema owner, and deprecation policy for every event.
Schema Evolution
Preserve backward/forward compatibility with additive fields, unknown-field tolerance, and defaults. Field deletion, type changes, or semantic changes require a new event version/type. Consumers should safely quarantine unknown versions.
If a schema registry or central catalog is used, enforce compatibility modes in CI. Keep event payloads small; avoid copying sensitive data into many topics when a reference or limited projection is enough.
Retry, DLQ, and Exactly-Once
Use bounded retries with exponential backoff and jitter for transient failures. Send poison messages to a DLQ with failure reason, attempt count, first-seen time, and safe replay information.
Exactly-once usually applies only inside the broker transaction and consumer-offset boundary. External side effects such as databases, email, or payments still need idempotency keys, unique constraints, inbox/outbox, or deduplication. Never say “exactly-once” without stating its boundary.
Operations
Test graceful shutdown, in-flight acknowledgment, and replay during consumer deployment. If queue age breaches an SLO, inspect downstream saturation, batch size, partition balance, and backpressure rather than only adding workers.
See Synchronous vs Asynchronous Communication, Async Processing, and Stream Processing for implementation details.
