How to organise the logic inside one service: DDD aggregates, domain events and bounded contexts.
key idea / coordinatorservice / good pathin progress / watch outproblem / failureneutral
Two styles of business logic
Simple apps can use a procedure per request. Complex ones need an object model.
Why aggregates exist
Without clear boundaries two people can change different parts of the same order and break a rule that covers the whole order.
A load, update or delete acts on the whole aggregate.
The aggregate rules
Four rules make an aggregate a self-contained unit, and a natural fit for a service.
Where the logic lives
A service stays thin. The aggregate holds the rules and has a life cycle of states.
Domain events
An aggregate announces a state change with an event named in the past tense, such as OrderCreated.
Speak the domain's language
Ubiquitous language means using the same words in code that the business uses. Newman tells of a bank team that built a corporate-liquidity product on a generic data model where everything was an "arrangement": a loan, a share, a credit card. The product owner talked about haircuts and end-of-day sweeps, and none of those words were in the code, so analysts spent their time translating.
The same words then flow into API names, event names and service boundaries.
From Building Microservices (ch. 2, Just Enough Domain-Driven Design).
Bounded contexts and shared models
A bounded context is an explicit boundary around a part of the business. It offers some things to the rest of the system and hides the rest. At MusicCorp, finance and the warehouse are two contexts.
A concept can also live in both contexts with a different meaning. Finance cares about a customer's payments and refunds. The warehouse cares about the parcels sent to them, and may call the same person a recipient. Both link to one global customer id.
From Building Microservices (ch. 2, Bounded Context).
Aggregates, services and references
Aggregates and bounded contexts both make good service boundaries. Start with a service per whole context. Later you can split it, but one aggregate must stay with one service. A service may own several aggregates.
Aggregates also guard themselves. If an outsider asks for a state change, the aggregate can say no, so illegal transitions are impossible. A single aggregate is changed one request at a time, while different aggregates run in parallel. Smaller aggregates therefore give more throughput.
When an aggregate in one service refers to another in a different service, make the link explicit. A bare CustID column hides what it points to. A URI such as a REST address, or a scheme like soundcloud:tracks:123, shows the target and can be followed.
From Building Microservices (ch. 2) and Practical Event-Driven Microservices Architecture (ch. 3).
Inside one service: N-tier or clean
Pick one internal layout and keep it across services so that anyone can change any service. The book compares two. From Practical Event-Driven Microservices Architecture, ch. 3.
In N-tier the business logic ends up depending on data access, so the database shapes the logic and testing needs a database. In clean architecture (also called hexagonal or onion) the domain sits in the core and frameworks, drivers and the broker client live in the outer layer. Upgrading the messaging library touches only the outside. The author is pragmatic: the real win is that database and framework quirks stay out of the business rules, not that you can swap databases.
Recap
Prefer a domain model, organized with DDD aggregates, for complex logic.
An aggregate is a cluster of objects changed as one unit through its root.
Aggregates refer to each other by id, and one transaction changes one aggregate.
Aggregates emit domain events. The service publishes them.
Use the business's words in code, APIs and events (ubiquitous language).
A bounded context hides its internals and shares only a named, trimmed view. The same person can be a customer in one context and a recipient in another.
One aggregate lives in one service. Link aggregates across services with explicit references.
Choose clean architecture to keep domain logic apart from the database and broker. Start with coarse services.
Quick check
Why should one transaction update only one aggregate?Show answer
It keeps each transaction inside a single service and matches the limited transactions of NoSQL stores. Changes across aggregates use a saga instead.