文章目录
- 一、到底什么是DDD
- 1、传统的MVC三层架构
- 2、DDD到底解决了什么问题
- 3、DDD四层架构
- 4、为什么需要舍弃MVC而用DDD
- 二、DDD改造实战
- 1、充血模型
- 2、避免大实体
- 3、Dao改造
- 4、构建防腐层
- 5、抽象中间件
- 6、使用领域服务,封装跨实体业务
- 7、使用设计模式
- 8、改造结果
- 9、使用四层架构
- 三、总结
- 1、DDD与中台
- 2、DDD的不足
- 3、cola架构
- 4、总结
- 参考资料
一、到底什么是DDD
1、传统的MVC三层架构
以下是一个简单的转账案例(忽略代码的正确性):
@PostMapping("/pay")
public ResponseData pay(HttpServletRequest request)
{
String requestPayStr = request.getParameter("pay");
logger.info("获取支付请求参数 => " + requestPayStr);
// 1.解密报文
String decrypt = DecryptUtils.decrypt(requestPayStr);
logger.info("解密后 => {}", decrypt);
Pay pay = JSON.parseObject(decrypt, Pay.class);
return payService.pay(pay);
}
public ResponseData pay(Pay pay)
{
// 2.校验流水号,保证幂等
Pay payPo = payMapper.selectById(pay.getId());
if (payPo != null)
{
return ResponseData.fail("流水号重复");
}
// 3.校验余额
Account fromAccount = accountMapper.selectById(pay.getFrom().getId());
if (fromAccount.getAvaliable() < pay.getAmount())
{
return ResponseData.fail("余额不足");
}
// 4.风控校验
if (riskCheckService.check(pay))
{
return ResponseData.fail("风控不通过");
}
// 5.记录流水
payMapper.insert(pay);
// 6.扣款
fromAccount.setAvaliable(fromAccount.getAvaliable() - pay.getAmount());
accountMapper.updateById(fromAccount);
accountMapper.updateById(pay.getTo());
// 7.通知
noticeService.notice(pay);
return ResponseData.success(null);
}
这种代码是不是再熟悉不过了?没错,这就是我们使用Springboot进行日常开发的代码。
乍一看是不是没什么问题?确实是没问题,每一步都是按照产品定义的标准,一步一步进行实现的。
2、DDD到底解决了什么问题
1、DDD首先解决的是:业务、产品、开发等等一系列人员之间沟通的困难性。
因为各个人员所具备的技术知识不同、对业务的理解也不一样,在描述同一个需求的时候,往往会因为一些不一致的观点进行长时间的讨论。
所以,需要在他们之间达成一个共识。这种通用语言就是领域。
2、其次,DDD解决了“越来越复杂的老系统频繁迭代导致代码复杂度增加”的难题。
DDD领域驱动设计,旨在解决软件复杂性之道。一个庞大古老的系统,通常迭代起来非常困难,经历了非常多的程序员之手,东拼西凑写的五花八门,这些老代码谁都不知道是干嘛的,要想把它们梳理清楚需要大量的时间。如果要动主流程,可能一不小心就得重构。重构又是得将原来所有的老流程梳理明白。而DDD就是为了解决这种没完没了的重构。
DDD的本质就是:
解决架构变化过程中,很多业务无法梳理的问题。
并且让技术主动理解业务,避免过度设计并且将必要的部分提供扩展。
3、DDD四层架构
DDD统一基础概念:实体、领域服务、防腐层、仓库、工厂、值对象、聚合……
DDD四层架构规范:
1.领域层:Domain Layer:放之四海而皆准的理想。系统的核心,纯粹表达业务能力,不需要任何外部依赖。
2.应用层:Application Layer:理想与现实。协调领域对象,组织形成业务场景。只依赖于领域层。
3.用户层:User Interface:护城河。负责与用户进行交互。解释用户请求,返回用户响应。只依赖于应用层。
4.基础层:Infrastructure Layer:业务与数据分离。为领域层提供持久化机制,为其他层提供通用技术能力。
4、为什么需要舍弃MVC而用DDD
传统MVC可维护性差:大量的第三方模块影响核心代码稳定性。
传统MVC可拓展性差:业务逻辑与数据存储相互依赖,无法复用。
传统MVC可测试性差:庞大事务脚本与基础设施强耦合,无法单元测试。
最后业务多发生几次迭代后,这段代码就将成为一个可怕的黑洞。
所以,高质量应用就要求高内聚、低耦合。
MVC的思维就是按照业务写逻辑,而DDD的思维,更容易运用设计模式与设计原则。
二、DDD改造实战
1、充血模型
我们平常用的实体对象,通常定义核心的参数,设置get、set方法,在service中调用其方法进行属性赋值。。
而充血模型的实体对象,对其状态改变
的操作都封装在实体对象中,描述了核心的业务能力。
实体中,应该包含引起它的状态发生变化的方法。并不是所有相关的方法全放在里面。(比如对金额转入转出操作适合放在实体中,比如说将帐号余额放到数据库就不适合放在实体中)
public class Account {
private String id;
// 余额
private Integer avaliable;
// 转出操作
public void debit(Integer amount) {
if (this.avaliable < amount) {
throw new RuntimeException("余额不足");
}
this.avaliable -= amount;
}
// 转入操作
public void credit(Integer amount) {
this.avaliable += amount;
}
}
系统能做什么事一目了然。
2、避免大实体
每一个实体的应用范围不要太大。
3、Dao改造
4、构建防腐层
构建防腐层,隔离外部服务。
对于外部服务,统一使用接口进行处理,并在实现类中进行额外的封装。
public class RiskCheckService implements IRiskCheckService{
public Result check(Pay pay)
{
// 参数封装
RiskCode riskCode = pay2RiskCode(pay);
if (riskCode == RiskCode.RISK_CODE_NORMAL)
{
return Result.success();
}
// xxxxxxx
}
}
5、抽象中间件
也算是防腐层,将MQ等中间件,也通过接口进行封装,而不是全都耦合在Service中。
public class RocketMQMessageService implements IMessageService{
public Result send(Message message)
{
// 参数封装
RocketMQMessage rocketMQMessage = message2RocketMQMessage(message);
// ……
// 发送消息
rocketmqTemplate.send(rocketMQMessage);
// ……
}
}
6、使用领域服务,封装跨实体业务
多个实体的操作,使用领域服务进行额外封装。
public class AccountTransferService implements IAccountTransferService{
public Result transfer(Account from, Account to, Integer amount)
{
if (from.getAvaliable() < amount)
{
return Result.fail("余额不足");
}
from.credit(amount);
to.debit(amount);
return Result.success("转账成功");
}
}
7、使用设计模式
运用设计模式,对适当场景提供扩展。
8、改造结果
改造之后,需求更容易梳理,业务逻辑纯净清晰,没有了业务逻辑与实现细节之间的复杂转换。
更容易单元测试,业务与基础设施隔离,没有基础设施,依然很容易设计单元测试案例。各个功能组件的依赖都是独立的,可以编写单元测试案例,单独测试。
更容易开发,领域内服务自治,不用担心其他模块的影响,下单模块的Account与账务模块的Account属性与方法都可以安全不同,没有任何直接关联。
技术容易更新,业务与数据隔离很清晰,改ORM技术只需要改仓库层实现,对业务无影响。
9、使用四层架构
└── demo
├── application
| ├── assembler # dto与do转换
| ├── dto # 数据传输对象
| ├── event
| | ├── publish # 事件发布
| | └── subscribe # 事件订阅
| |
| └── service
| ├── service # 应用服务接口
| └── serviceImpl # 应用服务实现
|
├── domain
| ├── aggregate1
| | ├── event # 存放事件实体,以及事件的具体业务逻辑实现
| | | ├── publish # 发布的具体逻辑(接口 + 实现)
| | | └── subscribe
| | |
| | ├── model
| | | ├── entity # 实体
| | | ├── member # 成员对象,相当于do
| | | └── do
| | |
| | ├── repository
| | | ├── factory # 工厂(po和do转换)
| | | └── facade # 仓储接口
| | |
| | └── service # 领域服务
| | ├── domainService # 领域服务接口
| | └── domainServiceImpl # 领域服务实现
| |
| ├── aggregate2 # 聚合2
| └── aggregate3
|
├── infrastructure
| ├── config # 全局配置
| ├── constant # 常量
| ├── common # 存放消息、数据库、缓存、文件、总线、网关、公用的常量、方法、枚举等
| ├── exception # 自定义异常
| ├── jpa # 持久层
| | ├── assembler
| | ├── mapper # 持久层
| | ├── po # 持久对象
| | └── repository # 仓储实现
| | ├── mysql # 仓储实现
| | ├── redis # 仓储实现
| | └── post # 仓储实现
| |
| ├── tool # 全局id生成工具等
| └── util # 工具
|
└── interfaces
├── validator # dto校验规则
├── handler # 全局异常处理
├── assembler # dto转为do
├── vo # 视图模型(根据需求引入VO, PO, DTO, DO)
└── facade # 接口
└── demo2
├── application
| ├── assembler
| ├── dto
| ├── event
| | ├── publish
| | └── subscribe
| |
| └── service # 应用服务
| ├── context1 # 按限界上下文划分
| | ├── service
| | └── serviceImpl
| |
| ├── context2
| └── context3
|
├── domain
| ├── aggregate1
| | ├── event
| | | ├── publish
| | | └── subscribe
| | |
| | ├── model
| | | ├── entity
| | | ├── member
| | | └── do
| | |
| | ├── repository
| | | ├── factory
| | | └── facade
| | |
| | └── service
| | ├── domainService
| | └── domainServiceImpl
| |
| ├── aggregate2
| └── aggregate3
|
├── infrastructure
| ├── config
| ├── constant
| ├── common
| ├── exception
| ├── jpa
| | ├── assembler
| | ├── mapper # 持久层
| | ├── po
| | └── repository # 仓储实现
| | ├── mysql
| | ├── redis
| | └── post
| |
| ├── tool
| └── util
|
└── interfaces
├── context1 # 按限界上下文划分
| ├── validator
| ├── handler
| ├── controller # 接口
| ├── vo # dto、vo
| └── wrapper # dto转do
|
├── context2
└── context3
通常情况下执行逻辑(业务不复杂):controller -> 应用服务 -> 仓储 -> 持久层
业务复杂时:展现层 -> 应用服务 -> 领域服务 -> 仓储 -> 持久层
每层对应不同的对象(展现层vo、应用层dto、领域层do、持久层po),中间隔了dto是为了vo与do解耦
持久化操作应该放在基础层
member相当于do
按照DDD分层架构,仓储实现的部分应该属于基础层代码
仓储为了解耦领域逻辑和数据处理逻辑,在中间加了薄薄的一层仓储,相当于只是过渡的作用
三、总结
1、DDD与中台
DDD其实与中台的思想非常相似:
2、DDD的不足
DDD并不是万能的银弹,映射到具体的业务场景时,DDD的理论体系也需要由模糊到清晰。
DDD缺乏一个规范的过程指导。
DDD没有万能的需求管理体系。
DDD并没有给出明确的领域建模方法。
对团队整体的技术能力要求较高。
DDD的学习成本很高。
直接指导DDD落地的框架非常少。
DDD是动态发展的,在不同的技术环境下,会有不同的表现形式。
3、cola架构
cola架构与DDD非常像,但是不是DDD架构,它主张清晰架构。
https://blog.51cto.com/u_16213578/7217553
4、总结
DDD的思想确实可以提高个人的视野,目前企业中基本也是小的、新的项目先试点,没有一套完整的体系结构。
日常开发中,也可以尝试去构建防腐层、中间件,虽然不需要严格的按照DDD的四层结构来开发,但是也可以进行适当的封装、提供扩展,也可以实现项目较好的扩展性。
参考资料
领域驱动设计(DDD)在爱奇艺打赏业务的实践
https://www.cnblogs.com/dogleftover/p/15858187.html