Event-Driven & Asynchronous Communication
Instead of phoning someone and waiting on hold, you send a text and they reply when they can — that’s the shift from synchronous calls to event-driven communication.
At a glance
Section titled “At a glance”- What it is: designing communication around events/messages (pub/sub, queues) rather than direct synchronous request/response calls.
- Why it matters: reduces tight coupling and stops one slow or failing service from freezing everything that calls it; absorbs traffic spikes better.
- For developers: before wiring up a new integration, ask whether it truly needs an instant answer, or whether an event will do.
- When to use it: when a caller doesn’t need an immediate response, when decoupling producer and consumer lifecycles matters, or when absorbing bursty load.
- When not to use it: when the caller genuinely cannot proceed without an immediate answer (e.g. checking real-time payment authorization) — that calls for a synchronous call, not an event.
- Prerequisites: Service & Component Boundaries.
Mental model
Section titled “Mental model”sequenceDiagram
participant Warehouse
participant Bus as Event bus
participant Storefront
Warehouse->>Bus: publish InventoryAdjusted
Bus-->>Storefront: deliver InventoryAdjusted
Note over Warehouse,Storefront: Warehouse doesn't wait for Storefront to process it
Step-by-step walkthrough
Section titled “Step-by-step walkthrough”- Identify whether the caller needs an immediate response or can proceed without waiting.
- If not, define the event: its name (past tense, e.g.
InventoryAdjusted), payload, and the delivery guarantee (at-least-once, exactly-once, ordering). - Choose a broker/queue appropriate to the guarantee needed.
- Design consumers to be idempotent — the same event may be delivered more than once.
- Add monitoring for the event stream (lag, dead-letter queue) so a stuck consumer is visible, not silent.
Worked example
Section titled “Worked example”The storefront currently polls the warehouse system every 5 seconds for stock levels.
- Input: constant polling adds load to the warehouse system and up to 5 seconds of staleness on the storefront.
- Process: the warehouse instead publishes an
InventoryAdjustedevent (SKU, new quantity, timestamp) whenever stock changes; the storefront subscribes and updates its cache on receipt. - Output: stock updates propagate within milliseconds of the change instead of on a polling delay; warehouse load drops since it’s no longer answering constant poll requests; the storefront keeps working (with slightly stale data) even if the warehouse is briefly unavailable.
public sealed record InventoryAdjusted(string EventId, string Sku, int NewQuantity, DateTimeOffset OccurredAt);
// Storefront consumer: idempotent on EventId, safe against at-least-once redeliveryasync Task HandleAsync(InventoryAdjusted evt){ if (await _processedEvents.ContainsAsync(evt.EventId)) return; // already handled this delivery
await _stockCache.UpdateAsync(evt.Sku, evt.NewQuantity); await _processedEvents.MarkProcessedAsync(evt.EventId);}On Azure, the warehouse publishes to an Azure Event Grid topic (or an Azure Service Bus topic if ordered, at-least-once delivery per SKU partition is needed); the storefront’s subscription includes a dead-letter destination so a repeatedly failing event is visible instead of silently dropped.
The storefront’s consumer can run as an Azure Function with a Service Bus trigger, so instance count scales automatically with queue length instead of needing its own autoscale rule:
[Function("StorefrontStockUpdater")]public async Task Run( [ServiceBusTrigger("inventory-adjusted", Connection = "ServiceBusConnection")] InventoryAdjusted evt){ await HandleAsync(evt);}The Function App only needs a small Azure Storage Account for its own runtime/trigger state (AzureWebJobsStorage) — no separately hosted, always-on service to manage for this consumer.
Common use cases
Section titled “Common use cases”| Use case | When to use | Notes |
|---|---|---|
| Notifying other systems of a state change | When consumers don’t need to block the producer | Use past-tense event names describing what happened |
| Replacing polling | When polling adds load or staleness | Publish on change instead of being asked repeatedly |
| Absorbing bursty load | When traffic spikes exceed steady processing capacity | A queue lets consumers drain the backlog at their own pace |
Advanced details
Section titled “Advanced details”- Eventual consistency trade-off: consumers see the change slightly after it happens, not instantly — this must be acceptable for the use case.
- Idempotency: most brokers guarantee at-least-once delivery, so consumers must handle duplicate events safely (e.g. by keying on an event id).
- Ordering guarantees vary by broker/partition strategy — don’t assume global ordering unless the technology explicitly provides it for your use case.
Quick reference
Section titled “Quick reference”| Term | Meaning |
|---|---|
| Event | A record that something happened, published without expecting an immediate reply |
| Pub/sub | A pattern where producers publish and any number of subscribers receive independently |
| At-least-once delivery | A guarantee that a message will be delivered, possibly more than once |
| Idempotent consumer | A consumer that produces the same result even if it processes the same event twice |
| Dead-letter queue | Where messages go after repeated processing failures, for inspection instead of silent loss |
Troubleshooting
Section titled “Troubleshooting”| Symptom | Likely cause | Fix |
|---|---|---|
| Duplicate side effects (e.g. double-adjusted inventory) | Consumer isn’t idempotent | Key processing on a unique event id and skip already-processed ids |
| Consumers falling behind silently | No monitoring on queue lag | Add lag/dead-letter monitoring and alerting |
| Events processed out of order causing incorrect state | No ordering guarantee where one was assumed | Use a partition key or sequence field the consumer can check |
Check your understanding
Section titled “Check your understanding”- Why is polling worse than events for both load and staleness?
- Name one place a synchronous call is still the right choice, and explain why an event wouldn’t work there.
Related pages
Section titled “Related pages”Part of Introduction to Software & Application Architecture. See also Service & Component Boundaries, API Design & Contracts, Resilience & Observability, and the Glossary.