Microservice architectures such as clean architecture, CQRS, hexagonal architecture are all aimed at “high cohesion and low coupling”. What about DDD hierarchies?
1 DDD Layered architecture
1.1 Basic principles of Layered Architecture
Each layer can only be coupled to the layer below it.
1.2 Classification of layered architectures
- Strict Layers Architecture
A certain level can only be coupled to its immediate lower level, the slave of my slave, not my slave.
- Relaxed Layers Architecture
Allow any upper layer to be coupled to any lower layer. Because the user interface layer and application services often need to deal with the infrastructure, many systems have this architecture.
Lower layers can sometimes be coupled to higher layers, but only in Observer mode or Mediator mode scenarios. Lower layers must not have direct access to higher layers. For example, when using the Mediator pattern, a higher level might implement a lower level interface and then pass the implementation object as a parameter to the lower level. When a lower level calls the implementation, it does not know where the implementation came from.
1.3 Layered Architecture Evolution
1.3.1 Traditional four-tier architecture
Separate domain models from business logic and reduce dependence on infrastructure, user interfaces, and even application-layer logic because they are not business logic. Divide a system into layers, each of which should have good cohesion and depend only on layers lower than itself.
The infrastructure layer of a traditional layered architecture is at the bottom, where persistence and messaging are located. The messages here include MQ messages, SMTP, or text messages (SMS). All components in the infrastructure layer can be thought of as low-level services of the application, with higher layers coupled to the layer to reuse the technical infrastructure. Even so, avoid direct coupling of core domain model objects to the infrastructure layer.
1.3.2 Improved four-tier architecture
Weaknesses of traditional architectures
The DDD start-up development team found that putting the infrastructure layer at the bottom had drawbacks. For example, some of the technical implementations in the domain layer are a headache at this point:
- Violate the basic principle of hierarchical architecture
- Difficult to write test cases
Why?
Use the dependency inversion design principle: low-level services (such as the infrastructure layer) should rely on interfaces provided by higher-level components (such as the user interface layer, application layer, and domain layer).
Apply the dependency inversion principle
- Layering after dependency inversion: The infrastructure layer is at the top and implements interfaces defined in all other layers
Does the dependency inversion principle really support all layers?
It is argued that there are only two layers in dependency inversion: the top and bottom, with the top layer implementing the abstract interface defined at the bottom. Therefore, the infrastructure layer in the figure above will be at the top, while the user interface layer, application layer, and domain layer should be at the same level and all at the bottom. You may reserve your opinion on this. Explain this in more detail in the Hexagonal [Cockburn] or port and adapter architecture.
2. Responsibilities of each layer
2.1 User Interface Layer
These include user interfaces and Web services.
Only user displays and user requests are handled and should not contain domain or business logic. One might think that since the user interface needs to validate user input, it should include business logic. In fact, user interface validation is different from validation of domain models: there should be restrictions on validation that is poorly constructed and only for domain models.
If the user interface uses objects in the domain model, then the domain objects are limited to data rendering presentations. In this approach, a presentation model can be used to decouple the user interface from the domain objects. Because users can be people or other systems, sometimes the user interface layer will provide apis externally in the form of open host services. The user interface layer is the direct user of the application layer.
The user interface layer is important because of the adaptation of front-end and back-end calls. If your micro service has to serve many applications or channels, and each channel has different input and output parameters, you are unlikely to develop many application services, so Facade interfaces can be useful, including the assembly and transformation of DO and DTO objects.
2.2 the application layer
It consists primarily of application services, which in theory should not have business rules or logic, but are primarily oriented towards use cases and process-related operations.
- The application layer is located above the domain layer, because the domain layer contains multiple aggregations, so it can coordinate multiple aggregation services and domain objects to complete service orchestration and composition, and cooperate to complete business.
- The application layer is also the interaction channel between microservices, which can call other microservices to complete the service composition and orchestration among microservices.
When designing, don’t put business logic at the application level that should be at the domain level. Because the huge application layer will make the domain model out of focus, over a long period of time, micro-services will evolve into the traditional MVC three-tier architecture, resulting in confusion of business logic.
Application service is in the application layer, responsible for service composition, choreography, forwarding, transformation and transmission, dealing with the execution sequence of business use cases and the assembly of results, and publishing coarse-grained services to the front end through API gateway. Security authentication, permission verification, transaction control, sending or subscribing to domain events can also be performed.
2.3 domain layer
Domain objects in the domain model include aggregation roots, entities, value objects, and domain services.
Realize the core business logic and ensure the correctness of business through various checks. The domain layer represents the business capabilities of the domain model and is used to express business concepts, business states, and business rules.
The business logic of the domain model is primarily implemented by entities and domain services:
- The entity uses the congestion model to implement all of its associated business functions.
Entity and domain services are not at the same level in implementing business logic. Domain services are used when some function in a domain cannot be implemented by a single entity or value object. They can combine multiple entities or value objects within an aggregation to implement complex business logic.
2.4 base layer
Provide common technical infrastructure services for other layers, including tripartite tools, drivers, MQ, API gateway, files, caching, DB, basic services, etc. The most common is to provide DB persistence.
The base layer contains basic services. It adopts dependency inversion to encapsulate basic resource services and decouples application layer, domain layer and base layer.
Traditional architecture due to the strong coupling of upper-layer applications to DB, many companies are most afraid of changing DB in the evolution of architecture, once the change, may need to rewrite a bunch of code. However, with dependency inversion, the application layer can keep independent core business logic through decoupling. When the DB changes, just replace the DB base service.
3 Evolution of the microservice architecture
The hierarchy of objects in the domain model is from the inside out: value objects, entities, aggregates, and bounded contexts.
Simple changes to entity or value objects generally do not result in major changes to the domain model and microservices. But a recombination or break-up of aggregates can. Because the business functions within the aggregation are cohesive, the specific business can be independently completed. The reorganization or breakup of the aggregation will inevitably lead to changes in business modules and system functions.
It can be aggregated as a base unit to complete the evolution of domain models and microservices architectures. Aggregations can be combined as a whole, recombined or split between models in different domains, or simply separate an aggregation into microservices.
Evolution of microservices architecture
Existing microservices 1: contains aggregates A, B, and C Microservices 2: microservices 3: contains aggregates D, E, and F
- If the functions of aggregation A in microservice 1 are frequently accessed and the performance of microservice 1 is affected, aggregation A can be separated from microservice 1 and used as microservice 2 to cope with high-performance scenarios
- As the business evolves, it is found that the domain model of microservices 3 changes, and aggregation D fits better into the domain model of microservices 1. Aggregate D can be migrated to microservice 1 as a whole. Be careful to define code boundaries between aggregations
- As the architecture evolved, Microservice 1 evolved from initially containing aggregations A, B, and C to include aggregations B, C, and D as a new domain model and microservice
As you can see, good aggregation and boundary design of code models allow you to quickly respond to business changes and easily implement domain models and microservices architecture evolution.
Evolution of services within microservices
Within microservices, entity methods are composed and encapsulated by domain services, which in turn are composed and encapsulated by application services. In the process of service composition and encapsulation layer by layer, you will find this interesting phenomenon.
In service design, you can’t always predict exactly which lower-level services will be assembled by how many upper-level services, so the domain layer usually only provides some atomic services, such as domain services A, B, and C. However, with the enhancement of system functions and more and more external access, application services will continue to enrich. One day you will find that domain services B and C are called by multiple application services at the same time, in roughly the same order. At this point, you can consider merging B and C, and then sinking the functions of B and C in the application services down to the domain level, evolving into the new domain services (B + C). This reduces both the number of services and the complexity of upper-level service composition and choreography.
You see, this is how services evolve as your system evolves, and eventually you’ll find that your domain model becomes more refined and more responsive to rapidly changing requirements.
How does a three-tier architecture evolve to a DDD tiered architecture?
Because of the loose coupling between layers, we can focus on the design of our own layer without having to worry about other layers or how our design affects them. Arguably, DDD succeeds in reducing layer to layer dependencies.
Secondly, the hierarchical architecture makes the program structure clear and easy to upgrade and maintain. When we modify one layer of code, as long as the interface parameters of this layer remain unchanged, other layers need not be modified. Even if the interfaces of this layer are changed, only the neighboring upper layers are affected. The modification work is small and the errors can be controlled without any unexpected risks.
So how do we move to DDD hierarchy? Take a look at this process.
Traditional enterprise applications are mostly monolithic architectures, and monolithic architectures are mostly three-tier architectures. Three-tier architecture solves the problem of complex invocation between codes within the program and unclear code responsibilities. However, such layering is a logical concept. Physically, it is a centralized architecture that is not suitable for distributed micro-service architecture.
The elements in the DDD layered architecture are similar to the three-tier architecture, except that in the DDD layered architecture, these elements are regrouped, the layers are redivided, and the interaction rules and responsibility boundaries between layers are determined.
The evolution of the three-tier architecture to the DDD hierarchical architecture occurs mainly in the business logic layer and data access layer.
The DDD layered architecture introduces Dtos at the user interface layer, providing the front end with more usable data and greater presentation flexibility.
The DDD layered architecture divides the business logic layers of the three-tier architecture more clearly, improving the situation that the core business logic of the three-tier architecture is chaotic and code changes greatly affect each other. The DDD layered architecture separates services from the business logic layer into the application layer and domain layer. The application layer can quickly respond to the changes of the front end, and the domain layer can realize the domain model.
Another important change occurs between the data access layer and the base layer. DAO is used to access data in three-tier architecture. DDD hierarchical architecture uses the Repository design pattern to access basic resources such as database, and decouples each layer of basic resources through dependency inversion.
About warehousing. The Repository itself belongs to the basic layer, but considering that an aggregation corresponds to a Repository, in order to facilitate the overall migration of the aggregation code in the future, a Repository Repository directory should be added to the aggregation directory during the design of the microservice code directory, and all the codes related to warehousing will be in this directory.
The code in this directory is separate from the rest of the aggregated business code. If you change the database in the future, just replace the code in the Repository directory. If the aggregation needs to migrate to other microservices as a whole, the repository code will migrate along with it.
Warehousing is divided into two parts: warehousing interface and warehousing implementation. The warehousing interface is placed in the domain layer and the warehousing implementation is placed in the base layer. In the original three-tier architecture, Common third-party toolkits, drivers, Common, Utility, Config and other Common and Common resource classes are unified in the basic layer.
conclusion
The DDD layered architecture consists of user interface layer, application layer, domain layer and base layer. Through these hierarchical divisions, we can define the functions of each layer of micro-services, delimit the boundaries of objects in each domain, and determine the cooperation mode of objects in each domain.
reference
- Implementing Domain-Driven Design
- DDD layered architecture: Effectively reduces layer to layer dependencies