Microservices
Reactive microservices: Part 3 - modeling events and domain-driven design (DDD)
A better way to model a microservices system
From the early days of object-oriented programming and service-oriented architectures, experts have recognized the benefits of encapsulation and of loose coupling between modules. Reactive microservices offer isolation and autonomy at a level that traditional architectures cannot. Reactive microservices each have a single responsibility and publish their capabilities through a protocol. They are message-driven and can cooperate and collaborate without being tightly coupled. These characteristics enable new development and deployment patterns:
- Each microservice is owned by a single team.
- Microservices can be monitored, tested, and debugged independently.
- Microservices can be upgraded frequently without impacting the other services in the system, supporting Continuous Delivery.
- Microservices can scale up and down and in and out independently, you can add resources or add and remove specific instances as demand fluctuates without affecting other services.
A reactive microservice, one that must provide scalability and resilience, should own its own data and be able to act autonomously. An autonomous service must store all the data necessary for it to achieve its functions. Sometimes this data can be owned by other services and be stored in many places. Obviously, you cannot ensure consistency by stopping all requests in the entire system to allow one operation to finish. How do you design a system capable of dealing with these complex interactions?
Domain-Driven Design (DDD) is a proven modeling pattern in which business and technical experts cooperate to design the system around business needs. DDD offers a realistic way to establish boundaries and contexts for individual microservices. However, what has proved to be difficult for many is modeling the space in between microservices.
Events-First Domain-Driven Design has emerged as a modification of DDD that makes it easier to model data dependencies and communication across the system. Events are facts, things that will happen in the running system. For example, in an ordering system, the finalization of an order is an important event. During event storming, you would explore what leads up to that event. That might include, for example, queries about sales tax rates, selection of a shipping method, and validation of payment. This will help you identify the entities involved and draw the correct boundaries around them.
The events provide a record of what happens to each entity and when it happened. In a reactive system, a microservice can publish events to durable storage, a so-called event log. Other microservices or external systems can then access those events. This helps by decoupling microservices from each other. The event log becomes a history that a recovering microservice can replay to recover and provides an excellent basis for auditing and debugging.
The challenge for developers new to an event-driven architecture is accepting the fact that when state changes, it takes an indeterminate amount of time before every component in the system sees that change. This is the tradeoff you make to achieve responsive characteristics and is referred to as eventual consistency.
In a traditional n-tiered architecture where functionality was tightly coupled and associated with a single database it was possible to maintain strong consistency. In a distributed system however, strong consistency limits scalability, availability, and throughput. A microservice design needs to identify where strong consistency is necessary — usually within a microservice — and where consistency can be relaxed.
The shift in paradigm to eventual consistency becomes easier when you realize that it more accurately reflects how the world works. The things we observe have already happened. And, workflows and state machines are common development practices that fit an event model.
Focusing on events and accepting eventual consistency allows for a variety of communication patterns in a microservices system.
One of the major benefits of microservices-based architecture is that it gives us a set of tools to exploit reality, to create systems that closely mimic how the world works.