DEV Community

Cover image for Microservices with Bounded Contexts, Event-Driven Operations and Sagas: The Art of Orchestrating a Distributed System
Francisco Silvério
Francisco Silvério

Posted on • Edited on

Microservices with Bounded Contexts, Event-Driven Operations and Sagas: The Art of Orchestrating a Distributed System

In a monolithic architecture, a transaction is a single, atomic database operation. It either happens in full, or it doesn't happen at all. It’s safe, but it can become a bottleneck when millions of users are trying to process transactions simultaneously. As we scale into distributed systems, we trade that localized safety for a "relay race" across multiple services.

To prevent this relay race from ending in a catastrophe, we must move beyond the basic "microservices" buzzword and master the orchestration between services.


Architecture Diagram

1. The System: International Wire Transfer

This architecture draft draws a microscope to a banking engine, specifically, the part responsible for wire transfers. Its mission is to move funds across borders while satisfying three distinct business requirements:

  • Integrity: Ensuring the math adds up and funds are accounted for.
  • Security: Screening every transaction against global watchlists.
  • Market Accuracy: Locking in volatile currency exchange rates in real-time.

Instead of a single "Banking Service," we have three autonomous "islands" of logic: Ledger (Core), Fraud (Compliance), and FX (Currency).


2. Establishing the concepts

I. Bounded Context: The "Territory" of Data

The term "Bounded Context" was originated by Eric Evans, who defines it as:

A description of a boundary (typically a subsystem, or the work of a particular team) within which a particular model is defined and applicable.

Looking at the diagram, you’ll notice that the Entity: Account exists in all three services, but the Account Model is different in each one. This is the Bounded Context in action.

  • In the Ledger Context: An account is a Balance Container. It only cares about Available Balance and Currency.
  • In the Fraud Context: An account is a Legal Risk Profile. It cares about the Owner Legal Name and Watchlist Flags.
  • In the FX Context: An account is a Regional Wallet. It cares about the Home ISO and the Local Market Rate.

By isolating these models, we ensure that a change in the Compliance department's risk-rating logic never accidentally breaks the code responsible for calculating account balances. Each service owns its own "version of the truth."

II. Event-Driven Operations: The "Nervous System"

In the provided architecture, notice that the services never call each other's APIs directly. They communicate via a Message Bus.

When a request arrives, the Ledger doesn't "tell" Fraud to start a check; it simply announces a fact: TransferRequested.

  • Decoupling: The Ledger doesn't know (or care) if the Fraud service is currently online. It drops the message on the Bus and continues its work.
  • Autonomy: The Fraud and FX services are "Subscribers." They react to the message when they have the resources to do so. This prevents a slow-down in one service from cascading into a total system failure.

3. The Saga: Orchestrating the Distributed "Undo"

Now that we have established what the system does and why it is organized this way, one question might pop up: If these services are independent islands, who ensures the process actually finishes?

This is where the Saga Pattern enters.

In a distributed system, there is no "Global Rollback." If the money is locked in the Ledger (Step 2) and the Fraud service emits a SecurityCleared message, but the FX system finds a problem, there is no way to automatically revert all the other database records. We must perform a Compensating Transaction.

Saga Pattern Flow

How the Saga Orchestrates the System:

  1. The Happy Path:

    • Ledger locks the funds emits TransferRequested.
    • Fraud clears the user emits SecurityCleared.
    • FX locks the conversion rate emits RateLocked.
    • Result: The transfer is finalized.
  2. The Failure Path (The "Undo" Button):

    • Ledger locks $10,000 and emits TransferRequested.
    • Fraud Service checks pass and it emits a SecurityCleared message.
    • FX Service fails to process the transaction and emits TransactionNotAllowed.
    • The Compensation: The Ledger and Fraud services are "listening" for failure events. Upon hearing TransactionNotAllowed, they trigger their own internal "Undo" logic to unlock the $10,000 and notify the user.

Choreography vs. Orchestration

Our system uses Choreography. There is no central "Boss" or "Orchestrator" service. The Ledger works as an entry point for the operation, but it is not responsible for managing the state of the entire flow.

If we required more granular control over the operation, we could implement Orchestration, where a dedicated service directs the transaction and has the final word to approve or deny it. For many decentralized systems, however, the Choreography shown here offers the best balance of scale and simplicity.


Conclusion: Resilience by Design

By combining Bounded Contexts (to isolate logic), Event-Driven Operations (to decouple communication), and Sagas (to handle failures), we build a system that is far more resilient and performant than any monolith. We accept "Eventual Consistency" in exchange for a system that can scale globally and fail gracefully.

Top comments (0)