Use Domain Driven Design’s Aggregates to reduce complexity

Use Domain Driven Design’s Aggregates to reduce complexity

Use Domain Driven Design’s Aggregates to reduce complexity ?

Domain Driven Design (DDD) is a collection of design principles and patterns that helps developers and architects develop elegant object-oriented systems. When properly implemented it can lead to software abstractions that are called domain models. These domain models encapsulate complex business logic, and invariants closing the gap between business reality and code.

In this article, I'll cover the aggregate concepts and design patterns that address data modeling and transaction in DDD. Think of this blog as an introduction to designing aggregates and evolving rich domain models. To establish some context to the discussion, we will use an aggregate to reduce complexity in implementing business domains that I'm familiar with medical insurance policy management and healthcare examples.

I highly recommend that you add DDD to your toolbox by reading the book Domain-Driven Design: Tackling Complexity in the Heart of Software, by Eric Evans. Eric Evans is the founder of Domain Driven Design

Aggregates

In Domain Driven Design, an aggregate can be a confusing concept. One way of looking at it is to think of an aggregate representing a domain concept such as an invoice or an insurance policy. Aggregates are transactional boundary that enforces business invariants?by grouping entities and value objects that are associated with business rules and invariants together in one transaction.?


No alt text provided for this image
Figure Aggregate building blocks big picture

Aggregates help reduce complexity in large complex object graphs that mirror reality but create issues when trying to model a solution or data model.?Having to build and hydrate such a large object would be an expensive operation and would cause high contention, as the entire domain object would be locked during transactions. ?Complex domain object graphs present difficult technical challenges and performance issues as well. ?Rehydrating or constructing such a large object graph can take some time to create and load into memory.?So, managing these complex object graphs is important.


No alt text provided for this image
Figure Complex object graphs mirroring reality

Managing Complex Object Graphs

We can manage complexity by breaking up the large complex object graph by justifying each association and ensuring it matches a domain invariant.?A model that justifies its object association is a domain model that reveals an important domain concept. ?When successful it makes possible the creation of a solution that reduces contention and decouples unnecessary dependencies. ?We gain cleaner technical implementation and better performance, this is achieved by reducing the size of the aggregates we need to reconstruct or hydrate, resulting in less expensive operations.?

No alt text provided for this image
Figure Breaking up the complex object graph by the business association

If the relationship is not required and does not work to fulfill an invariant don’t implement it.?Avoid modeling real life.?Just because the relationship exists in the problem domain doesn’t mean it provides benefit in the code implementation.?In the example below if we placed the patient inside of the aggregate transactional boundary of Billing Bounded Context and other systems that need a patient could cause locks and high contention as they try to update a patient.?By breaking the customer out and having the Patient aggregate transactional boundaries when a change is made to the patient it will not directly impact the other dependent Bounded Context as they will only hold a reference by id to a patient. ?

No alt text provided for this image
Figure Billing and Patient Aggregates



Business Rules and Invariants

The business has workflows where certain things need to happen in a particular order. This can be due to a business process or to be in compliance with some regulatory standard.?These rules cannot change therefore they are referred to invariants as unchanging, constant rules to follow.?Well, you may argue that all things change but, in this context, we are talking about the execution of the application during run time.?Of course, business change business rules, and what can be deemed invariant today can change tomorrow but in applications, invariants do not change during run time and must remain constant throughout the execution of the application. ?

For example, if a patient’s medication order is shipped before payment is received, then we have broken the invariants or business rule as it makes sense that payment must be received before orders are shipped. In the previous example, we placed payment in the wrong order and now the application is in a corrupted state as the business invariant is broken.?Let’s review how we group aggregates.

?

Grouping Aggregates

Then group your aggregates based on:

●???????Business invariants

●???????Language

●???????Association and logic grouping supporting the business rules

As shown in the following diagram, we group aggregates and their internal entities based on their transactional boundary, which enforces the business invariant or business rules:


No alt text provided for this image
Figure Transaction boundaries grouped by association

Transactional Boundary

Guaranteeing the consistency of changes to objects across complex business associations can be challenging, to say the least as we need to enforce business invariants. If our aggregates include common objects such as an address that other aggregates can reference and have different business rules around addresses, then conflicts can arise. These common objects can create contention leading to locking issues as aggregates can try to update the same object at the same time creating locking situations, these locking issues negatively impact performance or even lead to crashes of the application. ?By balanced grouping of our objects based on avoiding high-contention and tightening of strict invariants, we can avoid issues with locks and give the common objects their own aggregate transaction boundary and it will be the common objects responsible for updating themselves and their internal objects.

No alt text provided for this image
Changes are made through the root

Rules for Aggregates

To turn a conceptual aggregate into an implementation, we need to follow 5 important rules for all transactions.

1.??????Each aggregate has a root entity that has a global identity, and it also has internal objects that have an internal identity and can only be traversed through the root. No external entity can have a direct reference to an internal member of an aggregate.

2.??????All changes to the state of the aggregate and its members are done through the root.

If an external aggregate A needs an internal object from another aggregate B, it can request a copy from the root of that aggregate B. Internal members of an aggregate can have reference to an external aggregate root using the global identity.

3.??????Only root aggregate can be obtained by directly querying the database. All other objects must be found by traversal of internal associations of the root aggregate.

?4.??????If a delete operation is executed, it must remove everything within the aggregate boundary at once. ?Also, before any state changes of an object is committed, within the aggregate boundary all invariants or business rules of the aggregate must be satisfied.

Transactions that change state are hard enough to manage but combining state changes with satisfying some request response for a user interface in the same transaction creates unnecessary complexity. ??This can tightly couple business logic to the presentation layer, which violates the separation of concerns principle.??Let’s discuss this by discussing how data flows by direction.

Bidirectional communication between services creates complexity

Bidirectional communication by using a request-response model can tightly couple services and increase complexity as demonstrated:

No alt text provided for this image
Bidirectional communication

This can often occur when teams align aggregates state changes to UI concerns, which is a common mistake. Aggregates should align with the business rules and it’s the domain invariants as we have discussed previously, so avoid the tendency of looking at the world through the eyes of the User Interface.

Unidirectional flow

To reduce complexity, move to Unidirectional flow and separate the responsibilities of commands which change state and queries that fetch data for a view model for a user interface, or request and response pattern. There are situations where this is unavoidable the need for a request to return a response for example if you need to update inventory and return the inventory data but, when possible, avoid requiring a response back with an aggregate for state change operations limit response in these scenarios to request received or Http Status codes. Commands should be one-way for example, when an e-commerce application issues a command to create an order and persist the order in the data store and then raise an event that the order was created so that other services like a payment service can process payment for the order and executes its’ role in the workflow.


No alt text provided for this image
Figure Command’s flow in one direction and mutate data

Unidirectional design is a better choice as we first need to ensure we have enforced our business invariants and that domain objects are in the correct state. This can be achieved by employing Command Query Segregation Principles.

?

Command Query Segregation Principle (CQRS).

Commands are a one-way concept as illustrated in the following figure: Commands are responsible for changing state and Queries are responsible for fetching data for view Models for the user interface.?This pattern is perfect for aggregates as we can isolate the ATOMIC transactions and execute them inside the aggregate’s transactional boundary.??This allows us to focus on the business domain rule and insuring business invariants are not violated.?In addition, separating out the query responsibilities allows for the optimization of queries providing a better user experience.?

No alt text provided for this image
Figure Command and Queries have a separate model

Aggregates provide a powerful way of reducing complexity and managing states across the boundaries of other services.?As we move to a more distributed services approach like microservices, it can be challenging to keep the state across the integrated services within a business domain in sync. The data needs to be kept concurrent and consistent and aggregates help in managing the state within the boundaries of their respective service.?Other aggregates in the domain only hold a reference to a Global Identity to other aggregates reducing the complexity of keeping a complete copy and having to ensure its copy is in the right state.?We remove any unnecessary associations to entities that do not support the business invariant of that domain's root aggregate.???This in turn reduces locking and contention conflicts that can occur when two domains try to update or change the state of a shared domain entity.?By keeping the responsibility state at the root aggregates, we protect the aggregate's internals from being corrupted or mutated by an external source.??As all changes must flow through the root and all requests for copies of the internal objects must also come from the root aggregate as well.??

Recommended reading:

Eric Evans: book Domain-Driven Design: Tackling Complexity

https://read.amazon.com/kp/embed?asin=B00794TAUG&preview=newtab&linkCode=kpe&ref_=cm_sw_r_kb_dp_0RQ6R0YH6PDDXJTY3MNS

Vaughn Vernon: Domain Driven Design Distilled

https://read.amazon.com/kp/embed?asin=B01JJSGE5S&preview=newtab&linkCode=kpe&ref_=cm_sw_r_kb_dp_XVC93AJ3V7YEG0NVQ5FA

Scott Millett: ?Patterns, Principles, and Practices of Domain-Driven Design 1st Edition

https://read.amazon.com/kp/embed?asin=B00XLYUA0W&preview=newtab&linkCode=kpe&ref_=cm_sw_r_kb_dp_AFY1TZJGB3PGNK6PXB3J








要查看或添加评论,请登录

Tim Oleson的更多文章

社区洞察

其他会员也浏览了