Following up on the last article in this series, I’m back to share how Clean Architecture and DDD combine to achieve a layered Architecture.

The directory structure of the project

The figure above shows the first level directory of the project, which is divided into four parts: Application, Domain, facade and Infrastructure. The roles of these four layers are described.

Application Layer

Application corresponds to the “application layer” in DDD and also corresponds to the Application Business Rule in Clean Architecture. In terms of practice in the project, it serves as an entry point for “coarse-grained” business, or what some would like to call a Use Case. This layer should not contain complex business rules, but rather orchestrate the underlying domains (domain layer) to orchestrate the business logic. Note that this layer should only depend on the underlying domain and infrastructure layers.

Let’s look at the internal division of application:

Dto directory stores the parameter types accepted by application to the upper exposed service, which is also known as Data Transfer Object. The Service directory is the previously mentioned “coarse-grained” service interface, where all the services need to do is convert dTO objects into domain-layer domain objects according to the business logic, and invoke the methods of the related domain objects to complete the business logic. Infrastructure services are also invoked if needed. Again, this part of the service should not involve complex, core business logic.

Domain Layer

Domain is the core layer of DDD. The directory structure is as follows:

Below the domain is a directory called BC1, which represents a Bounded Context for a business in the project. The concept of A BC will be explained in more detail in a subsequent article. Below BC1 is the detailed domain layer.

The Exception directory defines domain-level exceptions, commonly known as BusinessException, which represent exceptions that violate some business logic, such as insufficient account balances. Domain objects are defined in the Model directory, and it is generally recommended to use a “congestion model” for modeling. Repository defines “repositories” for domain objects, and the concept of Repository will be covered in a future article. Service defines a “domain service” object. If model defines a business model and is a noun, then domain service is a verb.

Finally, let’s talk about the Event directory. In a complete domain model, we often need to divide multiple different Bounded contexts, but how should different BCS interact with each other? Eric Evans’ book offers centralized and different solutions, such as custom DSLS, preservatives, and more. In our specific project, we prefer to use “domain event” based interaction, which not only does not break the encapsulation between the BCS, but also removes the coupling between them. Producer is the sender of events, and handler is the object that processes events. Domain events will also be covered later.

Facade Layer

Facade is the part of the entire system that exposes services. The specific directory structure is as follows:

The system exposes services of two protocols, namely RESTful apis and Web Services, whose corresponding implementations are in the REST and WS directories respectively. The facade layer’s job is to validate the data provided by the client based on the protocol, then convert the data into dTO objects required by the Application layer and invoke the services provided by the application. There should be no business rules or logic in the facade, just the transformation of the data objects.

Infrastructure Layer

The Infrastructure layer is responsible for providing the underlying pure technical services. The directory structure is as follows:

The functions of this layer are straightforward and are familiar technical implementations without any dependence on the domain model, so I won’t repeat them here.

Questions and Reflections

The above is the layered implementation of Clean Architecture and DDD in our actual project. Its benefits are obvious. Compared with the traditional three-tier Architecture, it can take into account the isolation of domain layer better, and the whole dependency relationship is very clear and convenient for developers to understand.

Tedious data object conversion

In terms of the hierarchical architecture of the system, there are three types of data objects, namely DTO, Domain and PO(Persistence Object). In the implementation of a business function, there are often many data object conversions, and most of the time are getter and setter operations, which are very tedious.

To solve this problem, we introduced the Model Mapper as an object mapping framework, saving some redundant code. But there is another problem. Considering another concept in DDD: Aggregate, when converting from PO to Domain, all data needs to be loaded from the store in eager mode, which relatively loses the lazy loading optimization feature.

Undefined Modules and Bounded Context

In DDD theory, modules and Bounded Context are different things. In the above hierarchical architecture, the domain layer has a clear BC division, but there is no such division in other layers. The most direct phenomenon is that with the gradual increase of system functions and the increasing complexity of business rules, there will be more and more classes under DTO and Service under the application directory. Due to the lack of further abstraction, subsequent developers will find it difficult to understand.

Transaction issues introduced by domain events

After the introduction of domain events, part of the business process becomes asynchronous invocation, so the management of transaction boundaries becomes more complex and in some cases transaction consistency cannot be achieved. This certainly adds to the mental burden of developers and makes many tests more difficult. In this case, a deeper understanding of the business is required to remove or bypass transaction features from business rules as much as possible.

An increase in architectural complexity

The increased complexity of the architecture increases the cost of learning for developers. In practice, we find that developers often introduce the wrong dependencies into their code, such as domain method signatures with objects from Dtos or domain objects behind facades. A good solution to this problem would be to strengthen code review, improve developers’ understanding of layering ideas, and introduce frameworks like Unit Test Your Java Architecture-ArchUnit, Code dependencies are statically checked at CI time.

summary

This article introduces the implementation and problems of DDD layered architecture used in the project. In fact, there is no correct or suitable layered architecture for any project. It is an architect’s job to master the ideas behind it and learn how to make compromises. In the next article, I’ll show you how to implement Entity, Value Object, and Aggregate in a project.

Welcome to follow my wechat account “And get more high-quality articles”