04 How Domain Models Guide Software Design

04 How Domain Models guide Software Design #

Based on DDD program design, the domain model designed earlier is mapped into the program design in the data architecture, thereby improving the software design quality through domain-driven development. So, how can the domain model guide program design? To map the domain model to program design, it will ultimately be reflected in three types of object design: services, entities, and value objects.

Services, Entities, and Value Objects #

The first step in building a domain model is to distinguish between services, entities, and value objects.

Services #

Services specifically refer to operations and behaviors outside of domain objects. In DDD, “services” usually perform two types of responsibilities: accepting user requests and performing certain operations. When a user performs some operations in the system interface, a request is sent to the system. At this time, it is the “service” that first receives these requests from the user and then executes the corresponding methods based on the requirements. In the process of executing these methods, the “service” operates on the corresponding entities and value objects. Finally, after all operations are completed, the data in entities or value objects is persisted in the database.

For example, when a user needs to place an order, a “place order” request is initiated from the front end. This request is received by the “Order” service, which then performs the corresponding order operation. During the execution process, the “Order” service validates the data in the “Order” entity, completes various data operations, and finally saves it to the database.

Entities #

Entities are domain objects that are distinguished in the real world by a unique identifier field. For example, in a student management system, a “student” object is an entity that distinguishes each student through the identifier field “student ID”. Each student can be uniquely identified by a student ID; moreover, this student has many attributes such as name, gender, age, etc., which change over time. Such a design is called “entity”.

Value Objects #

Value objects represent unchanging and essential things in the real world. These domain objects are called “value objects”, such as geographic locations, administrative divisions, currencies, industries, positions, etc.

Distinguishing Entities and Value Objects #

In DDD, entities and value objects are strictly distinguished. Mutability is the characteristic of entities, while immutability is the essence of value objects. For example, Beijing is a city, and an architect is a position. The characteristics of these things are always unchanging.

In actual projects, we can flexibly choose entities or value objects based on different business requirements. For example, in an online ordering system, depending on the business requirements, the menu can be designed as either an entity or a value object. For example, “Kung Pao Chicken” is a dish. If it is designed as a value object, there is only one record of “Kung Pao Chicken” in the entire system, and all restaurant menus that have this dish reference this record. If it is designed as an entity, each restaurant’s “Kung Pao Chicken” is considered to be different, and for example, each restaurant’s “Kung Pao Chicken” has a different price. Therefore, it is designed with multiple records and unique IDs, and each restaurant uses its own “Kung Pao Chicken”.

Anemic Domain Model vs Rich Domain Model #

Services, entities, and value objects are the basic elements of domain-driven design. However, to ultimately convert the business domain model into program design, appropriate design needs to be added. In general, there are two design approaches for converting the business domain model into program design: anemic domain model and rich domain model.

Anemic Model and Rich Model #

Here’s the story: In 2004, software master Eric Evans published his immortal work “Domain-Driven Design”. Although more than a decade has passed, this book still provides us with considerable help today. Then, another software master Martin Fowler introduced the concept of “Anemic Model” in his own blog. This “Uncle Martin” has a very unique feature, that is, he invented various terms in the software industry, including software refactoring and microservices, which have had a huge impact on the industry. However, when Uncle Martin proposed the “Anemic Model”, he criticized it as an anti-pattern: the so-called “Anemic Model” in software design refers to the existence of many Plain Ordinary Java Object (POJO) objects that have a lot of get/set methods but almost no business logic. Such a design is called the “Anemic Model”.

image.png

As shown in the above figure, in the domain model, there is a domain object for VIP members, which has a bunch of properties as well as methods such as “member discount”, “member benefits”, and “member privileges”. If this domain model is designed according to the Anemic Model, an entity object and a service for VIP members will be designed. The entity object contains all the properties of the object and the data contained in these properties. Then, all the methods are put into the service, and when calling them, the domain object must be passed as a parameter. Such a design separates these methods in the domain object and the data required by these methods into two different objects, breaking the encapsulation of the object. What problems does it bring?

image

As shown in the above figure, VIP members in the domain model are divided into “gold card members” and “silver card members” through inheritance. If this domain model is designed according to the Anemic Model, an entity object and a service for “gold card members” will be designed, and an entity object and a service for “silver card members” will also be designed. The entity object of “gold card members” should call the service of “gold card members”. If the entity object of “gold card members” calls the service of “silver card members”, the system will encounter an error. Therefore, in addition to the above design, a client program needs to be created to determine whether the current entity object is a “gold card member” or a “silver card member”. At this time, system changes become less flexible.

For example, if it is now necessary to add a “platinum member” on the existing basis, not only an entity object and a service for “platinum members” need to be added, but also the judgment of the client program needs to be modified, which will increase the cost of system changes.

image

To address the problems of the Anemic Model, Uncle Martin proposed the concept of “Rich Model”. The so-called “Rich Model” directly transforms the original form of the domain model into the design of domain objects in the program. At this time, various business operations are no longer implemented in the “service”, but in the domain objects. As shown in the figure, in program design, there is both a parent class “VIP member” and subclasses “gold card member” and “silver card member”.

However, the difference between the Rich Model and the Anemic Model is:

  • The methods in the domain objects are also preserved in the entity objects in the program design. In this way, through inheritance, although “gold card members” and “silver card members” both have a “member discount” method, “member discount” for “gold card members” is different from that for “silver card members”;
  • Although the Rich Model also has a service with methods such as “member discount”, “member benefits”, and “member privileges”, the service in the Rich Model only does one very simple thing, which is to directly call the corresponding method in the entity object when receiving a request from the user, without doing anything else.

In this way, the “VIP member” service does not need to pay attention to whether it is currently calling the “gold card member” or the “silver card member”. It only needs to call “member discount”:

  • If the current entity object is a “gold card member”, “member discount” for “gold card members” is executed;
  • If the current entity object is a “silver card member”, “member discount” for “silver card members” is executed;
  • If another “platinum member” needs to be added, only a subclass for “platinum members” needs to be written, and the “member discount” method needs to be overridden. The “VIP member” service does not need to be modified at all, reducing the maintenance cost of changes.

Comparison of the advantages and disadvantages of two design concepts #

There are many benefits of adopting the rich domain model design:

  • It preserves the authenticity of the domain model, directly translating the domain model into the program’s design. This allows for direct mapping of changes to the program when the domain model undergoes frequent or significant adjustments due to business needs.
  • As mentioned in the example above, the rich domain model maintains the encapsulation of objects, making it easier to handle complex structures such as polymorphism and inheritance.

The rich domain model is elegant in theory, but it falls short in engineering practice. On the other hand, although the anemic domain model may seem simplistic on the surface, it still possesses many excellent characteristics in engineering practice, mainly in the following three aspects.

1. The anemic domain model is simpler and more feasible than the rich domain model

The rich domain model directly maps the original domain model into the program’s design, which requires the addition of more components such as repositories and factories during program design, placing higher demands on design and architecture skills.

For example, let’s say we want to design an order system. In domain modeling, each order needs to have multiple order details and corresponding customer and product information. Therefore, when loading an order, we need to simultaneously retrieve its order details, as well as the corresponding customer and product information. This requires a powerful order factory for assembly. After loading the order, it needs to be cached in a repository with caching capabilities. Furthermore, when saving an order, both the order and its order details need to be saved together in a transaction. All of this requires strong technical platform support.

In contrast, the anemic domain model appears more impoverished. In the anemic domain model, the MVC layer directly calls the Service, and the Service accesses the data through the DAO. In this process, each DAO only queries a specific table in the database and then hands it over to the Service to be used for various operations.

Using the order system as an example, there is an OrderDAO responsible for querying orders and an OrderDetailDAO responsible for querying order details. After they are queried, they do not require assembly but are directly handed over to the Order Service for use. When saving an order, the Order DAO handles saving the order, while the OrderDetail DAO handles saving the order details. They are organized and transactionalized by the Order Service. The anemic domain model does not require repositories, factories, or caching. Everything appears simple and straightforward.

2. The rich domain model requires stronger design and collaboration capabilities

The implementation of the rich domain model requires developers to possess higher skill requirements, including strong OOA/D (object-oriented analysis/design) abilities and the ability to analyze business requirements, perform business modeling and design. For example, in the case of the order system, developers need to first perform domain modeling, analyzing the relationships between domain objects such as orders, order details, users, and products in that scenario. They also need to analyze what behaviors each domain object has in the real world and determine the corresponding methods in the software design before proceeding with development.

At the same time, the rich domain model requires strong team collaboration capabilities. For example, in this scenario, when creating an order, it is necessary to query relevant information about the user and the user’s address. At this point, the Order Service cannot directly query the tables related to the user and user address; instead, it needs to call the relevant interfaces of the User Service to perform the queries. In this case, the team developing the Order module needs to communicate the interface requirements to the team developing the User module. Compared with the rich model, the anemic model is simpler and more straightforward. All business processing is delegated to the Service to complete. In the process of business processing, if you need data from certain tables, you can call the corresponding DAO: if you need orders, find the order DAO; if you need users, find the user DAO; if you need products, find the product DAO. The program is simple and easy to understand, making maintenance easier in the future. In summary, the rich model has a noble temperament, “exquisite” - expensive and elegant; the anemic model is “grassroots” - simple and direct.

DDD 04–Golden Quotes.png

3. The anemic model is more suitable for complex business processing scenarios

When designing the rich model, all business processes are implemented in the corresponding methods of the domain objects. This design can handle simple business processes with ease. However, when facing very complex business processing scenarios, it may become somewhat inadequate. In these complex business processing scenarios, if the anemic model is adopted, the complex scenarios can be divided into several relatively independent steps, and these steps can be assigned to multiple Services to be executed in a sequential manner. In this way, the steps are loosely coupled and organized together, with domain objects passed as parameters in various Services.

image

In this design, domain objects can be used as both inputs and outputs for various method calls. For example, in the example shown in the above figure, the domain object is first called by ServiceA as a parameter; after the call, the result data is written to the first 5 fields of the domain object and passed to ServiceB; after receiving the domain object, ServiceB can read the first 5 fields as input and write the execution result to the middle 5 fields as output; finally, the domain object is passed to ServiceC, and after the operation is completed, the last 5 fields are written; when all the fields are written, they are stored in the database, completing the entire operation.

In this process, if changes are needed in the future, such as adding a processing step, removing a processing step, or adjusting their execution order, it is relatively easy to do so. This design requires that the processing steps be implemented outside the domain object, in the Service. However, if the rich model is used, all processing steps must be implemented within the domain object, regardless of how complex they are. Such a design is bound to increase the cost of future changes and maintenance.

Therefore, whether it is the anemic model or the rich model, they each have their own advantages and disadvantages. It has been debated for so many years whether the anemic model or the rich model should be used. However, I believe that they are not mutually exclusive, and we should combine them, make use of their strengths, and utilize them reasonably. The key is to first understand their differences, that is, where the business logic should be implemented: the business logic in the anemic model is implemented in the Service, while in the rich model, it is implemented in the domain object. Once this point is clear, in future software design, we can put the business logic that needs to be encapsulated in the domain object and design it according to the rich model; for other business logic, we can put it in the Service and design it according to the anemic model. So, which business logic needs to be encapsulated and designed according to the rich domain model? This is subjective and can vary from person to person. I have summarized the following aspects:

  • As mentioned earlier, if there are situations in the domain model that involve inheritance and polymorphism, these parts should be implemented in the domain objects in the form of a rich domain model.
  • If there is a need to convert certain types or codes during software design, these conversion parts should be encapsulated in the domain objects. For example, some boolean fields may not have a boolean type in the database. People have different preferences, some use 0 and 1, some use Y and N, or T and F. This can confuse developers about which fields are Y and N, and which are T and F. In this case, they can be encapsulated in domain objects and converted to boolean types for presentation to the upper layer development in accordance with the rich domain model.
  • It is desired to better represent the relationships between domain objects in software design. For example, when querying orders, you may want to display the corresponding user for each order, as well as the order details for each order. In addition to reflecting the relationships in the domain model design, support from repositories and factories is also needed. For example, when loading orders, you need to query both the order and the order details and assemble them using the order factory. After querying an order, you need to fill in the corresponding user and details using the factory.
  • The last case is called “aggregation”, which refers to things in the real world that represent wholes and parts. For example, an order has order details, and an order can have multiple order details. From a business perspective, they have a whole and part relationship. The order details are a part of the order, and they have no meaning without the order. In this case, when operating on an order, the operations on the order details should be encapsulated in the order object and designed in the form of a rich domain model.

Summary #

This lecture explains DDD-based program design. Domain model analysis is just an intermediate process of software requirements analysis, and it ultimately needs to be translated into program design. The final implementation of the domain model consists of three types of objects: services, entities, and value objects. There are two design approaches: anemic domain model and rich domain model. By implementing it in this way, the domain model can effectively guide program development and improve the design quality.

In the process of implementing DDD, there is no need to focus too much on whether something is an entity or a value object. Instead, more effort should be put into analyzing and understanding the business. At the same time, combining the anemic domain model and rich domain model can take advantage of their strengths and code appropriately.

However, there are still many challenges to overcome in implementing the domain model. Therefore, the next lecture will further explain the aggregation, repository, and factory aspects of DDD and their design approaches.