当前位置: 首页 > news >正文

用 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)serviceController参数校验、鉴权、DTO ↔ Domain 转换
应用层(Usecase)bizService(理想状态)编排业务流程、事务控制、调用多个领域对象或外部服务
领域层repo实体类、领域服务接口维护业务不变式、暴露行为方法、定义仓储接口
基础设施层dataMapper / 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);}
}
  • 业务逻辑在领域对象:例如ensureActivereservefreeze方法,他们只关心业务实现,不关心数据是怎么获取的
  • 数据访问集中在仓储实现: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 接口定义在领域层,实现放在基础设施层;

领域模型对外暴露的是业务语义,数据访问实现细节被封装在仓储里,上层业务不感知底层变化。

http://www.xdnf.cn/news/1290097.html

相关文章:

  • provide()函数和inject()函数
  • 数据结构:后缀表达式:结合性 (Associativity) 与一元运算符 (Unary Operators)
  • ZKmall开源商城的容灾之道:多地域部署与故障切换如何守护电商系统
  • 21.Linux HTTPS服务
  • 【GESP】C++一级知识点之【集成开发环境】
  • 备战国赛算法讲解——马尔科夫链,2025国赛数学建模B题详细思路模型更新
  • UE5.3 C++ 动态多播实战总结
  • SQL 生成日期与产品的所有组合:CROSS JOIN(笛卡尔积)
  • JVM宝典
  • 每日五个pyecharts可视化图表-line:从入门到精通 (4)
  • 什么时候用WS(WebSocket),什么使用用SSE(Server-Sent Events)?
  • Pytest项目_day13(usefixture方法、params、ids)
  • 机器学习处理文本数据
  • linux 开机进入initramfs无法开机
  • 串口通信学习
  • 数据分析专栏记录之 -基础数学与统计知识
  • Spring-Cache 缓存数据
  • windows git安装步骤
  • XGBoost 的适用场景以及与 CNN、LSTM 的区别
  • 网络协议——HTTP协议
  • Linux服务:Apache 虚拟主机配置指南:多站点部署三种方式详解
  • 【超详细!题解|两种做法】洛谷P3196 [HNOI2008] 神奇的国度[MCS算法]
  • 深入剖析 React 合成事件:透过 onClick 看本质
  • 过程设计工具深度解析-软件工程之详细设计(补充篇)
  • Nginx 高级配置
  • 【后端】Spring @Resource和@Autowired的用法和区别
  • 通用同步/异步收发器USART串口
  • excel-随笔记
  • [ 数据结构 ] 时间和空间复杂度
  • Python初学者笔记第二十二期 -- (JSON数据解析)