Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 53

Event-Driven Microservices Workflow Design

Recommended resources

Welcome to Module 8, Lesson 4. In our last lesson, we compared the powerful messaging tools at our disposal—RabbitMQ, Kafka, and Redis—and learned to select the right one based on architectural needs. We now have the "what." This lesson focuses on the "how": how to use these tools to design a complete, asynchronous business process from start to finish.

Today, you will learn how to design an event-driven workflow for a classic microservices scenario: an e-commerce order confirmation. This is a fundamental skill that moves beyond simple request-response interactions and is central to building resilient, scalable distributed systems. By the end of this lesson, you will be able to map out a complex business process as a series of asynchronous events, a crucial step before writing any code.

1. From Synchronous Chains to Asynchronous Collaboration

In your experience with monolithic Django applications, processing an order is often a single, synchronous function call within a database transaction. This function might directly call other modules for inventory, payment, and notifications.




# A simplified monolithic approach
@transaction.atomic
def place_order(user, cart_items):



    # 1. Check inventory
    if not inventory.check_stock(cart_items):
        raise InsufficientStockError("Not enough items in stock.")
    



    # 2. Process payment
    payment_successful = payment_gateway.charge(user.credit_card, cart.total)
    if not payment_successful:
        raise PaymentFailedError("Payment was declined.")
        



    # 3. Create order record
    order = Order.objects.create(user=user, total=cart.total, status='CONFIRMED')
    



    # 4. Update inventory
    inventory.decrement_stock(cart_items)
    



    # 5. Send confirmation email
    notifications.send_order_confirmation(user, order)
    
    return order

This works well in a monolith because it's a single, atomic operation. A naive transition to microservices might replace these function calls with synchronous HTTP or gRPC calls:

  1. Order Service calls Inventory Service (and waits).
  2. Order Service calls Payment Service (and waits).
  3. ...and so on.

This creates a distributed monolith, a fragile chain of dependencies where a failure or slowdown in one service (like Payments) brings the entire process to a halt.

Event-driven architecture breaks this chain. Instead of the Order Service commanding other services, it simply announces a fact: an OrderPlaced event has occurred. Other services listen for this event and react independently. This is a shift from a tightly controlled sequence to a collaborative, asynchronous workflow.


2. Events, Messages, and the Language of Asynchrony

Before designing the workflow, it's critical to be precise with our terms. An event is different from a message or a command.

Patterns of Event Driven Architecture - Mark Richards

In this clip, software architect Mark Richards provides a clear, concise distinction between events and messages. Understanding this is fundamental to event-driven design.

Watch the segment from Events versus Messages. Pay close attention to the four key differences he outlines: Intent: Advertising a fact vs. commanding an action. Ownership: Sender owns the event topic vs. receiver owns the message queue. Contract: Sender owns the event schema vs. receiver owns the message schema. Communication Style: Broadcast (publish-subscribe) vs. point-to-point.

To summarize:

  • Event: A record of something that happened in the past (e.g., OrderPlaced). It's a broadcast notification.
  • Command: A request for an action to be performed in the future (e.g., ProcessPayment). It's directed to a specific handler.

An event-driven workflow is primarily composed of services reacting to events.


3. Choreography vs. Orchestration: Two Workflow Patterns

How do we coordinate the steps of a business process across multiple services? There are two primary patterns.

Pattern 1: Choreography

In this pattern, there is no central controller. Each service knows what events to listen for and what events to publish in response. They collectively achieve the business outcome without a single point of direction.

  • Analogy: A dance troupe. Each dancer knows their cues from the music and the movements of others, performing their part to contribute to the overall performance.
Event-Driven Order Management System Workflow
This diagram illustrates a choreographed workflow. The `Check-Out Service` publishes an `OrderCreated` event to the Event Broker. Other services like `Order Service` and `Fulfillment Service` subscribe to this event and perform their tasks, publishing their own events in turn, all without a central coordinator.
  • Pros: Highly decoupled, resilient, and flexible. You can add new services (e.g., a fraud detection service that also listens to OrderPlaced) without changing existing ones.
  • Cons: The overall business logic is distributed, making it harder to monitor and understand the state of a specific workflow instance (e.g., "Where is order #123 right now?").

Pattern 2: Orchestration (The Saga Pattern)

In this pattern, a central orchestrator service manages the workflow. It sends commands to other services and reacts to their reply events. This pattern is commonly used to implement the Saga pattern for managing distributed transactions.

  • Analogy: An orchestra conductor. The conductor explicitly tells each instrument section when to play and what to play.
The Saga Pattern for Distributed Transactions
This diagram shows an orchestrated Saga. The `Order Saga Orchestrator` is the central coordinator. It sends a `Process Payment Command` to the Payment Service and waits for a `Payment Completed Event`. It drives the entire flow, including handling failures by issuing compensating commands.
  • Pros: Centralized workflow logic makes it easier to understand, monitor, and modify. The state of the transaction is explicit.
  • Cons: The orchestrator can become a complex bottleneck. It introduces coupling between the orchestrator and the participating services.

For our design, we'll focus on the choreography pattern, as it represents a more purely event-driven mindset.


4. Designing the Order Confirmation Workflow

Let's design a choreographed workflow for an e-commerce order confirmation.

Business Process Steps:

  1. Customer submits an order.
  2. The system reserves inventory.
  3. The system processes payment.
  4. The customer is notified of the outcome.
  5. If successful, shipping is initiated.

Services Involved:

  • Order Service: Manages order data.
  • Inventory Service: Manages product stock.
  • Payment Service: Interacts with payment gateways.
  • Notification Service: Sends emails or other notifications.
  • Shipping Service: Manages the shipping process.

The Event Flow (Happy Path)

  1. Client sends a POST /orders request to the API Gateway, which routes it to the Order Service.
  2. The Order Service creates an order in its database with a PENDING status and publishes an OrderPlaced event.
  3. The Inventory Service consumes the OrderPlaced event. It attempts to reserve the required stock. If successful, it publishes an InventoryReserved event.
  4. The Payment Service consumes the InventoryReserved event. It processes the payment. If successful, it publishes a PaymentSucceeded event.
  5. The Order Service consumes the PaymentSucceeded event and updates the order's status to CONFIRMED.
  6. The Shipping Service consumes the PaymentSucceeded event and begins the fulfillment process, eventually publishing an OrderShipped event.
  7. The Notification Service consumes PaymentSucceeded and OrderShipped to send confirmation and shipping emails to the customer.

Designing the Events

A well-designed event is crucial. It must be self-contained and immutable.

Designing Events

This section from the "How to Design Event-Driven Architecture" article provides an excellent template for a well-structured event.

Focus on the example OrderPlacedEvent schema. Notice how it includes not just the payload but also essential metadata like eventId, timestamp, aggregateId, and correlationId for tracing.

An OrderPlaced event, for example, should contain all the data a consumer needs, so the consumer doesn't have to call back to the Order Service.

{
  "eventId": "uuid-v4-for-this-event",
  "eventType": "OrderPlaced",
  "timestamp": "2024-10-27T10:00:00Z",
  "version": "1.0",
  "payload": {
    "orderId": "ord-12345",
    "customerId": "cust-67890",
    "items": [
      { "productId": "prod-abc", "quantity": 1, "price": 99.99 },
      { "productId": "prod-def", "quantity": 2, "price": 19.99 }
    ],
    "totalAmount": 139.97
  },
  "metadata": {
    "correlationId": "uuid-v4-for-the-initial-request"
  }
}

Handling Failures: Compensating Actions

What if a step fails? For example, the PaymentService publishes a PaymentFailed event. This triggers compensating actions to undo previous steps.

  1. The Inventory Service consumes the PaymentFailed event and executes a compensating action: it un-reserves the stock for that order, making it available again. It might publish an InventoryReleased event.
  2. The Order Service consumes the PaymentFailed event and updates the order's status to CANCELLED.
  3. The Notification Service consumes PaymentFailed to inform the customer that their order could not be processed.

This sequence of actions and compensating actions forms a Saga.


5. A Practical Walkthrough with Code

Let's watch a practical implementation of these concepts. The following video builds a simplified e-commerce system using FastAPI and Redis Streams. It starts with a synchronous anti-pattern and refactors it into a resilient, event-driven workflow.

Microservices with FastAPI – Full Course

This "Microservices with FastAPI" course provides a concrete example of building an event-driven workflow.

First, watch the initial implementation using direct HTTP calls. This demonstrates the tightly-coupled, synchronous anti-pattern we discussed. Next, observe the refactoring to an event-driven model with Redis Streams. The Payment service publishes an order_completed event, and the Inventory service consumes it asynchronously. This is the core of the choreographed workflow. Finally, watch how a rare failure case is handled using a compensating event. When the inventory update fails, a refund_order event is published, triggering a compensating action in the Payment service. This is a practical demonstration of a Saga.


Conclusion

You have now learned the principles of designing an event-driven workflow. This is a significant step up from simple request-response communication and is essential for building robust microservice architectures.

Key Takeaways:

  • Shift from Commanding to Notifying: Instead of services telling each other what to do via direct calls, they publish events about what has happened.
  • Choreography vs. Orchestration: Choreography offers maximum decoupling but distributes logic, while orchestration centralizes workflow control but adds coupling.
  • Design for Failure: A robust workflow must include compensating actions (Sagas) to handle failures in a distributed environment and maintain data consistency.
  • Events are Immutable Facts: Design events to be self-contained data records, enabling consumers to act independently.

Preview of the Next Lesson

We've now covered the high-level design of an asynchronous workflow and even seen a simplified implementation with Redis Streams. In the real world, complex background jobs often require more advanced features like scheduled tasks, automatic retries, and dedicated worker pools.

In our next lesson, you will "Configure Celery with Redis as a message broker for background task processing." You'll learn how to set up this industry-standard combination to build a powerful and scalable task-processing system, which is a common component in event-driven architectures.