[Practice] Teach you how to implement DDD step by step

82e1212397799b22bc0746072e6b330c.gif

I. Introduction

There are many common DDD implementation architectures, such as classic four-tier architecture, hexagonal (adapter port) architecture, Clean Architecture, CQRS architecture, etc. There is no distinction between good and bad architecture, as long as you master it well, it is a suitable architecture. This article will not explain these architectures one by one, interested readers can learn by themselves.

This article will lead you to start from the daily three-tier architecture, refine and derive our own application architecture, and implement this application architecture as Maven Archetype, and finally use our Archetype to create a simple CMS project as a landing case of this article.

It needs to be clear that this article only introduces the DDD application architecture to readers, and there are many concepts that are not covered, such as entities, value objects, aggregation, domain events, etc. If readers are interested in the complete implementation of DDD, you can learn more at the end of this article .

2. Application Architecture Evolution

Many of our projects are based on a three-tier architecture, the structure of which is shown in the figure:

3103f079b0a908257e198c52546cf104.png

We talk about a three-tier architecture, why did we draw a layer of Model? Because the Model is just a simple Java Bean, which contains only the attributes corresponding to the database table, some applications will separate it out as a
Maven Module, but it can actually be merged into the DAO layer.

Next, we began to abstract and refine this three-tier architecture.

2.1 The first step, data model and DAO layer merge

Why should the data model be merged with the DAO layer?

First of all, the data model is an anemic model, which does not contain business logic and is only used as a container for loading model attributes;

Secondly, there is a one-to-one correspondence between the data model and the fields of the database table structure. The main application scenario of the data model is that the DAO layer is used for ORM, and returns the encapsulated data model to the Service layer, so that the Service can obtain the model properties to execute the business;

Finally, the Class or attribute fields of the data model usually have some annotations of the ORM framework, which are closely related to the DAO layer. It can be considered that the data model is used by the DAO layer to query or persist data. The data model is separated from the DAO layer. It is not meaningful.

2.2 The second step, Service layer extracts business logic

The following is a pseudocode of a common Service method, which includes both cache and database calls and actual business logic. The whole is too bloated, and it is impossible to start unit testing.

public class Service {


    @Transactional
    public void bizLogic(Param param) {


        checkParam(param);//校验不通过则抛出自定义的运行时异常


        Data data = new Data();//或者是mapper.queryOne(param);


        data.setId(param.getId());


        if (condition1 == true) {
            biz1 = biz1(param.getProperty1());
            data.setProperty1(biz1);
        } else {
            biz1 = biz11(param.getProperty1());
            data.setProperty1(biz1);
        }


        if (condition2 == true) {
            biz2 = biz2(param.getProperty2());
            data.setProperty2(biz2);
        } else {
            biz2 = biz22(param.getProperty2());
            data.setProperty2(biz2);
        }


        //省略一堆set方法
        mapper.updateXXXById(data);
    }
}

This is the code of a typical transaction script: first perform parameter verification, then do business through biz1, biz2 and other sub-methods, and set the results into the data model through a bunch of Set methods, and then update the data model to the database.

Since all business logic is in the Service method, the Service method is very bloated. Service needs to understand all business rules and know how to string together the infrastructure. The same rule, such as if(condition1=true), is likely to appear in every method.

Professional things should be done by professional people. Since the business logic is related to specific business scenarios, we try to extract the business logic to form a model, and let the objects of this model execute specific business logic. In this way, the Service method no longer needs to care about the if/else business rules inside, but only needs to execute the business logic through the business model and provide the infrastructure to complete the use case.

Abstract business logic into a model, such a model is a domain model.

To operate the domain model, the domain model must be obtained first, but at this time we don't care how the domain model is obtained, assuming it is obtained through the loadDomain method. Through the input parameters of the Service method, we call the loadDomain method to get a model, we let the model do business logic, and the final execution results are also in the model, and then we write the model back to the database. Of course, we don't care how to write the database, assuming it is through the saveDomain method.

After the methods of the Service layer are extracted, the following pseudocode will be obtained:

public class Service {


    public void bizLogic(Param param) {


        //如果校验不通过,则抛一个运行时异常
        checkParam(param);
        //加载模型
        Domain domain = loadDomain(param);
        //调用外部服务取值
      SomeValue someValue=this.getSomeValueFromOtherService(param.getProperty2());
        //模型自己去做业务逻辑,Service不关心模型内部的业务规则
        domain.doBusinessLogic(param.getProperty1(), someValue);
        //保存模型
        saveDomain(domain);
    }
}

According to the code, we have extracted the business logic, and the domain-related business rules are enclosed in the domain model. At this time, the Service method is very intuitive, which is to obtain the model, execute the business logic, save the model, and then coordinate the infrastructure to complete the rest of the operations.

After extracting the domain model, the structure of our project is as follows:

3eebd904d4e0262fd43a541134c4d15f.png

2.3 The third step, maintaining the domain object life cycle

In the previous step, the two methods loadDomain and saveDomain have not been discussed yet, and these two methods are closely related to the life cycle of domain objects.

For detailed knowledge about the life cycle of domain objects, readers can learn by themselves.

Whether it is loadDomain or saveDomain, we generally rely on the database, so the logic corresponding to these two methods must be related to DAO.

To save or load the domain model, we can abstract it into a component, and use this component to encapsulate the model loading and saving operations. This component is Repository.

Note that Repository is an abstraction for loading or saving domain models (here refers to aggregate roots, because only aggregate roots will have Repositories), and the details of domain model persistence must be shielded from the upper layer, so the input or output parameters of its methods , must be a basic data type or a domain model, not a data model corresponding to a database table.

The following is the pseudocode of Repository:

 
  
public interface DomainRepository {


    void save(AggregateRoot root);


    AggregateRoot load(EntityId id);
}

Next we have to consider where to implement DomainRepository. Since DomainRepository is related to the underlying database, but we have not introduced the Domain package into the DAO layer, the DAO layer naturally cannot provide the implementation of DomainRepository. We initially consider whether DomainRepository can be implemented in the Service layer.

However, if we implement DomainRepository in Service, we will inevitably need to operate the data model at the Service layer: query the data model and then encapsulate it into a domain model, or convert the domain model into a data model and save it through ORM. This process should not be the concern of the Service layer of.

Therefore, we decided to directly introduce the Domain package in the DAO layer, and provide the implementation of the DomainRepository interface in the DAO layer. After the DAO layer queries the data model, it is encapsulated into a domain model for DomainRepository to return.

After this adjustment, the DAO layer no longer returns the data model to the Service, but returns the domain model, which hides the details of database interaction. We also change the name of the DAO layer to Repository.

Now, the architecture diagram of our project looks like this:

06bb7670815e4ae728fb426276f133fd.png

Since the data model is an anemic model, it has no business logic itself, and only the Repository package will be used, so we will merge it into the Repository and will not list it separately.

2.4 The fourth step, generalization and abstraction

In the third step, our architecture diagram is already very similar to the classic four-layer architecture, and we will generalize and abstract some layers.

  • Infrastructure

The Repository storage layer actually belongs to the infrastructure layer, but its responsibility is persistence and loading aggregation. Therefore, we renamed the Repository layer as infrastructure-persistence, which can be understood as the persistence package of the infrastructure layer.

The reason why the format of infrastructure-XXX is adopted for naming is that Infrastructure may have many packages, each providing different infrastructure support.

For example: for general projects, it may be necessary to introduce caching, so we can add another package named infrastructure-cache.

For external calls, DDD has the concept of anti-corrosion layer, which isolates the external model through the anti-corrosion layer to avoid polluting the domain model of the local context. We use gateways to encapsulate access to external systems or resources (see "Enterprise Application Architecture Patterns", 18.1 Gateways (Gateways) for details), so this layer is called infrastructure-gateway.

Note: The facade interfaces of the Infrastructure layer should be defined in the Domain layer first, and the input and output parameters of its methods should be domain models (entities, value objects) or basic types.

  • User Interface

The Controller layer is actually the user interface layer, that is, the User Interface layer, which we refer to as ui in the project. Of course, many developers may think that calling UI seems awkward, thinking that UI is a graphical interface designed by UI designers.

There are many names for the Controller layer, some are called Rest, and some are called Resource. Considering that our layer not only has a Rest interface, but also a series of Web-related interceptors, I generally call it Web. Therefore, we renamed it ui-web, the web package for the user interface layer.

Similarly, we may have many user interfaces, but they provide external services through different protocols, so they are divided into different packages.

If we have externally provided RPC services, the package in which the service implementation class resides can be named ui-provider.

Sometimes the introduction of a middleware will increase both Infrastructure and User Interface.

For example, if you introduce Kafka, you need to think about it. If it is to provide calls to the Service layer, such as sending a message to notify the downstream after the logic is executed, then we will add another package infrastructure-publisher; if it is to consume Kafka messages, then call the Service layer to execute For business logic, it can be named ui-subscriber.

  • Application

So far, the Service layer has no business logic at present, and the business logic is executed in the Domain layer. The Service only coordinates the domain model and the infrastructure layer to complete the business logic.

Therefore, we renamed the Service layer to the Application Service layer.

After the fourth step of abstraction, the architecture diagram is as follows:

9bfc1a6684cfc410c13b83a70316cca5.png

2.5 The fifth step, the complete package structure

We continue to organize the packages that appear in the fourth step. At this time, we still need to consider a question, where should our startup class be placed?

Since there are many User Interfaces, it is not appropriate to place the startup class in any User Interface, nor is it appropriate to place it in the Application Service. Therefore, the startup class should be stored in a separate module. And because the name application is occupied by the application layer, the module where the startup class is located is named launcher. There can be multiple launchers in a project, and the User Interface is referenced as needed.

Adding the startup package, we get the complete maven package structure.

The package structure is shown in the figure:

7b2d8b7fb593eba5cff0ba24f0965ab8.png

So far, the overall structure of the DDD project is basically finished.

2.6 Thoughts after refining

In the architecture diagram obtained through the refinement of the previous five steps, all four layers of the classic four-layer architecture appear, and they look very similar to the hexagonal architecture. Why is this?

In fact, whether it is a classic four-tier architecture, a hexagonal architecture, or a neat architecture, they are all descriptions of system applications. The descriptions may focus on different things, but they describe the same thing. Since it is describing the same thing, it is only natural that it looks similar. It is impossible to just change the description method, and the system will fundamentally change.

For any application, it can be regarded as a process of "input-processing-output".

"Input" link: the ability to expose the domain to the outside world through a certain protocol. These protocols may be REST, RPC, MQ subscribers, WebSocket, or some task scheduling tasks;

"Processing" link: The processing link is the core of the entire application, which represents the core capabilities of the application and is where the value of the application lies. The application executes business logic in this link. The anemia model is processed by the Service, and the hyperemia model is performed by the model. business processing.

In the "output" link, after the business logic is executed, the results are output to the outside.

No matter what architecture we adopt, the core of the application it describes is this process, and there is no need to use a mechanical application architecture.

Just as the "Diamond Sutra" said: All conditioned dharmas are like dreams and bubbles, like dew and lightning, and should be viewed as such; all appearances are false; if you see all appearances and non-appearances, you will see the Tathagata.

Two, ddd-archetype

3.1 Introduction to Maven Archetype

Maven Archetype is a Maven plug-in that can help developers quickly create the infrastructure of the project, greatly reducing the time and effort required by developers when creating projects, and can ensure the consistency and reusability of the project structure, thereby improving code quality and maintainability.

When we introduced the DDD application architecture, we introduced the structure of the project. We divide the project into multiple Maven Modules. If each project is manually created once, it will be cumbersome work and it will not be conducive to the unification of the project structure.

We use Maven Archetype to create the scaffolding for DDD project initialization, so that it can fully implement the application architecture of the fifth step above during initialization.

3.2 Use of ddd-archetype

3.2.1 Project introduction

ddd-archetype is a prototype project of Maven Archetype. After we clone it locally, it can be installed as Maven Archetype to help us quickly create DDD project scaffolding.

Project link: https://github.com/feiniaojin/ddd-archetype

3.2.2 Installation process

The following will take IDEA as an example to show the installation and use process of ddd-archetype. The main process is:

Clone project-->archetype:create-from-project-->install-->archetype:crawl

3.2.3 Clone project

Clone the project locally:

git clone https://github.com/feiniaojin/ddd-archetype.git

Just use the main branch directly, and then use IDEA to open the project

0912f841497155e5a3dcfb509d32aa8c.png

3.2.4 archetype:create-from-project

Configuration Open the run/debug configurations window of IDEA, as follows:

fc40264f066bec7ba593acb8b59574d4.png

Select add new configurations, the following window pops up:

05203e7b6b3ddb745583b11a31daff46.png

Among them, the values ​​of the signs 1 to 4 in the above figure are:

Mark 1 - select the "+" sign;

Mark 2 - select "Maven";

ID 3 - The command is:

 
  
archetype:create-from-project -Darchetype.properties=archetype.properties

Note that the commands added in IDEA do not need to add mvn by default

Logo 4 - select the root directory of ddd-archetype

After the above configuration is complete, click to execute the command.

3.2.5 install

After the previous step is completed and no error is reported, configure the install command.

cc22f1c28d0609b752f69a0ece0ab145.png

Among them, the values ​​of the signs 1 to 2 in the above figure are:

Identifier 1 - the value is install;

Flag 2 - the value is the result of the previous step, and the path is:

 
  
ddd-archetype/target/generated-sources/archetype

After the install configuration is complete, click Execute.

3.2.6 archetype:crawl

The install is executed without error, and then configure the archetype:crawl command.

9ae194ac3c7077ca325a4f2d7ce21348.png

Among them, the value in ID 1 is:

 
  
archetype:crawl

After the configuration is complete, click Execute.

3.3 Initialize the project using ddd-archetype

  • When creating a project, click manage catalogs:
    dfed3327fc69a8d7540781a897cd387b.png

  • Add the archetype-catalog.xml in the local maven private server to the catalogs:

697e127ad1e486a6b03fdf0e52c6701e.png

Added successfully, as follows:

97cfac3ac9101bf9a645017c1bd41b88.png

When creating a project, select the local archetype-catalog, and select ddd-archetype, fill in the project information and create the project:

1abd61060b085d9563a7d38a6d5ad1c4.png

After the project is created:

aece31b824f574a1c9bb057480cdb1f7.png

4. Code case

This article provides a supporting code case, which implements a simple CMS system using DDD and the application architecture of this article. The case project adopts the method of separating the front and back ends, so there are two code bases of the back end and the front end.

4.1 Backend

The back-end project is created using the ddd-archetype in this article, which realizes some of the functions of CMS and implements some of the concepts of DDD.

GitHub link: https://github.com/feiniaojin/ddd-example-cms

52259b9329297a8e618d749b7eb10bbb.png

The implemented DDD concepts are: Entity, Value Object, Aggregate Root, Factory, Repository, CQRS.

Technology stack:

  • Spring Boot

  • H2 in-memory database

  • Spring Data JDBC

There is no external middleware dependency, and the clone can be compiled and run locally, which is very convenient.

4.2 Front end

The front-end project is developed based on vue-element-admin. For detailed installation methods, see the README of the code base.

GitHub link: https://github.com/feiniaojin/ddd-example-cms-front

376c0be72a5ef3d98d1fe09334e7755a.png

4.3 Run screenshot

a08b9e8a3543334933490c3d90dccd1a.png

5. Summary and further study

This paper refines the anemia three-tier architecture, deduces an application architecture suitable for our landing, and implements it as a Maven Archetype to apply to actual development. However, the application architecture is only a knowledge point for implementing DDD, and a system must be used to fully implement DDD. Master knowledge points such as bounded context, context mapping, congestion model, entity, value object, domain service, Factory, Repository, etc.

-end-

Guess you like

Origin blog.csdn.net/jdcdev_/article/details/130998640