14 Code Model Below How to Ensure the Consistency of Domain Models and Code Models

14 Code Model below How to Ensure the Consistency of Domain Models and Code Models #

Hello, I’m Ou Chuangxin.

In [Lesson 12], we learned how to construct a domain model using event storming. During the process of building the domain model, we extract various domain objects, such as aggregates, entities, commands, and domain events. Then, in [Lesson 13], we established a standard microservice code model based on the DDD layered architecture, defining the layers and directory structure for code objects.

However, completing the design and implementation of microservices requires one more step, which is the focus of today’s topic - mapping domain objects to the microservice code model. Why is this step so important?

DDD emphasizes building the domain model before designing microservices in order to ensure the unity of the domain model and microservices. Therefore, we cannot discuss the design and implementation of microservices without considering the domain model. However, when building the domain model, we usually take a business perspective, and some domain objects are even expressed in business language. Therefore, we need to use the domain model as input for designing and transforming domain objects, establishing a mapping relationship between domain objects and code objects.

Next, we will discuss this topic in detail.

Organizing Domain Objects #

After completing the microservice decomposition, the boundaries of the domain model and the domain objects are basically determined.

Our first important task is to organize the various domain objects generated during the event storming process, such as aggregates, entities, commands, and domain events, and record these domain objects and business behaviors in the table below.

As you can see, this table includes four dimensions: domain model, aggregate, domain object, and domain type. A domain model may contain multiple aggregates, an aggregate may contain multiple domain objects, and each domain object has its own domain type. The domain type primarily identifies the properties of the domain object, such as aggregate root, entity, command, and domain event types.

img

From Domain Model to Microservice Design #

To implement microservices based on the domain model, further design and analysis are required. The domain objects extracted from event storming need to undergo user story or domain story analysis, as well as microservice design, in order to be used for microservice system development.

This process is more in-depth and detailed than event storming. The main focus is as follows:

  1. Analyze the services within the microservice.
  2. Determine the layers that the services belong to.
  3. Identify the services and orchestrations that compose the application services.
  4. Define the business logic of the entities that are part of the domain services.
  5. Define the attributes and methods of the entities that adopt the rich model.
  6. Identify the value objects.
  7. Determine which entity is the aggregate root.

Finally, the dependencies between all domain objects and their corresponding code objects will be mapped out. Design of the package and code directory for each domain object will also be defined.

The recommended roles for participating in this design process are: DDD experts, architects, designers, and development managers.

Domain Objects in the Domain Layer #

At the end of the event storming, the domain model aggregation generally consists of aggregates, entities, commands, and domain events. After completing the story analysis and microservice design, the microservice aggregation typically includes aggregates, aggregate roots, entities, value objects, domain events, domain services, and repositories.

Let’s take a look at how these domain objects are derived.

1. Design Entities

In most cases, the business entities in the domain model correspond one-to-one with the database entities in the microservice. However, for certain domain model entities, they may be designed as multiple data entities or some of their attributes may be designed as value objects in the microservice design.

When analyzing personal customers, we also need entities such as addresses, telephone numbers, and bank accounts, which are referenced by the aggregate root. It is not easy to discover these entities during domain modeling, so they need to be identified and designed in the microservice design process.

In a layered architecture, entities adopt the rich model by implementing all of the entity’s business logic within the entity class. These different entities have their own methods and behaviors. For example, the address entity has methods for adding and modifying addresses, and the bank account entity has methods for adding and modifying bank accounts.

Entity classes are placed in the “Entity” directory of the domain layer.

2. Identify Aggregate Roots

Aggregate roots come from the domain model. In the personal customer aggregate, the personal customer entity serves as the aggregate root, responsible for managing the lifecycle of addresses, telephone numbers, and bank accounts. Through the factory and repository patterns, the personal customer aggregate implements the initialization and persistence of entities and value objects within the aggregate.

The aggregate root is a special type of entity with its own properties and methods. The aggregate root can establish object references between aggregates and reference all entities within the aggregate. The aggregate root class is placed in the “Entity” directory of the code model. The aggregate root has its own implementation methods, such as generating customer codes, adding and modifying customer information, etc.

3. Design Value Objects

Design certain attributes or sets of attributes of certain entities as value objects as needed. Value object classes are placed in the “Entity” directory of the code model. In the personal customer aggregate, the customer has a customer identification type, which exists as an enumeration value, so it is designed as a value object.

Some domain objects can be designed as either value objects or entities, depending on the specific situation. If the lifecycle of this domain object is maintained within other aggregates and only allows whole replacement within the entities it is attached to, it can be designed as a value object. If this object has multiple instances and needs to be queried and summarized based on it, I recommend designing it as an entity.

4. Design Domain Events

If the domain events in the domain model trigger subsequent business operations, we need to design domain events. First, determine whether the domain event occurs within the microservice or between microservices. Then design the event entity objects, the event publishing and subscribing mechanism, and the event handling mechanism. Determine whether an event bus or message queue middleware needs to be introduced.

In the personal customer aggregate, there is a domain event called “CustomerCreated”, so it has an entity called “CustomerCreatedEvent”. Entities and processing classes for domain events are placed in the Event directory structure in the domain layer. I recommend placing the publish and subscribe classes for domain events in the Event directory structure in the application layer.

5. Design Domain Services

If a business action or behavior spans multiple entities, we need to design domain services. Domain services combine multiple entity and entity methods to complete the core business logic. You can think of domain services as a layer of business logic above entity methods and below application services.

According to the strict layered architecture, if the methods of an entity need to be exposed to the application layer, they need to be encapsulated as domain services before being called by the application services. So if certain entity methods need to be called by the frontend application, we will encapsulate them as domain services and then encapsulate them as application services.

The method for creating personal customer information in the personal customer aggregate root entity is encapsulated as the CreatePersonalCustomerInfoDomainService. It is then encapsulated as the CreatePersonalCustomerInfoAppService and exposed to the frontend application.

Domain service classes are placed in the Service directory structure in the domain layer.

6. Design Repositories

Each aggregate has a repository, which is primarily used for data querying and persistence operations. Repositories consist of interfaces and implementations, achieving decoupling between application business logic and database resource logic through dependency inversion.

Repository code is placed in the Repository directory structure in the domain layer.

Domain Objects in the Application Layer #

The main domain objects in the application layer are application services and event publishing and subscribing.

During event storming or domain story analysis, we often design services or entity methods based on the commands initiated by users or the system. To respond to these commands, we need to analyze and record:

  • The business actions that will occur in the application layer and domain layer respectively;
  • The services or methods that need to be designed for each layer;
  • The hierarchical relationships between these methods and services, as well as their domain types (such as entity methods, domain services, and application services), and the calling and dependency relationships between them.

In a strict layered architecture pattern, it is not allowed to make cross-layer service calls. Each service can only call the next layer service. The services from bottom to top are: entity methods, domain services, and application services.

What should we do if we need to implement cross-layer service calls? I suggest using a method of encapsulating services one layer at a time.

img

Let’s take a look at the above diagram, which illustrates the encapsulation and calling of services.

1. Encapsulation of Entity Methods

Entity methods are the most basic atomic business logic. If a method of a single entity needs to be called cross-layer, you can encapsulate it as a domain service, so that the encapsulated domain service can be called and orchestrated by the application service. If it also needs to be called by the user interface layer, you also need to encapsulate this domain service as an application service. Through the step-by-step encapsulation of services, entity methods can be exposed to different layers, achieving cross-layer calls.

When encapsulating, you can keep the name of the service consistent. You can use the suffix DomainService or AppService to differentiate between domain services and application services.

2. Combination and Encapsulation of Domain Services

Domain services combine and orchestrate multiple entities and entity methods for the application service to call. If it needs to be exposed to the user interface layer, the domain service needs to be encapsulated as an application service.

3. Combination and Orchestration of Application Services

Application services combine and orchestrate multiple domain services, expose them to the user interface layer, and are called by frontend applications.

When combining and orchestrating application services, you need to pay attention to the fact that multiple application services may repeat the same business logic combination and orchestration of the same domain services. When this happens, you need to analyze whether the domain services can be integrated. You can merge the domain services that are repeatedly combined into a single domain service, which not only eliminates the repetitive orchestration of application services but also achieves service evolution. This way, the domain model will become more refined and better suited to the business requirements.

Application service classes are placed in the Service directory structure in the application layer. The publish and subscribe classes for domain events are placed in the Event directory structure in the application layer.

Mapping between Domain Objects and Microservice Code Objects #

After completing the analysis and design mentioned above, we can establish the mapping relationship between domain objects and microservice code objects, as shown in the diagram below.

Typical Domain Model #

In the personal customer domain model, the personal customer aggregate is a typical domain model, from which multiple entities and value objects can be extracted, as well as its aggregate root.

Let’s take a look at the following diagram, where we have further analyzed the personal customer aggregate. We have extracted the customer type value object, as well as entities like phone, address, and bank account. We have encapsulated and layered the entity methods and services, established the associations and dependencies of domain objects, and designed repositories. The key point is that during this process, we have established the mapping relationship between domain objects and microservice code objects.

img

Now, let’s briefly explain each column of the table.

Layer: Specifies which layer of the layered architecture the domain object belongs to, such as presentation layer, application layer, domain layer, or infrastructure layer.

Domain Object: The specific name of the domain object in the domain model.

Domain Type: The type of the domain object based on the DDD knowledge system, including bounded context, aggregate, aggregate root, entity, value object, domain event, application service, domain service, and repository.

Dependent Domain Object: The dependencies of the domain object based on the business object dependencies or layered calls, such as service invocation dependencies and associated object aggregation.

Package Name: The package name in the code model, corresponding to the package where the domain object is located.

Class Name: The class name in the code model, corresponding to the class name of the domain object.

Method Name: The method name in the code model, corresponding to the implemented or operated method of the domain object.

After establishing this mapping relationship, we can obtain the microservice code structure shown in the diagram below.

img

Atypical Domain Model #

There may be some business scenarios where you cannot design a typical domain model. In such scenarios, there may be multiple entities that are independent of each other and have a loose coupling. These entities are mainly involved in analysis or calculations, and it may be difficult to identify an aggregate root. However, in terms of the business itself, these entities are highly cohesive. Moreover, the business they combine with other aggregates is within a bounded context, making it unlikely to design them as separate microservices.

This type of business scenario is actually quite common. For example, in the personal customer domain model, there is an aggregate for customer merging. It scans all customers, checks whether they are duplicates based on business rules such as ID card number and phone number, and then merges the duplicate customers. In this type of business scenario, it is not possible to identify an aggregate root.

So, what can we do for these atypical models?

We can still borrow the concept of aggregates and use them to define this functionality. We can use the same analysis methods as in typical domain models to design entity attributes and methods, encapsulate and layer methods and services, design repositories, and establish dependencies between domain objects. The only regret is that we still cannot find an aggregate root. However, that’s okay, because besides aggregate management functionality, we can also use other design methods from DDD.

Summary #

Today we learned the design process from domain model to microservices, which is crucial in the process of microservice design. From the perspective of the microservice system, you need to analyze the domain model in depth and detail, layer the domain objects, identify the dependency relationships between the domain objects, establish the mapping relationship between the domain objects and the microservice code objects, in order to ensure the consistency between the domain model and the code model, and finally complete the design of the microservices.

After establishing the relationship between this business model and the microservice system architecture, the entire project team can work in a unified common language, even if the development personnel are not familiar with the business, or the business personnel are not familiar with the code, they can quickly locate the code position.