A preface.
Long time no see. The department has had a big release in recent weeks, so I haven’t got around to blogging. As version iteration, function more and more complex, system maintenance and function iteration more and more difficult. Some time ago, my leader asked me if I could do some work on the architecture and migrate the architecture to DDD. Ha ha ha ha, when I heard the words of the moment came to spirit. To be honest, I came into contact with DDD from some friends of big factory last year. I also read related articles and open source projects from time to time, but I have no chance to implement DDD in actual work. This is an opportunity to get started.
This paper focuses on how MVC three-tier architecture migrates DDD, so it will first make a brief concept introduction to DDD (refined domain concepts are not expanded too much), and then make suggestions for migration of MVC three-tier architecture to DDD. If have wrong place, welcome to point out, common progress.
This article is especially grateful to LilPilot for their valuable advice on DDD landing solutions.
What is DDD
2.1 introduction of DDD
I believe those of you who know DDD have heard the official introduction on the Internet:
-
Domain Drive Design
-
Hexagonal architecture model
-
A common language that domain experts, designers, and developers can understand as a tool for communicating with each other
-
.
Said more or less abstract point, listen to your words, such as listen to words, ha ha ha
In my opinion, in the MVC three-tier architecture, we get the requirements and read the requirements before we do the function development. The first step is to design the table structure and design the dao, service, and controller layer by layer. For the product or user needs to do a layer of self-understanding transformation.
As we all know, talent is the biggest bug in the system.
After so many layers of transformation of user requirements are put forward, especially the transformation of R&D requirements in the layer of database structure, the business is transformed into subjective behavior. Once the business boundary is blurred, it is not considered completely. A lot of logical additions pile up in the code layer implementation and become increasingly difficult to maintain, full of if/else, legendry like code.
All DDD does is
- Eliminate information asymmetry
- The bottom-up design approach in the conventional MVC three-tier architecture is reversed by taking business as the leading and dividing business domains from top to bottom
- Divide and conquer large business requirements
At this point you might be a little confused about the difference between DDD and the usual MVC architecture. This section takes the e-commerce order scenario as an example. Suppose we now want to make an e-commerce order placing demand. It involves the user selecting the goods, placing the order, paying the order, shipping the order when placing the order to the user.
In an MVC architecture, it is common to design table structures, order tables, payment tables, goods tables, etc., after analyzing the business requirements. Then write the business logic. This is the requirement of the first version, the function iteration is hungry, I can cancel the order after payment, we will return the goods ordered, is it necessary to add the table, followed by the implementation logic of the modification. As functionality iterates, code keeps piling up.
In DDD architecture, we divide business boundaries first. The core of this is the order. So the order is the aggregation logic of this business domain. Payment, product information, address and so on are all around the order and. After the order itself is determined by the attributes, the address is just the embodiment of an attribute. Once you have built the domain model of the order, the logical boundaries and warehouse design follow.
2.2. Why USE DDD
- Object-oriented design, data behavior binding, farewell to the anaemic model
- Reduce complexity, divide and conquer
- Prioritize domain models over cutting data and behavior
- Accurately communicate business rules, business first
- Code as design
- It simplifies complex business domains by demarcating boundaries and helps us design clear domain and application boundaries, making it easy to achieve uniform business and technology architecture evolution
- Share domain knowledge to improve efficiency of assistance
- Increases maintainability and readability and extends the software life cycle
- The cornerstone of Centralization
2.3.DDD terminology
Strategic design: bounded context, common language, subdomain
Tactical design: aggregation, entities, value objects, repositories, domain services, domain events, modules
2.3.1. Bounded context and universal language
A bounded context is an explicit semantic and contextual boundary within which the domain model exists. Within boundaries, all terms and phrases in common language have specific meanings.
A common language is a language that simply, clearly, and accurately describes business meaning and rules.
Take the bounded context apart. A boundary is the boundary of a domain, and a context is a semantic environment. Through the bounded context of a domain, we can communicate in a unified language within a unified domain boundary.
The domain is the problem space and the bounded context is the solution space
2.3.2. Context organization and integration patterns
Anticorruption Layer: ACL, in integrating two contexts, if both sides are in good condition, introduce an Anticorruption Layer as a translator and isolate the domain models on both sides.
2.3.3. Entity
DDD requires entities to be unique and continuously changing. This means that during the lifetime of an entity, no matter how it changes, it remains the same entity. Uniqueness is determined by unique identifiers. Variability also reflects the state and behavior of the entity itself.
Entity = Unique identifier + variability [State + Behavior]
2.3.4. Value object
An object can be used as a value object when you only care about its properties. We need to treat value objects as immutable objects, give them no identity, and try to avoid the complexity of entity objects.
Value object = Represents a value as an object to express a specific fixed concept.
2.3.5. Aggregation
Aggregation is an explicit grouping of domain objects designed to support the behavior and invariance of domain models, while acting as consistency and transactional boundaries.
We put together an aggregation of highly correlated entities and value objects with the same life cycle.
2.3.6. Polymerization root
Aggregate root entity, the most representative entity
2.3.7. Domain services
When some logic doesn’t belong to an entity, you can pull it out and put it into domain services. Ideally, there are no domain services. If domain services are not used properly, it slowly evolves back to the service layer where the logic was before.
Where domain services can be used:
- Perform a significant business operation
- Transform domain objects
- Multiple domain objects are evaluated as input parameters, resulting in a value object
2.3.8. Application services
Is the management of aggregation. The repository is between the domain model and the data model and is mainly used for the persistence and retrieval of aggregation. It isolates the domain model from the data model so that we can focus on the domain model without thinking about persistence.
We persist domain objects that are not used for the time being from memory to disk. When the domain object needs to be used again, the record can be found in the database according to the key value, and then the application can continue to use it. This is the design idea of domain object persistent storage
Factory 2.3.9.
The responsibility is to create the complete aggregation
- The factory method
- The factory class
Factories in the domain model
- Assign responsibility for creating complex objects and aggregations to a single object that does not assume responsibility in the domain model, but is part of the domain design
- For aggregations, we should create the entire aggregation at once and ensure that its immutable conditions are met
- The factory is only responsible for creating models and has no other domain behavior
- The main responsibility of an aggregation root containing factory methods is to complete its aggregation behavior
- Using the factory approach on aggregation is a better way to express the general language than using constructors
Factory method in aggregate root
- The factory method in the aggregate root represents the domain concept
- The factory approach can provide safeguards
Factories in domain services
- When integrating bounded contexts, domain services act as factories
- The interfaces of domain services are placed in the domain model and the implementation is placed in the infrastructure layer
2.3.10. Resource Database [Storage]
Is the management of aggregation. The repository is between the domain model and the data model and is mainly used for the persistence and retrieval of aggregation. It isolates the domain model from the data model so that we can focus on the domain model without thinking about persistence.
We persist domain objects that are not used for the time being from memory to disk. When the domain object needs to be used again, the record can be found in the database according to the key value, and then the application can continue to use it. This is the design idea of domain object persistent storage
2.3.11. Event model
Domain events are an extremely important part of a domain model to represent events that occur in the domain. Ignore unrelated domain activities while making clear what the domain expert is tracking or wants to be notified of, or is associated with state changes in other model objects
Domain events = event publishing + Event storage + Event distribution + event handling.
For example, after placing an order, increase the demand for points and free coupons for users. If you write code like a waterfall stream. One logic call after another, and then different users give different things, and the logic becomes very long and smelly. A better way is to release domain events after the user places an order successfully, and the points aggregation and coupon aggregation monitor the domain events of the order release for processing.
2.4.DDD Architecture Overview
Against 2.4.1. Architecture diagram
Strictly hierarchical architecture: a layer can only be coupled to a layer directly beneath it.
Loosely layered architecture: Allows coupling of the upper layer to any lower layer
Dependency inversion principle
High-level modules should not depend on low-level modules; both should depend on abstractions
Abstraction should not depend on implementation details; implementation details should depend on interfaces
In a nutshell, it’s interface oriented programming.
According to the DIP principle, the domain layer can no longer depend on the infrastructure layer, and the infrastructure layer can complete the decoupling of the domain layer through the implementation of injection persistence. The new layered architecture model using dependency injection principle becomes as follows:
From the top down
The first layer is the user interaction layer. Web requests, RPC requests, MQ messages and other external inputs are regarded as external input requests and may be modified to internal business data.
The second layer is the business application layer, which, unlike services in MVC, stores a lot of business logic. But in the implementation of application services (in terms of function points), it takes care of choreography, forwarding, validation, and so on.
The third layer is the domain layer, in which the aggregation root is the highest person. The core logic is embodied in the aggregation root [congestion model]. If the current aggregation root cannot process the current logic and requires the cooperation of other aggregation roots, a layer of domain services will be wrapped outside the aggregation root to realize the logic. Ideally, of course, there would be no domain services.
The fourth layer is the infrastructure layer, providing technical implementation support for other layers
I believe you also see a line here where the application service layer calls the repository layer directly. What does this line mean?
The establishment of domain model is to control the business boundary of data addition, deletion and modification. As for data query, different reports and different pages need to display data aggregation without strong business domain, so CQRS is commonly used for query logic processing.
2.4.2. Hexagonal Architecture (Ports and adapters)
For each external type, there is an adapter for it. External interfaces interact internally through application-layer apis.
For the ports and adapters on the right, we can think of the repository as persistent adapters.
2.4.3. Separation of command and query responsibilities –CQRS
- A method of an object that modifies the state of the object is a Command. It should not return data and be declared void.
- A method on an object that returns data is a Query and should not modify the object state either directly or indirectly.
- Aggregation has only Command methods, not Query methods.
- The library only has add/ Save /fromId methods.
- The domain model is divided into two parts, the command model (write model) and the query model (read model).
- Client and query processor Client: Web browser, desktop applications, etc. Query processor: A simple component that only knows how to perform basic queries to a database. The query processor is not complex and can return DTO or other serialized result sets, depending on the system state
- Query model: A de-normalized data model that does not reflect domain behavior and is used only for data display
- Client and command processor aggregation is the command model command models have well-designed contracts and behaviors, and matching commands to the corresponding contracts is straightforward
- The event subscriber updates the query model
- Process query models with final consistency
2.4.4. Event-driven architecture
-
Event-driven architectures can be integrated into hexagon architectures, which is better integrated, as well as traditional hierarchical architectures
-
Piping and filter
-
Long time processing
- Active pull status check: race conditions between timer and completion events may cause failure
- Passive check: After receiving an event, check whether the status record times out. Problem: If for some reason, the event never expires
-
The event source
- For each aggregated command action, at least one domain event is published, representing the execution result of the action
- Each domain event will be saved to the event store
- When an aggregation is retrieved from the repository, it is rebuilt based on the events that occurred on the aggregation, which are played back in the same order as they were generated
- Aggregate snapshot: Stores a snapshot of the aggregated status when an event occurs. To reduce the time it takes to replay events
Three. Landing sharing
3.1. Event storm
EventStorming is a Workshop approach that works like brainstorming. DDD predates EventStorming by more than 10 years, and EventStorming is not designed for DDD alone, but as an independent, collaborative, event-based approach to quickly analyze and model complex business domains.
For the business logic in the old system, divide the business logic aggregation according to the above method
For example, the car purchase process under the scene of the electric shopping mall event storm
3.2. Scene identification
Event storm After service aggregation, identify scenarios and divide tiers
3.3. Package module division
3.4. Migration instructions
3.4.1 track. The storage layer
Using the Repository pattern in our everyday code is a simple, yet highly rewarding thing to do. The biggest benefit is that it can be completely decoupled from the bottom layer, so that the upper layer of the business can quickly grow by itself.
Take the current reverse model for example, existing
- OrderDO
- OrderDAO
The Repository pattern can be implemented incrementally through the following steps:
- Generate the Order entity class. The initial fields can be identical to those of OrderDO
- Generate the OrderDataConverter, which is basically 2 lines of code using MapStruct
- Write unit tests to ensure that the conversion between Order and OrderDO is 100% correct
- Generate the OrderRepository interface and implementation to ensure the correctness of OrderRepository through single test
- Change the place where OrderDO is used in the original code to Order
- Change the original code to use OrderRepository where OrderDAO is used
- Ensure consistency of business logic through single test.
It is important to note that the dao and Mybatis operations in a repository should not have this method. The dao and Mybatis operations in a Repository should not have this method. The dao and Mybatis operations in a Repository should not have this method.
Extreme DDD advocates demand that only the save and byId aggregation methods exist in Repository. This of course depends on the actual business scenario, but it is still recommended to keep only these two methods. Other business requirements query aggregation methods open a separate queryRepository for different data query aggregation and page data display. Ensure that the data add, delete and modify entry is unique
3.4.2. Isolate third-party dependencies – Adapter
The idea is the same with repository, using payApi as an example:
- Create an Adapter package in domain
- Create a PayAdapter interface
- The implementation of the Adapter is defined in the infrastructure, transforming the internal model and the external model, calling the Pay interface, and returning the internal model DTO
- Change the place where RPC is called in the original business to Adapter
- Single test compares RPC and Adapter to ensure correctness
3.4.3. Extract technical components
Also in line with the idea of hexagonal architecture, mqProducer, JsonUtil and other technical components are defined interfaces in domain and implemented in infrastructure. The replacement steps are similar to Adapter.
3.4.4. Business process modularization – Application
If it is a capability chain project, the capability chain service can be an application. If the business logic in the original service is mixed, even parameter assembly is embodied in the service. Then the logic needs to be grouped into the aggregation root, which cannot be fully covered by the current aggregation root, to prevent it from being reflected in the domain model. In the application service layer is the embodiment of the capability chain.
3.4.5. Explicit CQRS parameters
Ability chain project, define command, query package, through the ability chain to reflect command, query, including inherit CommandService, QueryService, Po inherit CommandPo, QueryPo
CQRS is not a capability chain project. The command, Query package, parameter, and class names are defined in the application to reflect CQRS.
3.4.6. Strategic design – Domain
Redesign aggregations and entities that might be different from the existing model, and if the model is not that different, migrate the logic within the capability point directly to the entity,
Mybatis can be replaced by jPA. If you use JPA, the DAO layer can be eliminated. At this point, most of the classes in the original BIZ have been migrated.
Questions that may exist in the process of migration
There are bound to be some areas that are more or less unclear during the migration process, so HERE are some of the issues I encountered during the migration process.
1. Actual application scenarios of domain services and application services
Application service: Understood as an orchestration of various methods, it does not deal with the task business logic, such as order number changes resulting in price changes, which is embodied in the aggregation root, and the application service is only responsible for invocation.
Domain services: Aggregators cannot handle this logic completely, such as the payment step. Order aggregators cannot pay, so they put a layer of domain services on order aggregators to implement the payment logic in domain services. Application services invoke domain services.
2. What are the business boundaries defined by the aggregation root?
Business logic is not divided by table structure data, and a business body is a business. For example, an order involves goods, shipping address, shipping address, personal information, etc. Defined within the aggregation as entity and value objects.
3. When a command modifies an aggregation, it associates the changes to other associated tables to determine whether the associated table is an aggregation
An associative table is not an aggregate, but a value object
4. Whether adapter must be used when RPC is called by the application service layer
Yes, it must be used to shield external dependencies from the current business logic. Imagine that you now need to call the RPC interface, return 100 fields, and take 50 of them. After some time, the caller changes the return of the interface logic and the data is contained in the entity. And you have a lot of places calling this interface, so it changes a lot. But with the adapter layer, you just define the data structure that your business needs, the rest of the business doesn’t need to worry about, and the completely new adapter can load the data that you want from RPC into.
5. If the internal logic of the aggregation root cannot be handled independently, if it is put into the domain service, whether the domain service or application service of other aggregation root can be called to join the form of business strong binding, and how to do if the service service or storage needs to be called inside the aggregation root.
You can do that, but the logic has to be as cohesive as possible.
6. Event notification mode, for example, is it in the form of strong binding, whether it is still in this mode, or whether it is a logical equalization event notification unrelated to the aggregation root
Strongly dependent forms of logical orchestration, such as the application of service orchestration when orders are aggregated and modified depending on payment results. Order payment after sending coupons, points and other weakly coupled way to go event notification mode.
7. Bounds of aggregate roots, PO,DTO, and VO
Po is a one-to-one mapping of the database table structure.
Dto is a data carrier, anemia model, which only loads data.
Vo is the packaging when the DTO structure does not meet the requirements of front-end display.
The aggregation root is the aggregation data of one or more Po, of course, not only the combination of Po, but also the value object data, congestion model, cohesive core business logic processing.
8. Does the query logic have a separate repository or can it be stored in an aggregate root repository
Open a separate warehouse. The storage of the aggregation root should be the aggregation root of the query result and the parameters of the save, but the business queries may be diverse, and the data presented to the front end may not all be composed of the fields of the aggregation root, and the query will not cause irreversible consequences to the database. Therefore, the query logic processing is set up separately and the CQRS mode is adopted.
9. The returned result data consists of multiple interfaces. Is it directly combined at the application service layer
No, you need to define an Assember class that separately processes the various externally dependent data.
10. Do delete, INSERT, update all of the save methods?
The delete method is handled separately, and a delete method can be added. In theory, the INSERT and update methods need to keep the same method.
11. What if the query logic involves modifying the aggregate root
Simple query logic goes directly to the storage, complex logic goes to the application service, in the application service for aggregation root data modification.
12. Where to place logically processed services
If the logic is used only for one aggregation, it is placed in the corresponding domain service, and if the logic processing is used by multiple aggregations, it is defined as a separate service as a utility class.
In live.
This article provides a modest conceptual and architectural introduction to DDD. After that, it introduces the migration of MVC three-tier architecture which is still the most used to DDD scheme, and finally makes a question and answer for some details that may be encountered.
Vi. Special thanks
lilpilot
Seven. Contact me
If there is an incorrect place in the article, welcome to correct, writing the article is not easy, point a thumbs-up, yao yao da ~
Nailing: louyanfeng25
WeChat: baiyan_lou