用 Spring 思维快速上手 DDD——以 Kratos 为例的分层解读
用 Spring 思维理解 DDD —— 以 Kratos 为参照
在此前的学习工作中,使用的开发框架一直都是 SpringBoot,对 MVC 架构几乎是肌肉记忆:Controller 接请求,Service 写业务逻辑,Mapper 操作数据库,这套套路早已深入骨髓。
最近开始学习 Go,为了写微服务,接触到了 Kratos 框架,也顺带深入了解了 DDD(领域驱动设计)。一开始,我的第一反应是——“这不就是 MVC 换个说法吗?”
但越学越发现,虽然 DDD 和我们熟悉的 Spring MVC 分层在形状上很像,但它对“业务逻辑该放哪、数据访问该放哪”划分得更严格,还用工程化的方式强制执行这些规则。
这篇文章,我就用自己熟悉的 Java Spring 思维,把 DDD 的分层思想翻译成 Spring 语言,再对照 Kratos 在 Go 里的实现,帮你快速搞懂它们的异同。
往期博客
Go语言新手村:轻松理解变量、常量和枚举用法
Go 语言中的结构体、切片与映射:构建高效数据模型的基石
1. Spring 的常见分层
在 Java 项目中,最典型的分层是:
Controller → Service → Mapper → DB
- Controller:接收请求、参数校验、调用 Service
- Service:执行业务逻辑(有时混合数据访问)
- Mapper:访问数据库(MyBatis Mapper / JPA Repository)
这种分层没错,而且配合团队自律,也能写出很干净的项目结构。但现实是:
- Service 往往既写业务规则,又写 SQL 条件拼接;
- 业务规则分散在 Service、Mapper 甚至 Controller 中;
- 一旦底层数据访问方式变化(换数据库、换 RPC),改动会影响大量上层代码。
2. DDD 的目标:边界清晰、职责单一
DDD 要解决的,就是让业务逻辑与技术实现彻底解耦,做到:
- 业务规则集中在领域模型中,贴着数据维护不变式;
- 数据访问细节被隔离在仓储实现中,随时可替换;
- 跨聚合的编排逻辑集中在应用服务(Usecase)中,清晰可测。
DDD 的典型分层:
接口层(Controller / API)→ 应用层(Usecase / Application Service)→ 领域层(Entity / Aggregate / Domain Service / Repository 接口)→ 基础设施层(Repository 实现 / 外部服务实现)
3. 用 Spring 语言对照 DDD 分层
DDD 层次 | Kratos 对应 | Spring 对应 | 职责 |
---|---|---|---|
接口层(API) | service | Controller | 参数校验、鉴权、DTO ↔ Domain 转换 |
应用层(Usecase) | biz | Service(理想状态) | 编排业务流程、事务控制、调用多个领域对象或外部服务 |
领域层 | repo | 实体类、领域服务接口 | 维护业务不变式、暴露行为方法、定义仓储接口 |
基础设施层 | data | Mapper / Feign 实现层 | 数据持久化、调用远程服务、模型映射(PO ↔ Domain) |
4. 核心理念对比
4.1 Repository
- Spring 常见写法:Mapper/Repository 接口直接返回 PO(数据库模型),业务层可能直接用它判断。
- DDD 写法:仓储接口定义在领域层,返回的是领域对象(封装了业务行为的方法),由基础设施层实现。
4.2 业务规则的位置
- 常见误区:在 Service 里写
if (user.getEnabled() == 0) throw ...
。 - DDD 方式:在领域对象中提供
ensureActive()
,领域对象自己决定什么是可用。
4.3 应用服务(Usecase)
- 职责:一次完整业务流程的编排、事务边界、调用多个仓储接口、发布领域事件。
- 不做的事:不直接写 SQL,不去判断
user.getEnabled()
,不实现底层细节。
5. 案例:锁单流程
下面的锁单方法只是为了演示 DDD 分层思路,并不具备生产可用性。在真实系统中,锁单流程往往要面对并发控制、一致性等复杂问题。
5.1 常见 Spring 写法(简化版)
@Service
@AllArgsConstructor
public class OrderService {private final OrderMapper orderMapper;private final UserMapper userMapper;private final StockMapper stockMapper;private final WalletMapper walletMapper;@Transactionalpublic String lockOrder(String userId, String sku, int count) {// 校验账户是否可用if (!userMapper.ensureActive(userId)) {throw new BizException("用户不可用");}// 校验库存if (!stockMapper.hasStock(sku, count)){throw new BizException("商品库存不足");}// 检查余额if (!walletMapper.hasBalance(userId, count * price)){ throw new BizException("余额不足");}// 预减库存stockMapper.reserveStock(...);// 冻结余额walletMapper.freezeBalance(...);// 添加一条订单记录orderMapper.insertOrder(...);return orderId;}
}
缺点:业务判断和数据访问混杂
5.2 DDD 写法(Spring 风格)
领域层(实体类 + 仓储接口)
// 聚合根:用户
public class User {public void ensureActive() { /* 校验用户有效性 */ }
}// 聚合根:库存
public class Stock {public void reserve(int count) { /* 校验库存并预留 */ }
}// 聚合根:钱包
public class Wallet {public void freeze(double amount) { /* 校验余额并冻结 */ }
}// 仓储接口
public interface UserRepo { User load(String id); void save(User u); }
public interface StockRepo { Stock load(String sku); void save(Stock s); }
public interface WalletRepo { Wallet load(String uid); void save(Wallet w); }
public interface OrderRepo { void save(Order o); }
应用层(用例编排)
@Service
@AllArgsConstructor
public class LockOrderUsecase {private final UserRepo userRepo;private final StockRepo stockRepo;private final WalletRepo walletRepo;private final OrderRepo orderRepo;public void lock(String userId, String sku, int qty, double price) {// 调用仓储接口,获取领域对象User user = userRepo.load(userId);Stock stock = stockRepo.load(sku);Wallet wallet = walletRepo.load(userId);// 业务编排user.ensureActive();stock.reserve(qty);wallet.freeze(qty * price);// 持久化数据orderRepo.save(new Order());stockRepo.save(stock);walletRepo.save(wallet);}
}
- 业务逻辑在领域对象:例如
ensureActive
、reserve
、freeze
方法,他们只关心业务实现,不关心数据是怎么获取的 - 数据访问集中在仓储实现:Repo 不做业务判断,取出来交给实体自己判断
- 应用层清晰编排流程:用 Repo 加载实体 → 调用实体方法做判断/修改 → 再通过 Repo 保存变更
6. Kratos 如何落地
Kratos 在 Go 里用目录结构 + wire 静态注入强制执行这种依赖方向:
service(接口层) → biz(用例+仓储接口) → data(仓储实现) → DB/远程服务
biz
中不能 import ORM/HTTP 客户端等具体库;data
中实现所有仓储接口,负责 PO ↔ Domain 映射;service
只负责接收请求、调用 Usecase。
这跟 Spring 在理想状态下的分层几乎一致,但 Kratos 用工程手段物理防止越层,减少团队自律成本。
7. 总结
用 Spring 开发者的眼光看:
- DDD 并不是要你放弃 Controller/Service/Mapper,而是让 Service 变成 应用服务,专注业务编排;
- 业务判断应该写在领域对象中,不应该在 Mapper 或 Service 里直接写;
- Repository 接口定义在领域层,实现放在基础设施层;
领域模型对外暴露的是业务语义,数据访问实现细节被封装在仓储里,上层业务不感知底层变化。