Domain-driven programming

I. Introduction

Compared with the MVC layered architecture that everyone is familiar with, domain-driven design is more suitable for the architectural model of complex business systems and software systems that require continuous iteration. Regarding the concept and advantages of domain-driven design, there are many literatures that can be referred to. Most students have read related books, so this article does not discuss domain-driven design. Development makes some brief introductions.

After joining AliHealth, my team is also actively promoting the application of domain-driven design. Relevant students have also given excellent scaffolding codes, but it seems that the current implementation situation is not ideal. In my humble opinion, the main reasons for this result are Four reasons.

Everyone is more familiar with the MVC programming mode. When a certain function needs to be implemented quickly, they tend to use a more secure and familiar method.

Everyone does not have a unified understanding of how domain-driven programming should be written (Axon Framework[1] implements domain-driven design very well, but it is too "heavy").

The implementation of DDD itself is relatively difficult, and it often requires event-driven and Event Store to realize it perfectly, and these two are not commonly used by us.

Domain-driven design is oriented to complex systems, and the initial stage of business development looks relatively simple, and it is suspected of over-designing domain-driven design as soon as it comes up. This is why domain-driven design is often discussed only when the system has to be refactored.

The author has researched and practiced domain-driven programming during the research and development process, and also has a deep understanding of the domain-driven framework Axon Framework. (Maybe because the business scenario is relatively simple) the landing effect was not bad at that time. Regardless of the perspective of an architect, from the perspective of front-line R&D students, the core advantages of domain-driven programming are:

*Implement an object-oriented programming model to achieve high cohesion and low coupling.
*During the iterative process of complex business systems, it is guaranteed that the code structure will not become chaotic indefinitely, thus ensuring the sustainable maintenance of the system.
* Of course, the most important thing in domain-driven development is to disassemble the domain correctly. This disassembly can be carried out under the guidance of theory and combined with the designer's in-depth analysis and full understanding of the business. This article assumes that domain division has been carried out before development, and focuses on how to practice in the coding phase to reflect the advantages of domain-driven.

2. Introduction to insurance field knowledge

Taking the insurance business as an example to carry out programming practice, a highly abstract division of the insurance field is shown in the figure. Through use case analysis, we divide the entire business into product domains, underwriting, underwriting, claims and other domains (Bounded-Context), and each domain can be divided into sub-domains according to business development. Of course, the complete insurance business is much more complicated than what is shown in the figure. Here we do not introduce it as a chapter of business knowledge, but only for the convenience of subsequent code practice.

Three domain-driven development code structure

1 Domain-driven code layering

You can use different Java projects to publish different microservices to isolate the domain, or you can use different modules to isolate the domain in the same Java project. Here we use module to implement domain isolation. However, no matter what method is used for domain isolation, the interaction between domains can only use the HTTP service provided by the other party's second-party package or API layer, and cannot directly introduce other services in other domains.

Within each domain, compared to MVC's splitting of the application's three-tier architecture, domain-driven design divides the application module into four layers as shown in the figure.

user interface layer

Responsible for directly facing external users or systems, receiving external input, and returning results, such as the implementation class of the second-party package, Controller in Spring MVC, specific data view converters, etc. are usually located in this layer. Package names that are often used at the code level can be interface, api, facade, etc. The input and output parameters of the user interface layer are defined in POJO style.

The user interface layer is a light layer that does not contain business logic. Security authentication, simple input parameter verification (such as using @Valid annotation), access logging, unified exception handling logic, and unified return value encapsulation should be completed at this layer.

The implementation of the functions required by the user interface layer is completed by the application layer, and there is generally no need to perform dependency inversion here. When coding, this layer can directly introduce the interface defined in the application layer, so this layer depends on the application layer. It should be noted that although in theory the user interface layer can directly use the capabilities of the domain layer and infrastructure layer, it is recommended that you adopt a strict layered architecture before you are proficient in this usage, that is, the current layer only relies on the adjacent floor below it.

application layer

The application layer specifically implements the functions required in the interface layer, but this layer does not implement real business rules, but coordinates and calls the capabilities provided by the domain layer according to actual use cases.

Message sending, event monitoring, transaction control, etc. are recommended to be implemented at this layer. The commonly used package names at the code level can be application, service, manager, etc. It is used to replace the service layer in Spring MVC and transfer business logic to the domain layer.

domain layer

The domain level is object-oriented, and it is mainly used to reflect and realize the inherent capabilities of objects in the domain. Therefore, in domain-driven programming, the programming implementation of the domain layer is not allowed to rely on other external objects. The programming of the domain layer is based on the inherent capabilities we have for the objects in the domain and what it will show in the current business scenario. After you have a certain understanding of your ability, you can directly code it.

For example, when we first came into contact with object-oriented programming, an example we often encountered was that birds can fly and dogs can swim. Assuming that our business domain only cares about the movement of these objects, we can do the following implementation.

Domain-driven programming requires the ability to realize objects in this way (congested model), instead of writing business logic in services as we often use anemic models in MVC architecture.

Of course, even if such a programming method is adopted, it is still far from realizing domain-driven, and some seemingly simple problems may bring us a huge sense of uneasiness. For example, how should complex objects be initialized and persisted? The same thing exists in different fields, but when the focus is different, how should the thing be abstracted? When objects in different fields need each other's information, how should they obtain it?

For these problems, we will also try to give some reference solutions in the code example section.

infrastructure layer

The infrastructure layer provides common technical capabilities for the above layers, such as the ability to monitor and send messages, and the CRUD capabilities of databases/caches/NoSQL databases/file systems, etc.

2 Summary

According to the further analysis of each layer of domain-driven design, a more specific hierarchical structure is as follows.


Based on the above layering principle, a code structure that can be referred to in the aforementioned insurance field is as follows. We will explain the concept and function of each subcontract in detail in the following coding examples.

Four domain-driven development code

Theoretically, DOMAIN does not depend on other layers and is the core of the business. We should write the domain layer code first. However, due to our lack of knowledge in the insurance field, we may not know what inherent capabilities the insurance policy has; We show the code directly with the help of a use case.

1 use case

The user selects an insurance product on the front-end page, selects optional protection responsibilities, enters the information of the applicant/insured, selects the payment method (installment/single payment, etc.) and submits the insurance request after payment;
The server accepts the insurance application -> underwriting -> issuing the policy -> issuing the policy benefits.
Here use case 1 is the pre-use case of use case 2. We assume that use case 1 has been successfully completed (the rate calculation has been completed in use case 1), and only use case 2 is implemented, and use case 2 is only a rough implementation, as long as the code style can be displayed That's it.

2 User interface layer programming practice

Subcontract structure

Among them, client is the implementation of the insurance-client (public second-party package), and web is the implementation of the rest-style interface.

The classes of input parameters and return values used here are defined in the application layer.

3 Application layer programming practice

1. Subcontract structure

Among them, the outermost interface is oriented to specific business scenarios, and can be subcontracted according to business development.
The pojo package defines various data classes used by the application layer (the IssuePolicyRequest above is here) and the converters that need to perform type conversion when propagating to other layers.
Some timing task entries are defined in the tasks package.
Note that in the practice of domain programming, a lot of type conversions are required, and we can use some frameworks (such as MapStruct[2]) to reduce the tedious work that these type conversions bring to us.

2. Use case code

The code here shows how the application layer handles use case 2.

Use the factory class of the domain layer to build Policy aggregation. If you need to pass complex objects, you need to use a type converter to convert the data class of the application layer into the entity class or value object of the domain layer.
Use domain layer services to control the order process
Send an order success message, and other domains will respond when they hear the message of interest.

4 Domain layer programming practice

1. Subcontract structure

There are a total of 5 first-level subcontracts in the domain layer.

Anticorruption is a domain anti-corruption layer, which is the encapsulation of other domains’ second-party packages when the current domain needs to know other domains or external information. From the perspective of the code level, the anti-corrosion layer can avoid complex parameter assembly and result conversion within the domain when calling an external client.

factory solves the initialization problem of complex aggregates. We design the domain model for external calls, but if the external must also use how to assemble this object, you must know the internal structure of the object. This is very unfriendly to caller development. Secondly, the domain knowledge (business rules) in complex objects or aggregates needs to be satisfied. If you let the outside assemble complex objects or aggregates by yourself, domain knowledge will be leaked to the caller code. It should be noted that this is mainly to fill in the data required by the aggregation or entity, and does not involve the behavior of the object.

Therefore, the core role of the factory here is to pull the external data needed to initialize the aggregate or entity from everywhere.

In the model is the definition of domain objects. The vo package defines the value objects used in the domain. You can see that there is such an insurance product category as PolicyProduct. In the field of insurance, we focus on a certain product related to the policy and its snapshot information. Therefore, we define a policy insurance product category here. The anti-corrosion layer is responsible for the product domain The obtained insurance product information is converted into the policy insurance product class object we care about.

According to the best practice of domain-driven design, services and repositories are not allowed in the domain object model to obtain external information. Its core concept is what a complete entity can do after initialization is completed, or what it has experienced What happens to the state after that.

repository is a storage package, which only defines the storage interface and does not care about the specific implementation. The specific implementation is left to the infrastructure layer, which embodies the idea of ​​dependency inversion.

service is a domain service, which defines some behaviors that do not belong to domain objects, but there are necessary operations, such as some process control.

2. Use case code

Note here that we have commented out a line policyRepository.save(policy);, so why should we distinguish between save and create?

save is the most correct approach in domain-driven design: if my aggregation or entity changes, the storage does not care whether it is new or updated, just save it for me. It sounds beautiful, but it is very unfriendly to relational database storage. Therefore, in our scenario, we need to violate the so-called best practice in the book. We tell the warehouse whether to create or update, and even if it is updated, which columns to update.

In addition, the best domain-driven practice is based on event-driven, and AxonFramework has a perfect implementation of it. The application layer sends out an IssuePolicyCommand instruction, and the domain layer receives the instruction. After the policy is created, a PolicyIssuedEvent is issued. The event will be monitored and persisted to in the event store. At present, it seems that this method is unlikely to be implemented here, so I won’t make more introductions.

5 Infrastructure Layer Programming Practices

1. Subcontract structure

Only the implementation of the repository is shown here, but in fact there are many things such as the second-party package for RPC calls to implement class injection. As mentioned above, the domain layer does not care about the realization of storage, and the infrastructure layer is responsible. The infrastructure layer can use relational database, cache or NoSQL as needed, and the domain layer is unaware. Here we take a relational database as an example. Both dao and dataobject can be generated using tools such as mybatis generator. Convertor is responsible for the conversion between domain objects and dataobjects.

2. Use case code

Related Articles

Explore More Special Offers

  1. Short Message Service(SMS) & Mail Service

    50,000 email package starts as low as USD 1.99, 120 short messages start at only USD 1.00

phone Contact Us