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-core、spring-cloud-openfeign、lombok、validation
② 应用层(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 + domain、spring-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 之间转换频繁时 |
依赖:依赖 app、spring-boot-starter-web、mybatis-plus、mysql、nacos 等
三、数据流向
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 分层(业务会持续增长)。