动手实践DDD领域驱动设计,DDD到底好不好用?真有那么神吗

文章目录

  • 一、到底什么是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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/619853.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

自然资源-城市更新从立项到开发全流程梳理

自然资源-城市更新从立项到开发全流程梳理 一、城市更新项目分类 &#xff08;一&#xff09;按改造力度划分&#xff1a;整治、改建和拆建 按照改造力度由弱到强&#xff0c;城市更新项目可分为 整治类、改建类和 拆建类三种类型。不同城市命名略有不同&#xff0c;但实质相…

知识付费行业数字化转型:转的是什么?你知道吗!

在知识付费的浪潮中&#xff0c;数字化转型正悄然改变着这个行业的格局&#xff01;那么&#xff0c;知识付费行业数字化转型到底转的是什么呢&#xff1f;这是一个值得我们深入探讨的问题。 1.转的是商业模式&#xff1a;从传统的销售模式转向多元化的盈利模式。从简单的买卖关…

Pycharm2024版,更换安装源

1、选择Python Packages 2、点击图中的小齿轮 3、点击 号 4、添加源地址 常用源如下&#xff1a; 清华&#xff1a;https://pypi.tuna.tsinghua.edu.cn/simple 阿里云&#xff1a;http://mirrors.aliyun.com/pypi/simple/ 中国科技大学 https://pypi.mirrors.ustc.edu.cn…

MySQL·索引

目录 索引的意义 索引的理解 为何IO交互要是 Page 理解Page 其他数据结构为何不行&#xff1f; 聚簇索引 VS 非聚簇索引 索引操作 主键索引操作 唯一键索引操作 普通索引的创建 总结 全文索引 索引的意义 索引&#xff1a;提高数据库的性能&#xff0c;索引是物美…

挖洞不懂JS?没关系!一个BP小技巧让你快速在JS代码中找到关键信息

我们在漏洞挖掘的时候&#xff0c;一个很重要的方式是对网站的JavaScript代码做审计&#xff0c;比如 找到了一堆path&#xff0c;但是不知道参数&#xff0c;也fuzz不出来&#xff0c;一个可能的未授权接口就只能放弃 数据被加密了&#xff0c;但是不知道算法&#xff0c;需要…

HTML五彩缤纷的爱心

写在前面 小编准备了一个五彩缤纷的爱心&#xff0c;送给各位小美女们~ 在桌面创建一个.txt文本文件&#xff0c;把代码复制进去&#xff0c;将后缀.txt改为.html&#xff0c;然后就可以双击运行啦&#xff01; HTML简介 HTML&#xff08;超文本标记语言&#xff09;是一种…

【HCIP学习】BGP对等体组、聚合、路由反射器、联盟、团体属性

一、大规模BGP网络所遇到的问题 BGP对等体众多&#xff0c;配置繁琐&#xff0c;维护管理难度大 BGP路由表庞大&#xff0c;对设备性能提出挑战 IBGP全连接&#xff0c;应用和管理BGP难度增加&#xff0c;邻居数量过多 路由变化频繁&#xff0c;导致路由更新频繁 二、解决大…

小区物业管理系统

文章目录 小区物业管理系统一、项目演示二、项目介绍三、部分功能截图四、部分代码展示五、底部获取项目源码&#xff08;9.9&#xffe5;带走&#xff09; 小区物业管理系统 一、项目演示 小区物业管理系统 二、项目介绍 基于springbootvue的前后端分离物业管理系统 系统角…

实验12:综合实验

1、实验目的及要求&#xff1a; 通过本次实验完成一个小型网络中配置设备。实现配置一个路由器、两个交换机和两台PC&#xff0c;以支持IPv4和IPv6连接&#xff1b;路由器和交换机必须安全管理&#xff0c;配置VLAN间路由、DHCP、以太网通道和端口安全。通过登录思科网络技术学…

Nginx详解:高性能Web服务器与反向代理的奥秘

Nginx&#xff0c;发音为“engine-x”&#xff0c;是一个开源、高性能的HTTP和反向代理服务器&#xff0c;也是邮件代理服务器。自2004年首次发布以来&#xff0c;Nginx凭借其轻量级、高效能和高并发处理能力&#xff0c;在互联网领域迅速崛起&#xff0c;成为许多大型网站和高…

某东抢购某台脚本——高成功率

某东抢购某台脚本——高成功率 小白操作-仅供学习参考 说明 这段代码主要关联了许多网络请求和对应的业务逻辑处理&#xff0c;用于处理与一个名为“茅台商城”的应用相关的网络操作。主要功能和关键组件的详细说明如下&#xff1a; 全局变量和配置&#xff1a; 使用AES加密…

用迭代加深解决加成序列问题

可以看到这个最坏的结果是100层搜索&#xff0c;但是其实1 2 4 8 16 32 64 128&#xff0c;到128的话也只要8&#xff0c;所以大概只需要10几层搜索就可以解决了&#xff0c;这个时候就可以用迭代加深的方法&#xff0c;深度一点点的加&#xff0c;如果大于概深度就舍去。有人说…

黏土滤镜教程分享:让你的照片瞬间变身黏土艺术

在数字时代的浪潮中&#xff0c;手机摄影和修图软件成为了我们日常生活中不可或缺的一部分。而近期&#xff0c;一款名为“黏土滤镜”的修图功能火爆全网&#xff0c;让无数摄影爱好者和创意达人为之疯狂。本文将为你详细介绍几款拥有黏土滤镜功能的软件&#xff0c;并附上详细…

ChatGPT付费创作系统软件开发

uni-app框架&#xff1a;使用Vue.js开发跨平台应用的前端框架&#xff0c;编写一套代码&#xff0c;可编译到Android、小程序等平台。 框架支持:springboot/Ssm/thinkphp/django/flask/express均支持 前端开发:vue.js 可选语言&#xff1a;pythonjavanode.jsphp均支持 运行软件…

中仕公考:公务员考试都有哪些类型?

1、国考&#xff1a;国考即国家公务员考试&#xff0c;是全国统一招考。每年10月份发布公告&#xff0c;11月份笔试&#xff0c;涉及的岗位比较多。 2、省考&#xff1a;省考是各省份公务员考试&#xff0c;主要是地方各级组织的统一考试。分为全国联考和非联考&#xff0c;一…

Kubernetes-容器的生命周期(init容器、健康检查探针、钩子)

目录 一、概述 二、init容器 1.概述 2.init容器作用 3.InitC容器示例 三、容器探针 1.概述 2.探针类型 3.readinessProbe-就绪检测示例 4.livenessProbe-存活检测示例 5.livenessProbe-tcp--检测端口模板 四、钩子 1.概述 2.yaml模板 3.示例 一、概述 1.当一个p…

PSO-SVM多变量回归预测|粒子群算法优化支持向量机|Matalb

目录 一、程序及算法内容介绍&#xff1a; 基本内容&#xff1a; 亮点与优势&#xff1a; 二、实际运行效果&#xff1a; 三、算法介绍&#xff1a; 四、完整程序下载&#xff1a; 一、程序及算法内容介绍&#xff1a; 基本内容&#xff1a; 本代码基于Matlab平台编译&am…

一周学会Django5 Python Web开发 - Django5注册模型到Admin系统

锋哥原创的Python Web开发 Django5视频教程&#xff1a; 2024版 Django5 Python web开发 视频教程(无废话版) 玩命更新中~_哔哩哔哩_bilibili2024版 Django5 Python web开发 视频教程(无废话版) 玩命更新中~共计54条视频&#xff0c;包括&#xff1a;2024版 Django5 Python we…

在全志H616核桃派开发板上进行PyQt5的代码编写和运行

核桃派本地 在上一节我们通过Qt Designer设计了ui窗口并转换成了Python代码&#xff0c;由于是Python编程&#xff0c;因此我们可以在核桃派开发板打开Python代码进行编程。 在核桃派上推荐使用Thonny来打开编写Python文件, 使用请参考&#xff1a;Thonny IDE。 打开上一节生…

揭秘抖音快速涨1000粉的方法:巨量千川投流,真实粉丝引爆你的账号;怎么快速涨有效粉

在抖音这个热门社交平台上&#xff0c;拥有大量粉丝不仅能增加曝光度&#xff0c;还能带来更多的关注和互动。因此&#xff0c;许多用户都在寻找能够快速增粉的方法。本文将揭秘抖音怎么快速涨1000粉的方法&#xff0c;并介绍了一种利用巨量千川投流和真实粉丝的方式&#xff0…