Domain-Driven Microservices Design from a practitioner’s view (Part 1)

Introduction

Technology shapers such as Martin Fowler, Eric Evans, and Vince Vaughn have long advocated Domain Driven Design (DDD), and with good reason. Simply defined, DDD is a design technique that helps design complex systems, or re-architecture monolithic applications. It helps break down complex systems into micro-services. In addition, the technique places an emphasis on modeling core domain and domain logic.

This design technique introduces several concepts and techniques for strategic and tactical design, including:

  • Bounded Context
  • Domains and sub-domains
  • Aggregates (domain objects)

More importantly, DDD helps define a Ubiquitous Language that is spoken by the teams that create the software model, within a Bounded Context.

DDD is a vast topic, with significant amount of literature backing its effectiveness. That said, it can be daunting to sift through all that information, when trying to design micro-services using the DDD model. I would like to explore a few key topics from a practitioner’s viewpoint. For ease of understanding, I have broken it down into focused blogs:

Part 1: Communication between micro-services, Domain Events and Event Driven Architecture (EDA)

Part 2: Using Workflow Engine to model configurable business process within a micro-service. And using Orchestration vs Choreography techniques

Part 3: DDD Aggregates and how to avoid anemic Domain Models

Part 4: Wrap up, and exploring how DDD techniques can be adapted to Functional Programming style


Part 1: Communication between Micro-services, Domain Events and Event Driven Architecture (EDA)

Several discussions have debated how these micro-services communicate with each other while continuing to maintain loose coupling. A well-designed system clearly addresses the what, when and how to communicate between micro-services.

Here’s an example of a “Billing system”.

The Bounded Contexts within this system – each of which map to a micro-service in turn –  could read as follows:

When a new bill is received, the Billing Context processes and codes it. It then makes a call to the Approvals Context, to request bill review and approval. Once the bill is approved, the Approvals Context makes a call to the Payment Context, to issue the payment to the vendor.

This way of communication between micro-services results in a tightly-coupled system, with point-to-point communication.

To further illustrate, imagine a need to implement a business process change to automatically initiate payment for any bills less than USD 1000. To do this, you can:

  • change the Approvals Context to ignore bills less than USD 1000, and make a call to Payment Context automatically, or
  • change the Billing Context to call Payment Context directly for payment

Both are sub-optimal approaches for communication between micro-services. For subsequent complex business scenarios you will need to make more complex system changes. And it builds on…. You get the idea.

Domain Events and Event Driven Architecture (EDA)

Domain Events and Event Drive Architecture (EDA) provide for better communication between Bounded Contexts / micro-services.  Micro-services emit a Domain Event for each business event of significance. Other Bounded Contexts can subscribe to receive those Domain Events, and take action on interested events.

Domain Events also serve as an Audit Log of changes to the system state.

Let’s redesign the above scenario using Domain Events and EDA.

Once a bill is received and processed, the Billing Context emits a domain event “NewBillReceived”.

  • Approval Context can receive the event, and choose to ignore the event if the bill amounts to less than USD 1000
  • At the same time, Payment Context can also receive the Domain Event and choose to pay automatically, when the bill is valued at less than USD 1000
  • If the bill is greater than USD 1000, it will wait additionally, for Domain Event “BillApproved” from Approval Context

This approach ensures:

  • Each Bounded Context maintains the “single responsibility” principle. Billing Context is responsible to process bills and does not have to worry about calling Approvals or Payment Contexts
  • Bounded Contexts remain loosely coupled
  • Domain Events enable communication of significant business events
  • Changes in business process don’t require wholesale changes to affected Bounded Contexts

Handling Domain Events

There are multiple ways to publish and handle Domain Events.

  • They can be published at the end of an aggregate method, and then have the configured event handler take immediate action
  • Alternatively, the Domain Events can first be stored in a Domain Event store. A separate service that reads the Domain Event store can be build, which then publishes the event to the respective Domain Event handler

The second approach is most effective, since it decouples the process of generating and publishing Domain Events, paving way for increased testability. Also, Domain Event Stores serve as an Audit Log of all business events that have occurred within the system. If designed properly, it can reconstruct the state of the system, using events in the store.

Data storage and exchange

In addition to Domain Event-based communication, effective design of Aggregates / Domain Objects helps improve message handling, and transaction management in a micro-service. For instance:

  • Vendor, Bill and other Aggregates define transaction boundary
  • The transaction boundary ensures the Aggregate and the Domain Event are saved together in the same transaction
  • Only one aggregate can be changed in any transaction
  • Domain Events help propagate change / mutation to other contexts, or in some cases, within the same context via eventual consistency model
  • Domain Event subscribers must be Idempotent Receivers
  • The Domain Event dispatchers / messaging mechanism should be reliable and support delivery, at least once

“Bounded Contexts reference aggregates outside of the context by reference only.”  This goes a long way in avoiding anemic domain models.

When your system involves complex and long-running business processes, using a workflow engine with configurable business process will simplify your micro-services. Also using Command Pattern over just emitting events can further improve your micro-services design.

In Part 2 of my blog on DDD, I will attempt to explore Orchestration vs Choreography, and elaborate on how to define a workflow engine, to model a business process.


References, and additional reading
  1. Design a DDD-oriented microservice
    • https://docs.microsoft.com/en-us/dotnet/architecture/microservices/microservice-ddd-cqrs-patterns/ddd-oriented-microservice
  2. Domain Driven Design Distilled by Vince Vaughn
  3. Domain Event by Martin Fowler
    • https://martinfowler.com/eaaDev/DomainEvent.html
  4. A better domain events pattern
    • https://lostechies.com/jimmybogard/2014/05/13/a-better-domain-events-pattern/
  5. How to publish and handle Domain Events
    • http://www.kamilgrzybek.com/design/how-to-publish-and-handle-domain-events/