当前位置: 首页 > 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.dtcms.com/a/328609.html

相关文章:

  • 当赞美来敲门:优雅接纳的艺术
  • 在线免VIP的动漫网站
  • 【沧海拾昧】使用LibUsbDotNet进行Windows/Ubuntu跨平台串口管理
  • 当人工智能照进现实:机遇与挑战的双重奏
  • SQL 合并两个时间段的销售数据:FULL OUTER JOIN + COALESCE
  • vue文件或文件夹拖拽上传
  • 悬空标记攻击 -- idekctf 2025 CTFinder
  • qt界面优化--api绘图
  • 杰里常用功能API
  • 科普:python中的“__init__.py”与“import”的关系
  • React中的Hook到底是个什么鬼
  • 北京-4年功能测试2年空窗-报培训班学测开-第七十四天-线下面试-聊的很满意但可能有风险-等信吧
  • mysql中的常见的索引类型及其特点
  • 【活动回顾】开源共建 · 智能体时代的 AI 基础设施全栈实践
  • 并发编程中的 ABA 问题:从原理到实战解决方案
  • Java数据结构之LinkedList
  • 电子电路原理学习笔记---第5章特殊用途二极管---第2天
  • 基于51单片机RFID智能门禁系统红外人流量计数统计
  • -bash: ./restart.sh: /bin/bash^M: 坏的解释器: 没有那个文件或目录
  • MySQL 从入门到精通:基础概念与操作指南
  • Sklearn 机器学习 异常值检测 局部异常因子算法LOF
  • SQL连接操作全解析:从入门到精通
  • 某跨国金融机构法律法规自动文本摘要(ATS/文本大意提取)功能规划
  • 嵌入式第二十六天(文件IO相关操作)
  • [Robotics_py] docs | 机器人状态/位姿 | 环境表示_栅格地图
  • 准则 :用“检测到什么”的方式来编写需求条件
  • Python 异常捕获
  • 为什么我换了项目管理软件?
  • 如何在 Odoo 18 管理产品文档手册
  • Redis面试题及详细答案100道(16-32) --- 数据类型事务管道篇