05 Aggregates and Aggregate Roots How to Design Aggregates

05 Aggregates and Aggregate Roots - How to Design Aggregates #

Hello, I’m Ouchuangxin. Today we are going to learn about aggregates and aggregate roots.

Let’s first review the previous lecture. In an event storming session, we identify entities or value objects based on some business operations and behaviors. Then, we combine the entities and value objects that are closely related in terms of business logic to form an aggregate. After that, we assign multiple aggregates to the same bounded context according to the business semantics and complete the domain modeling within the bounded context.

Do you know why we introduce the concepts of aggregation and aggregate root between bounded contexts and entities? What are their roles? How do we design aggregates? These are the questions we will focus on in this lecture.

Aggregates #

In DDD, entities and value objects are fundamental domain objects. Entities generally correspond to business objects, which have business attributes and behaviors. Value objects mainly consist of a set of attributes that describe the state and characteristics of entities. However, entities and value objects are just individual objects, and their behaviors reflect the abilities of individuals.

So, what is the role of aggregates?

Let’s take an example. Society is composed of individuals, representing each one of us. As society develops, organizations such as associations, institutions, and departments emerge, transforming us from individuals into members of organizations. We can work together towards a common goal, exerting greater power.

Entities and value objects in the domain model are like individuals, and the organization that enables them to work together is the aggregate. It is used to ensure the consistency of data when these domain objects implement shared business logic.

You can think of an aggregate as a combination of entities and value objects closely associated with business and logic. An aggregate is the basic unit for data modification and persistence, with each aggregate corresponding to a repository for data persistence.

An aggregate has an aggregate root and context boundaries. Based on the principle of single responsibility and high cohesion, these boundaries define which entities and value objects should be included within the aggregate, while the boundaries between aggregates are loosely coupled. Microservices designed in this way naturally exhibit “high cohesion, low coupling”.

In the context of DDD layered architecture, aggregates belong to the domain layer, which includes multiple aggregates working together to implement core business logic. Entities within an aggregate implement individual business capabilities and have high cohesion with business logic. Business logic that involves multiple entities is implemented through domain services, and business logic that spans multiple aggregates is implemented through application services. For example, if a business scenario requires entities A and B within the same aggregate to work together, we can implement this piece of business logic using a domain service. If a business logic requires two services from aggregates C and D to work together, you can use an application service to combine these two services.

Aggregate Root #

The main purpose of an aggregate root is to avoid data inconsistency between aggregates and entities due to a lack of unified business rule control in a complex data model.

In a traditional data model, each entity is equal in status. If entities are allowed to call and modify data without control, it may lead to inconsistent data logic between entities. However, using locks would increase software complexity and decrease system performance.

If we liken an aggregate to an organization, then the aggregate root is the leader of this organization. The aggregate root, also known as the root entity, is not only an entity but also the manager of the aggregate.

Firstly, as an entity itself, the aggregate root possesses entity attributes and business behaviors, implementing its own business logic.

Secondly, as the manager of the aggregate, it is responsible for coordinating entities and value objects within the aggregate to collaboratively accomplish common business logic according to fixed business rules.

Lastly, it serves as the interface between aggregates, accepting external tasks and requests associated with the aggregate root ID, and implementing business collaboration between aggregates within the context. In other words, aggregates refer to each other through the aggregate root ID. If there is a need to access entities in other aggregates, one must first access the aggregate root and then navigate to the internal entities of the aggregate. External objects cannot directly access entities within the aggregate.

How to Design Aggregates? #

DDD domain modeling often uses Event Storming, which typically involves techniques such as use case analysis, scenario analysis, and user journey analysis. Through brainstorming, all possible business behaviors and events are outlined, domain objects that generate these behaviors are identified, and the relationships between domain objects are clarified. Then, aggregates are created by combining the root entities, entities, and value objects that are closely related to the aggregate root.

Let’s take the insurance application scenario as an example to understand the main steps involved in aggregate design.

img

Step 1: Using Event Storming, identify all the entities and value objects involved in the insurance application process, such as application form, subject, customer, insured person, etc.

Step 2: From the numerous entities, select the root entity that is suitable to act as the aggregate root. To determine if an entity is an aggregate root, you can consider the following scenarios: Does it have an independent lifecycle? Does it have a globally unique ID? Can it create or modify other objects? Is there a dedicated module to manage this entity? In the diagram, the aggregate roots are the application form and customer entities.

Step 3: Based on the single responsibility principle and high cohesion principle, identify all the closely related entities and value objects associated with the aggregate root. Construct an object collection that contains the aggregate root (only one) and multiple entities and value objects. This collection is the aggregate. In the diagram, we have created two aggregates: customer and insurance application.

Step 4: Within the aggregate, create the reference and dependency models of the objects based on the dependencies between the aggregate root, entities, and value objects. I need to clarify something here: the data of the applicant and insured person is obtained from the customer aggregate by associating with the customer ID. In the insurance application aggregate, they are value objects of the application form, and the data of these value objects is redundant data from the customer. Even if the data of the customer aggregate changes in the future, it will not affect the data of the application form value objects. From the diagram, we can also see the reference relationship between entities. For example, the application form aggregate root in the insurance application aggregate references the quote entity, and the quote entity references the quote rule sub-entity.

Step 5: Multiple aggregates are assigned to the same bounded context based on business semantics and context.

This is the complete process of how an aggregate is created.

Some Design Principles of Aggregation #

Let’s first take a look at the description of aggregation design principles in the book “Implementing Domain-Driven Design”. The original text is a bit hard to understand, let me explain it to you.

1. Model the real invariants within the consistency boundary. Aggregates are used to encapsulate real invariants, rather than simply combining objects together. Aggregates have a set of invariant business rules, and entities and value objects within the aggregates operate according to the same business rules, achieving data consistency of the objects. Anything outside the boundary of the aggregate is irrelevant to the aggregate. This is why aggregates can achieve high business cohesion.

2. Design small aggregates. If aggregates are designed to be too large, they will contain too many entities, leading to overly complex management of the entities. This can result in concurrency conflicts or database locks during high-frequency operations, ultimately reducing system availability. On the other hand, designing small aggregates can reduce the possibility of aggregate refactoring due to the aggregation being too large, making the domain model more adaptable to changes in the business.

3. Refer to other aggregates through unique identifiers. Aggregates refer to each other by associating with the ID of the external aggregate root, instead of directly referencing objects. Objects of external aggregates are managed within the boundary of the aggregate, which can easily result in unclear boundaries of the aggregate and increased coupling between aggregates.

4. Use eventual consistency outside the boundary. Data within an aggregate maintains strong consistency, while data between aggregates maintains eventual consistency. In a single transaction, at most one aggregate’s state can be changed. If a business operation involves changing the state of multiple aggregates, it should be done asynchronously through domain events, achieving decoupling between aggregates (I will explain this in more detail in the domain events section).

5. Implement cross-aggregate service calls through the application layer. To achieve decoupling between aggregates in microservices, as well as future composition and splitting of microservices based on aggregates, avoid domain service calls across aggregates and table associations across aggregates in the database.

These principles are some universal design principles of Domain-Driven Design (DDD). However, as the saying goes, “What suits oneself is the best.” When designing a system, you must consider the specific circumstances of the project. If there are factors such as convenience of use, high performance requirements, lack of technical capabilities, and global transaction management, these principles can also be overcome. In summary, everything should be based on solving practical problems.

Summary #

The content of Lecture 04 and Lecture 05 are actually closely related. Let’s summarize the relationships and differences between aggregation, aggregate roots, entities, and value objects.

Characteristics of Aggregation: High cohesion, low coupling. It is the lowest level boundary in the domain model and can be used as the smallest unit for splitting microservices. However, I do not recommend over-splitting microservices. In scenarios where performance is extremely demanding, aggregation can be an independent microservice to meet the requirements of frequent version releases and extreme scalability.

A microservice can contain multiple aggregations, and the boundaries between aggregations are natural logical boundaries within the microservice. With these logical boundaries, it is easier to split and combine in the evolution of microservice architecture, making the evolution of microservices architecture no longer a difficult task.

Characteristics of Aggregate Roots: Aggregate roots are entities with global unique identifiers and independent lifecycles. An aggregation only has one aggregate root. The aggregate root organizes and coordinates entities and value objects within the aggregation by directly referencing them. The collaboration between aggregate roots is implemented by associating them with IDs.

Characteristics of Entities: Entities have ID identifiers, equality is determined by IDs, and IDs need to be unique within the aggregation. Entities have mutable states and are attached to aggregate roots, with their lifecycles managed by the aggregate roots. Entities are generally persisted, but they do not necessarily have a one-to-one relationship with the database persisted objects. Entities can reference aggregate roots, entities, and value objects within the aggregation.

Characteristics of Value Objects: Value objects do not have IDs, they are immutable, have no lifecycles, and can be discarded after use. Equality between value objects is determined by their attribute values. The core essence of a value object is the values, which are a set of attributes that together form a complete concept, used to describe the state and characteristics of an entity. Value objects should preferably only reference other value objects.