文章

DDD

DDD 微服务标准包结构

一、四层架构总览

1
2
3
4
5
6
catfun-xxx/                          # 微服务根目录
├── pom.xml                          # 聚合 POM
├── catfun-xxx-api/                  # ① API 层(对外契约)
├── catfun-xxx-app/                  # ② 应用层(用例编排)
├── catfun-xxx-domain/               # ③ 领域层(业务核心)
└── catfun-xxx-infrastructure/       # ④ 基础设施层(技术实现)

依赖方向:infrastructure → app → domain ← api(domain 不依赖任何层)


二、各层详细说明

① API 层(catfun-xxx-api)

定位:对外契约,定义其他服务或前端需要知道的一切数据结构。

1
2
3
4
5
6
7
8
9
10
com.catfun.xxx.api
├── dto/                             # 数据传输对象(请求入参)
│   ├── CreateXxxRequest.java
│   └── UpdateXxxRequest.java
├── vo/                              # 视图对象(响应出参)
│   └── XxxVO.java
├── facade/                          # Facade 接口(供其他微服务 Feign 调用)
│   └── XxxFacade.java
└── event/                           # 对外发布的事件定义(可选)
    └── XxxCreatedEvent.java
放什么 什么时候用
dto 请求参数对象,带校验注解 有 POST/PUT 接口时
vo 响应结果对象,只暴露需要展示的字段 有 GET 接口时
facade Feign 客户端接口 + DTO 需要被其他微服务调用时
event 事件定义(类名 + 字段) 需要跨服务异步通知时

依赖:只依赖 catfun-common-corespring-cloud-openfeignlombokvalidation


② 应用层(catfun-xxx-app)

定位:用例编排,协调领域层完成业务操作,不包含业务规则。

1
2
3
4
5
6
7
8
9
com.catfun.xxx.app
├── command/                         # 命令(写操作入参,可选)
│   └── CreateXxxCommand.java
├── query/                           # 查询(读操作入参,可选)
│   └── XxxQuery.java
├── handler/                         # Command/Query Handler(CQRS 模式,可选)
│   ├── CreateXxxCommandHandler.java
│   └── XxxQueryHandler.java
└── XxxAppService.java               # 应用服务(直接编排,简单场景)
放什么 什么时候用
command 写操作的入参封装 写操作参数多、需要区分读写时
query 读操作的入参封装 查询条件复杂、需要分页筛选时
handler 单个命令/查询的处理逻辑 采用 CQRS 模式时
XxxAppService 直接编排领域服务 MVP / 简单场景首选

两种风格选其一

  • 简单风格(推荐 MVP):直接写 XxxAppService,注入领域服务/仓储,编排调用
  • CQRS 风格(复杂场景):Command/Query 分离,每个操作一个 Handler

依赖:依赖 api + domainspring-boot-starter-validation


③ 领域层(catfun-xxx-domain)⭐ 核心层

定位:业务规则和状态的核心,不依赖任何框架。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
com.catfun.xxx.domain
├── aggregate/                       # 聚合根(管理一组实体的生命周期)
│   └── XxxAggregate.java
├── entity/                          # 实体(有唯一 ID,状态可变)
│   └── XxxEntity.java
├── valueobject/                     # 值对象(无 ID,不可变,通过属性值判断相等)
│   ├── Money.java
│   └── Address.java
├── repository/                      # 仓储接口(持久化契约,由基础设施层实现)
│   └── XxxRepository.java
├── service/                         # 领域服务(跨聚合的业务逻辑)
│   └── XxxDomainService.java
├── event/                           # 领域事件(聚合内部状态变化后发布)
│   └── XxxCreatedEvent.java
├── specification/                   # 规格模式(复杂业务规则的组合)
│   └── XxxSpecification.java
└── exception/                       # 领域异常(业务规则校验失败)
    └── XxxNotFoundException.java
放什么 什么时候用
aggregate 聚合根,控制内部实体的访问和一致性 有多个关联实体需要一起管理时
entity 有 ID 的业务对象,状态可变 需要追踪身份的业务概念
valueobject 无 ID 的不可变对象 金额、地址、坐标等描述性概念
repository 仓储接口(只有方法签名) 始终需要,定义持久化契约
service 领域服务,不属于任何聚合的逻辑 跨聚合操作、外部规则计算
event 领域事件 聚合状态变化需要通知其他模块时
specification 规格模式 复杂条件组合(如风控规则)
exception 领域异常 业务规则校验失败时抛出

MVP 最小配置:只保留 entity(聚合根)+ repository,其他按需添加。

依赖:只依赖 lombok不依赖任何框架


④ 基础设施层(catfun-xxx-infrastructure)

定位:技术实现,把领域层的接口落地。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
com.catfun.xxx.infrastructure
├── config/                          # Spring 配置类
│   └── XxxConfig.java
├── dataobject/                      # 数据对象(对应数据库表,MyBatis-Plus 实体)
│   └── XxxDO.java
├── mapper/                          # MyBatis-Plus Mapper 接口
│   └── XxxMapper.java
├── repository/                      # 仓储实现(实现 domain 层的 Repository 接口)
│   └── XxxRepositoryImpl.java
├── controller/                      # REST 控制器(HTTP 入口)
│   └── XxxController.java
├── client/                          # 外部服务调用(Feign 客户端实现)
│   └── XxxFeignClient.java
├── mq/                              # 消息队列(生产者/消费者)
│   ├── XxxProducer.java
│   └── XxxConsumer.java
├── converter/                       # 对象转换器(DO ↔ Domain ↔ VO)
│   └── XxxConverter.java
└── XxxApplication.java              # Spring Boot 启动类
放什么 什么时候用
config 配置类、拦截器、过滤器 需要自定义 Spring Bean 时
dataobject 数据库表映射对象(DO/PO) 始终需要,操作数据库
mapper MyBatis-Plus Mapper 始终需要,数据库 CRUD
repository 仓储实现类 始终需要,实现 domain 的接口
controller REST 接口 始终需要,暴露 HTTP API
client 调用其他微服务的 Feign 实现 需要调用其他服务时
mq 消息发送/监听 需要异步处理或跨服务事件时
converter 对象转换(MapStruct 或手写) DO/Domain/VO 之间转换频繁时

依赖:依赖 appspring-boot-starter-webmybatis-plusmysqlnacos


三、数据流向

1
2
3
4
5
6
7
8
9
10
11
12
13
HTTP 请求
  │
  ▼
Controller(infrastructure)         ← 接收请求,参数校验
  │
  ▼
AppService(app)                    ← 编排业务,事务控制
  │
  ▼
Domain Service / Repository(domain)← 业务规则,状态变更
  │
  ▼
RepositoryImpl → Mapper → DB(infrastructure)← 持久化

各层一句话总结

回答的问题
API 别人怎么跟我交互?(契约)
App 用户能做什么操作?(用例)
Domain 业务规则是什么?(核心)
Infrastructure 用什么技术实现?(HTTP/DB/MQ)

四、传统分层 vs DDD 分层对比

项目中存在两种分层方式:

传统分层(catfun-auth):所有代码在一个模块内,按技术角色分包

DDD 分层(catfun-user):拆成 4 个 Maven 模块,按业务职责分层

结构对比

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
传统分层(catfun-auth)                DDD 分层(catfun-user)
─────────────────────                ─────────────────────
catfun-auth/                          catfun-user/
└── com.catfun.auth                   ├── catfun-user-api/
    ├── controller/                   │   └── dto/、vo/
    ├── dto/                          ├── catfun-user-app/
    ├── entity/                       │   └── XxxAppService
    ├── handler/                      ├── catfun-user-domain/
    ├── mapper/                       │   ├── entity/
    ├── service/                      │   └── repository/
    └── vo/                           └── catfun-user-infrastructure/
                                          ├── controller/
                                          ├── dataobject/
                                          ├── mapper/
                                          └── repository/

核心差异

维度 传统分层 DDD 分层
模块数量 1 个 4 个
分包依据 技术角色(controller、service、mapper) 业务职责(API、应用、领域、基础设施)
依赖方向 无强制约束,容易循环依赖 严格单向:infra → app → domain
领域层 无独立层,entity 和 mapper 混在一起 独立模块,不依赖任何框架
业务规则 散落在 service 中 集中在 domain 层
可测试性 需要启动 Spring 容器 domain 层可纯单元测试
替换数据库 需要改 entity 和 mapper 只需替换 infrastructure 层

DDD 分层的好处

1. 业务逻辑与技术实现分离

传统分层中,entity 既是业务对象又是数据库映射对象,加了 @TableName@TableId 等注解,业务逻辑和数据库耦合在一起。DDD 分层中,domain 层的 UserProfile 是纯 Java 对象,UserProfileDO 才是数据库映射,两者互不干扰。

2. 依赖方向可控

传统分层没有模块边界,controller 可以直接调 mapper,service 可以互相调用,容易形成网状依赖。DDD 通过 Maven 模块强制约束依赖方向:domain 不依赖任何层,app 只依赖 domain,infra 依赖 app。编译期就能发现违规依赖。

3. 领域层可独立测试

domain 层不依赖 Spring、MyBatis 等任何框架,可以写纯 Java 单元测试,不需要启动容器,测试速度快、稳定性高。

4. 技术选型可替换

如果将来要从 MyBatis-Plus 换成 JPA,只需重写 infrastructure 层,domain 层和 app 层完全不用改。传统分层中 entity 和 mapper 耦合,替换成本高。

5. 业务规则集中管理

传统分层中业务规则散落在各个 service 方法里,难以复用。DDD 将业务规则集中在 domain 层,通过领域服务、规格模式等方式组织,逻辑更清晰。

什么时候用哪种

场景 推荐方式
简单 CRUD、内部工具、原型验证 传统分层
核心业务模块、长期维护的系统 DDD 分层
需要被其他服务调用的模块 DDD 分层(api 模块独立)
业务规则复杂、需要频繁变更 DDD 分层

猫趣平台的选择:auth 模块用传统分层(登录注册逻辑简单),user 等核心业务模块用 DDD 分层(业务会持续增长)。

本文由作者按照 CC BY 4.0 进行授权