One article to easily get you started with Domain-Driven Design (DDD) | Essentials for Architects (1)

Preface

In daily work, most of the projects we take over or maintain use a three-layer architecture, namely controller, service, and dao. In the process of use, you will encounter many problems:

  • Data-oriented modeling, process-oriented programming, no real "object-oriented"
  • Only focusing on results, not on process, the service layer often has hundreds or thousands of lines, full of process code and glue code, which are either bloated, running accounts, repetitive, or logically scattered, making it extremely difficult to maintain in the future.
  • The code coupling is serious, and layers call each other and reverse calls, which affects the whole body.
  • The code cannot reflect the business. When no one likes to write comments, as time goes by, no one will understand the business logic of the code, and no one will dare to change it.

So is there a good solution? The DDD we are going to talk about today is a good choice.

DDD

DDD, or domain-driven design , perfectly solves the above problems:

  • Domain-oriented modeling, object-oriented programming, code directly maps real-world concepts, closer to business, closer to customers
  • Domain logic is highly cohesive and conforms to Java development principles
  • Changes in technical details such as database, cache, timer, etc. have a relatively small impact on business logic and are very suitable for plug-in architecture.
  • The code is more readable and maintainable, has better support for subsequent expansion, transplantation, etc., and the layering is more scientific.

The concept of DDD is easy to find online, so I won’t go into details here.

However, although there are many articles on DDD on the Internet, most of them are theoretical knowledge, and the introduction is nothing more than some terms: strategic design, tactical design, core domain, support domain, value object, entity, aggregation... but they are not very useful for our actual implementation. Thank you for your help. Here is my implementation plan for applying DDD in SpringBoot.

Implementation plan

1. Code layering

image

Code layering

  • User interface layer : the api package in the picture (that is, the controller layer, I think the controller suffix is ​​too long...)
  • Application layer : The command mode is used here, and reading and writing are separated into two packages (command, query). If the command mode is not used, it can be merged into one service package.
  • Domain layer : domain package, using JPA (has good support for DDD)
  • Infrastructure layer : infra package, all other public components are placed here. If DIP dependency inversion is used, the implementation class is also placed here.
  • model model : model package, used to store objects transferred between different layers. I have tried placing these objects in many places, but finally found that it is better to put them under one package (to facilitate the sharing of objects when calling between services)

2. Hierarchical relationship and model transfer

image

Layering and calling relationships

3. Detailed description of layering

  • api package (controller)
@Tag(name = "用户", description = "用户")
@RestController
@RequestMapping(value = "/api/sys-user")
public class SysUserApi extends BaseApi {
    @ApiResult
    @Operation(summary = "根据ID查询用户")
    @GetMapping("/{id}")
    public SysUserVo get(@PathVariable Long id) {
        return queryExecutor.execute(new SysUserByIdQry(id));
    }
    @Pagination(total = true)
    @ApiResult
    @Operation(summary = "分页查询用户")
    @GetMapping
    public List<SysUserVo> getList(SysUserQo sysUserQo) {
        return queryExecutor.execute(new SysUserListQry(sysUserQo));
    }
    @ApiResult
    @Operation(summary = "新增用户")
    @PostMapping
    public void save(@Valid @RequestBody SysUserDto sysUserDto) {
        commandExecutor.execute(new SysUserCommonCmd(sysUserDto));
    }
}

Two command execution classes, queryExecutor and commandExecutor, are encapsulated in BaseApi. Different commands can be executed when calling the application layer without @Autowired introducing different services.

After @ApiResult adds this custom annotation, the returned results are uniformly encapsulated

@Pagination After adding this custom annotation, the paging parameters will be automatically stored in the thread variable. The paging parameters will also be automatically obtained during subsequent queries. The paging information will also be added when the returned results are unified and packaged.

Qo is the query parameter object, Dto is the command parameter object such as addition, deletion and modification. The return object is Vo. It should be noted here that Entity must not be exposed to this layer and needs to be converted to Vo and then returned.

In this layer, each method is almost a line of statements that executes commands. Generally, no business logic is performed (of course there are special cases)

  • command package
@AllArgsConstructor
public class SysDeptAddCmd implements Command<Void> {
    private SysDeptDto sysDeptDto;
    @Override
    public Void execute(Executor executor) {
        // 获取命令的接收者:领域服务
        SysDeptManager receiver = executor.getReceiver(SysDeptManager.class);
        // 对象模型转换,由DTO转为Entity,使用了MapStruct
        SysDept sysDept = SysDeptMapper.INSTANCE.toSysDept(sysDeptDto);
        // 使用JPA保存
        receiver.save(sysDept);
        return null;
    }
}

Add, delete, and modify commands, a very thin layer, as an organizer of work, with almost no business logic, calling domain services and congestion object methods

Command mode, implements custom Command interface, and generics are return values

Receive parameters through properties and constructors (using lombok annotations)

There is only one execute method in a command. The disadvantage is that a large number of command classes will be generated. Each class is equivalent to a method in the previous service class, but this conforms to the single responsibility principle.

Obtain the domain service (manager) through the executor.getRecerver method

DTO will never be penetrated into the domain layer. It needs to be converted from DTO to Entity first (the MapStruct used here is the conversion method, which will be discussed in detail later)

  • query package
@AllArgsConstructor
public class SysDeptByIdQry extends CommonQry<SysDeptVo> {
    private Long id;
    @Override
    public SysDeptVo execute(Executor executor) {
        if (id == null) {
            throw new BusinessException("部门ID不能为空");
        }
        QSysDept sysDept = QSysDept.sysDept;
        return queryFactory.select(this.fields())
                .from(sysDept)
                .where(sysDept.deleted.eq(false), sysDept.id.eq(id))
                .fetchOne();
    }
    /**
     * 部门VO映射
     *
     * @return QBean<SysDeptVo>
     */
    public static QBean<SysDeptVo> fields() {
        QSysDept sysDept = QSysDept.sysDept;
        return Projections.fields(
            SysDeptVo.class,
            sysDept.deptName,
            sysDept.orderNum,
            sysDept.id
        );
    }
}

Command mode, inherits the custom CommonQry base class (this class also implements the custom Command interface, which references the queryFactory class of QueryDSL and encapsulates the paging method), and the generic type is the return value

The query commands in the query package are roughly the same as the commands in the command package. The only difference is that the query command theoretically directly queries the database without calling the domain layer.

Since JPA is not easy to use for complex queries, it is strongly recommended to use QueryDSL (which will be discussed in detail later). The picture is a simple usage example.

  • domain package

There are many classes, and the code parts are not listed one by one:

The database table is automatically generated by the Entity class, and only the Entity class is maintained (shielding the database)

When designing Entity, use @OneToMany, @OneToOne and other annotations flexibly according to actual business (the concept of aggregate root)

The aggregate root should not be too large. In 80% of cases, an aggregate root only contains one entity (do not over-design it into a large aggregate root)

Don't use an anemic model, but be object-oriented. Methods belonging to objects should be placed in objects. However, it is not recommended to introduce the repository class into objects. Methods that need to operate the database should be written in the domain service manager.

Business logic should be written in the domain service (manager) as much as possible, and different methods should be continuously extracted and abstracted for application layer calls.

Appropriate use of domain events, JPA can use @DomainEvents annotation in Entity to send domain events

Experience

  • Through DDD, you can understand the business more thoroughly, and the code you write can better convey the customer's business demands.
  • It is very satisfying to be able to write code that is low-coupled, conforms to the principles of single responsibility, opening and closing, encapsulation, inheritance, and polymorphism.
  • Compared with traditional architecture, the amount of code in the early stage is larger, and developers invest more in the early stage:
  • Reasonable division of fields and reasonable design of entities
  • A large number of DTO, VO and other data objects
  • A large number of data object conversion methods
  • A large number of command classes
  • ...

However, unless it is a particularly simple function, for a moderately complex system, these upfront efforts are still worth it. A picture illustrates:

summary

The above briefly introduces my understanding and practice of DDD, and shows how to apply DDD in SpringBoot through actual code. I hope it can provide you with an idea.

This article is published by OpenWrite, a blog that publishes multiple articles !

OpenAI opens ChatGPT to all users for free. Voice programmers tampered with ETC balances and embezzled more than 2.6 million yuan a year. Spring Boot 3.2.0 was officially released. Google employees criticized the big boss after leaving the company. He was deeply involved in the Flutter project and formulated HTML-related standards. Microsoft Copilot Web AI will be Officially launched on December 1st, supporting Chinese Microsoft's open source Terminal Chat Rust Web framework Rocket releases v0.5: supports asynchronous, SSE, WebSockets, etc. The father of Redis implements the Telegram Bot framework using pure C language code . If you are an open source project maintainer, encounter How far can you endure this kind of response? PHP 8.3 GA
{{o.name}}
{{m.name}}

Guess you like

Origin my.oschina.net/u/5587102/blog/10141477