Event Simulator

Design and trace event flows between microservices β€” from publication to consumption and chain reactions.

Objective

In this lab you will design event flows between microservices, trace their complete journey, and anticipate the problems that can arise in an event-driven architecture.

Events are the nervous system of a distributed architecture. Understanding how they flow is essential to designing decoupled and resilient systems.

Prerequisites

Before you start, make sure you understand the difference between:

ConceptDescriptionExample
CommandA request to do somethingCreateOrder
EventA notification that something already happenedOrderCreated
QueryA request for informationGetOrderStatus

Events are immutable facts β€” something that already happened and cannot be undone. Commands are intentions β€” they can be rejected.


Exercise 1: Design an event flow

Scenario

A user buys a product in your e-commerce store. Design the complete event flow from the moment they click β€œBuy” until they receive the confirmation.

Services involved

  • Orders Service: creates and manages orders
  • Inventory Service: reserves and deducts stock
  • Payments Service: processes the charge
  • Notifications Service: sends emails and push notifications
  • Analytics Service: records business metrics

Your task

Complete the event diagram:

User clicks "Buy"
    β”‚
    β–Ό
[Orders Service]
    β”‚ publishes: OrderCreated { orderId, userId, items, total }
    β”‚
    β”œβ”€β”€β–Ί [Inventory Service]
    β”‚       consumes: OrderCreated
    β”‚       action: reserve stock
    β”‚       publishes: _______________
    β”‚
    β”œβ”€β”€β–Ί [Payments Service]
    β”‚       consumes: _______________
    β”‚       action: process charge
    β”‚       publishes: _______________
    β”‚
    β”œβ”€β”€β–Ί [Notifications Service]
    β”‚       consumes: _______________
    β”‚       action: _______________
    β”‚
    └──► [Analytics Service]
            consumes: _______________
            action: _______________

Solution

View full flow
User clicks "Buy"
    β”‚
    β–Ό
[Orders Service]
    β”‚ publishes: OrderCreated { orderId, userId, items, total }
    β”‚
    β”œβ”€β”€β–Ί [Inventory Service]
    β”‚       consumes: OrderCreated
    β”‚       action: reserve stock for the items
    β”‚       publishes: StockReserved { orderId, items }
    β”‚              or StockInsufficient { orderId, items }
    β”‚
    β”œβ”€β”€β–Ί [Payments Service]
    β”‚       consumes: StockReserved
    β”‚       action: process charge with the provider
    β”‚       publishes: PaymentCompleted { orderId, transactionId }
    β”‚              or PaymentFailed { orderId, reason }
    β”‚
    β”œβ”€β”€β–Ί [Notifications Service]
    β”‚       consumes: PaymentCompleted
    β”‚       action: send confirmation email to the user
    β”‚       consumes: PaymentFailed
    β”‚       action: send failure email to the user
    β”‚
    └──► [Analytics Service]
            consumes: OrderCreated, StockReserved, PaymentCompleted
            action: record conversion and revenue metrics

Exercise 2: Handling the unhappy path

Scenario

What happens when something fails in the middle of the flow? Design the compensations.

Case 1: Stock was reserved but payment failed

OrderCreated β†’ StockReserved β†’ PaymentFailed
                                    β”‚
                                    β–Ό
                              What happens now?

Your task: Which event should the payments service publish? Who consumes it? What action does it take?

View solution
PaymentFailed { orderId, reason }
    β”‚
    β”œβ”€β”€β–Ί [Inventory Service]
    β”‚       consumes: PaymentFailed
    β”‚       action: release reserved stock
    β”‚       publishes: StockReleased { orderId, items }
    β”‚
    β”œβ”€β”€β–Ί [Orders Service]
    β”‚       consumes: PaymentFailed
    β”‚       action: mark order as failed
    β”‚       publishes: OrderCancelled { orderId, reason }
    β”‚
    └──► [Notifications Service]
            consumes: OrderCancelled
            action: notify the user that the payment failed

This is the Saga with choreography pattern: each service reacts to events and runs compensations when something fails. There is no central orchestrator.

Case 2: The inventory service is down

OrderCreated β†’ [Inventory: DOWN] β†’ ???

Your task: What happens to the OrderCreated event? Is it lost? Is it retried?

View solution

If you use a message broker with durable queues (RabbitMQ, Kafka):

  1. The OrderCreated event stays in the queue waiting
  2. When the inventory service comes back up, it consumes the pending events
  3. Processing continues normally (with delay)

Key point: The queue acts as a temporary buffer. The event is not lost as long as the queue is durable and the messages are persistent.

Consideration: If the service is down for a long time, the queue grows. You need:

  • Alerts based on queue size
  • TTL on messages to avoid processing very old events
  • Compensation logic if the event is no longer relevant

Exercise 3: Event ordering

The problem

Two events arrive at the inventory service:

  1. StockUpdated { productId: "ABC", quantity: 100 } (timestamp: 10:00:01)
  2. StockUpdated { productId: "ABC", quantity: 50 } (timestamp: 10:00:00)

Event 2 was published first but arrived later. If you process in arrival order, the stock ends up at 50 (incorrect β€” it should be 100).

Your task

How do you guarantee the correct order? Evaluate these strategies:

StrategyProsCons
Sort by timestampSimpleClocks not synchronized across services
Sequence numberGuaranteed orderRequires coordination between publishers
Partition by keyOrder within the partitionLimits parallelism
Optimistic versioningNo global order requiredRequires conflict resolution logic
View recommendation

The most practical strategy for most cases is partition by key:

  • In Kafka: use productId as the partition key
  • All events for the same product go to the same partition
  • Within a partition, order is guaranteed
  • Different products are processed in parallel

For cases where you need global order, use a sequence number generated by the publisher and reject events with a sequence lower than the last one processed.


Exercise 4: Design your own flow

Your task

Pick one of these scenarios and design the complete event flow:

Option A: Hotel booking system

  • Services: Bookings, Rooms, Payments, Notifications
  • Flow: user books β†’ check availability β†’ charge β†’ confirm

Option B: Delivery platform

  • Services: Orders, Restaurants, Riders, Tracking, Notifications
  • Flow: user orders β†’ restaurant accepts β†’ rider assigned β†’ delivery

Option C: Subscription system

  • Services: Users, Subscriptions, Payments, Access, Notifications
  • Flow: user subscribes β†’ recurring charge β†’ renewal/cancellation

For each one, define:

  1. The main events (happy path)
  2. The compensation events (unhappy path)
  3. Which service publishes and who consumes each event
  4. How you handle failures at each step

Reflection

  1. How many events did you need for a β€œsimple” flow?
  2. How difficult was it to design the compensations?
  3. Would you prefer a central orchestrator (orchestrated Saga) or choreography?
  4. How would you debug a problem in a flow of 5+ events?

Event-driven systems are powerful but complex. The key is to design the flows before writing code β€” and always think about what happens when something fails.