Asynchronous events
How event-driven asynchronous flows work: publishing, consuming, and processing across microservices through the Event Bus.
What is an event-driven asynchronous flow
In an asynchronous flow, a service publishes an event signaling that something happened, and other services react to that event without the publisher needing to wait for a response. There is no direct connection between the producer of the event and its consumer.
This model is fundamental in distributed architectures because it enables temporal decoupling: services don’t need to be available at the same time in order to communicate.
Difference from synchronous communication
| Aspect | Synchronous | Asynchronous (events) |
|---|---|---|
| Coupling | The sender knows the receiver | The sender doesn’t know who consumes |
| Availability | Both must be active | The consumer can process later |
| Response | Immediate | No direct response |
| Scalability | Limited by the slowest receiver | Each consumer scales independently |
| Complexity | Lower | Higher (ordering, duplicates, consistency) |
Anatomy of an event
A well-defined event contains:
- Type: name identifying what happened (
OrderPlaced,PaymentCompleted) - Payload: data relevant to the fact (order ID, amount, timestamp)
- Metadata: traceability information (correlationId, timestamp, source)
- Version: to handle schema evolution
{
"type": "OrderPlaced",
"version": "1.0",
"timestamp": "2024-01-15T10:30:00Z",
"correlationId": "abc-123-def",
"source": "ms-orders",
"payload": {
"orderId": "order-456",
"customerId": "cust-789",
"totalAmount": 150.00,
"items": [
{ "productId": "prod-001", "quantity": 2 }
]
}
}
The complete flow of an event
sequenceDiagram
participant MS1 as Microservicio Origen
participant EB as Event Bus
participant MS2 as Consumidor A
participant MS3 as Consumidor B
participant MS4 as Consumidor C
MS1->>EB: Publicar evento (OrderPlaced)
Note over MS1: El publicador continúa su trabajo
EB->>MS2: Entregar evento
EB->>MS3: Entregar evento
EB->>MS4: Entregar evento
MS2->>MS2: Actualizar inventario
MS3->>MS3: Enviar notificación
MS4->>MS4: Registrar analytics
Step 1: Publishing
The origin microservice completes its main operation (for example, creating an order) and publishes an event to the Event Bus. Once published, the service does not wait for anyone to process it.
Step 2: Distribution
The Event Bus receives the event and distributes it to all subscribed consumers. Depending on the technology (Kafka, RabbitMQ, SNS/SQS), distribution can be:
- Fan-out: every subscriber receives a copy
- Competing consumers: only one consumer in the group processes each message
- Combination: fan-out across groups, competing consumers within each group
Step 3: Consumption
Each consumer receives the event and runs its logic independently. A slow consumer doesn’t affect the others.
Step 4: Acknowledgment
The consumer acknowledges that it processed the event successfully. If it fails, the Event Bus can retry delivery.
Publishing patterns
Publish after persisting
The service first saves to its database and then publishes the event. It’s simple but carries a risk: if publishing fails after persisting, the event is lost.
Outbox Pattern
The service saves the event to an outbox table within the same transaction as the business operation. A separate process reads the outbox table and publishes the events to the bus. This guarantees consistency between the operation and the publication.
Event Sourcing
Instead of saving the current state, all the events that produced that state are stored. Publishing is inherent to the model.
Challenges of asynchronous flows
Event ordering
Events can arrive in a different order from the one in which they were published. Consumers must be able to handle this, either by reordering or by being tolerant of out-of-order delivery.
Duplicate events
The Event Bus can deliver the same event more than once (at-least-once delivery). Consumers must be idempotent: processing the same event twice must produce the same result as processing it once.
Eventual consistency
Data across services is not synchronized instantly. After an event is published, some time may pass before all consumers reflect the change. The system must be designed to tolerate this window of inconsistency.
Observability
Tracing an asynchronous flow is harder than a synchronous one. The correlationId is essential for following the chain of events across multiple services.
When to use asynchronous events
They are appropriate when:
- Multiple services need to react to the same fact
- No immediate response is needed
- You want to decouple services to scale independently
- The process can tolerate eventual consistency
They are not appropriate when:
- An immediate response is needed for the user
- The operation requires strong consistency across services
- The added complexity is not justified by the volume or requirements
Summary
Event-driven asynchronous flows let services communicate without direct coupling. The publisher doesn’t know the consumers, and each consumer processes at its own pace. This brings scalability and resilience, but it introduces challenges of ordering, duplicates, and consistency that must be addressed with patterns such as idempotency, outbox, and distributed tracing.