Su的技术博客

  • 首页
  • 原创
  • 视频
  • Java
  • MySQL
  • DDD
  • 事故复盘
  • 架构方案
  • AI
  • Other
  • 工具
    • AI工具集
    • 工具清单
    • JSON在线格式化
    • JSON在线比较
    • SQL在线格式化
  • 打赏
  • 关于
路很长,又很短
  1. 首页
  2. DDD
  3. 正文
                           

【转载】解构领域驱动设计(一):为什么DDD能够解决软件复杂性

2023-12-08 1743点热度 0人点赞 0条评论

1 为什么我要研究领域驱动设计

1.1 设计方法各样且代码无法反映设计

我大概从2017年10月份开始研究DDD,当时在一家物流信息化的公司任职架构师,研究DDD的初衷在于为团队寻找一种软件设计的方法论。作为架构师,经常参与设计评审,包括:需求评审、设计评审、代码评审。在评审过程中,有一点感受非常深,就是评审过程非常痛苦且几乎没有效率和成果。让我痛苦的地方有:

  • 每一个系统分析师都是基于自己的方式来进行设计功能,有的用类图、有的基于流程图,有的详细、有的粗放,更麻烦的是,大家对业务背景的理解程度完全不同,认知不同,沟通低效,很难找出设计的不合理性。
  • 评审代码时,我几乎很难将其与设计对应起来,看设计我已经够痛苦了,还要被这些代码再虐待一遍,实在痛苦至极,这样的代码评审也就变成了代码规范性、代码设计优雅度的评审,很难找出代码业务逻辑的问题。让代码正确的反应设计,是当时评审过程中碰到的一个更大的问题。

1.2 代码质量很难有效提升

在承担架构师之前,我的另一个职责是技术管理,做的工作是与软件质量相关的。当时加入一个大概2000万规模的项目,有大约100开发人员参与,开发周期大概1年。加入该团队在开发的过程中,发现了两个问题:

  • 每一个BA(可以理解为PD)设计的产品界面操作习惯都不一样,所有的开发人员做出来的界面的操作也完全不同。但是,这是一个面向物流行业的信息化软件,操作习惯的一致性很重要。
  • 代码非常混乱,没有任何的规范可言,看代码简直想吐。

基于第一个问题,我定义了统一的界面规范,这个界面规范通过和公司的PMO合作将其融入到工程过程中,作为开发人员必须遵循的规范。第二个问题,我则花费了很多的时间来尝试解决(大概有2年时间都与代码质量做斗争),最终与寻找统一的设计方法殊途同归。

如何让我们的代码变得更加干净,我在执行的过程中,按照以下步骤一步一步的执行。

  • 定义了统一的代码规范,基于界面规范的基础上,统一定义了模板工程,这些模板工程都有很好的代码基因。
  • 定义了代码规范的培训教程,包括基本的书写规范、《代码整洁之道》、《重构技巧》。
  • 定义了代码规范、代码评审制度,写入PMO定义的过程工作,作为开发人员遵循的制度。
  • 通过代码评审提升质量太慢,为了大规模快速推广,引入了SonarQube,定义了软件代码质量的度量方法,软件的代码质量分数由:圈复杂度、重复率、代码规模问题、SonarQube扫描的问题数四个维度来衡量。在度量方法之上,定义了代码质量管理制度,每周扫描软件获得详细的代码质量报告,发送给相应的产品负责人,将代码质量管理制度也融入PMO的工程过程里面,全公司进行推广,由产品负责人负责本部分的代码质量提升。

基于以上的代码质量管理方法,我认为已经是做的相当不错,但是非常遗憾的是,当我抽样评审产品的代码时,我依然感到无比沮丧,软件的代码还是太复杂、太难看懂了,与《代码整洁之道》的要求相差太远了,我耗费了1年多的工作几乎毫无成果可言。因此,我在深深思考,在编码层面,定义了规范、做了优雅编码培训、定义了编写优秀代码的相关制度,就为了让开发人员把代码写好,使代码看起来更加清晰,软件更加容易维护,为什么还是无法实现?

2 软件复杂性的根源

贫血模型是软件复杂性的根源。贫血模型本质是面向数据的设计,面向过程的编码。基于贫血模型的分层架构,通常分为UI层、业务逻辑层、数据访问层、贫血模型层,贫血模型与数据模型一致。

业务规则是软件最核心的代码,通常只占整个软件很小的一部分。在基于贫血模型的架构中,业务规则的实现,通常混杂在上层UI展现逻辑、数据库访问、缓存等各种逻辑中,分散在各个层和关联对象。通过阅读业务逻辑层的代码来还原真实的业务规则很困难,很难从代码反映其业务规则设计,并且随着软件需求变更,业务规则更加难以还原,软件复杂度将不可控。

以下是一段业务逻辑层的实现代码。

public OrderDto signOrder(Order order) {
    Assert.notNull(order, "OrderDto can not be null.");
    OrderDto result = new OrderDto();
    result.setIsOperationSuccess(true);
    if (null == order.getId()) {
        result.setIsOperationSuccess(false);
        result.setOperationMassage("id不能为空。");
        return result;
    }
    OrderCondition orderCondition = new OrderCondition();
    orderCondition.setId(order.getId());
    order = orderMapper.selectOne(orderCondition);
    if (null == order) {
        result.setIsOperationSuccess(false);
        result.setOperationMassage("该订单不存在。");
        return result;
    }
    if (order.getOrderStatus() != Integer.valueOf(StatusEnum.ORDER_STATUS.ORDER_WAIT_RECEIVE.getCode())) {
        result.setIsOperationSuccess(false);
        result.setOperationMassage("订单号:{" + order.getOrderNo() + "}不是待收货状态,不能进行签收。");
        return result;
    }
    // 该订单下的所有商品的实收数(发货数量)必须都大于0
    boolean validDeliveryCount = true;
    Double orderTotalAmount = 0d;
    List orderGoodsList = orderGoodsBiz.selectOrderGoodsByOrderId(order.getId());
    List orderGoodsListForUpdate = new ArrayList();
    if (EmptyUtil.isNotEmpty(orderGoodsList)) {
        for (OrderGoodsDto orderGoods : orderGoodsList) {
            if (null == orderGoods.getDeliveredNum() || orderGoods.getDeliveredNum() ) {
                validDeliveryCount = false;
            } else {
                // 根据商品发货数量重新计算订单总金额......
                Double price = (null == orderGoods.getDiscountPrice() ? orderGoods.getOriginalPrice() : orderGoods.getDiscountPrice());
                Integer goodsNum = (null == orderGoods.getDeliveredNum() ? 0 : orderGoods.getDeliveredNum());
                orderTotalAmount += price * goodsNum;
                // 更新orderGoods的收货数量
                orderGoods.setReceivedNum(goodsNum);
                OrderGoods orderGoodsForUpdate = new OrderGoods();
                BeanUtils.copyProperties(orderGoods, orderGoodsForUpdate);
                orderGoodsListForUpdate.add(orderGoodsForUpdate);
            }
        }
    }
    if (!validDeliveryCount) {
        result.setIsOperationSuccess(false);
        result.setOperationMassage("订单号:" + order.getOrderNo() + ",订单下所有商品都已发货才可进行签收操作,请确认。");
        return result;
    }
    order.setOrderStatus(Integer.valueOf(StatusEnum.ORDER_STATUS.ORDER_SIGN.getCode()));
    order.setOrderTotalAmount(orderTotalAmount);
    order.setPaymentAmount(orderTotalAmount);
    order.setUnpaidAmount(orderTotalAmount);
    update(order);
    orderGoodsBiz.batchUpdate(orderGoodsListForUpdate);
    List orders = new ArrayList();
    orders.add(order);
    saveRouteMessage(orders);
    return result;
}

类似这样的代码非常常见,通过阅读这段业务逻辑代码,可以发现它处理了以下的任务:
(1)返回结果的处理。
(2)数据库访问。
(3)关联对象的数据库访问。
(4)业务规则。

业务规则代码与数据库访问、关联对象数据库访问、结果处理等其它逻辑在一起实现,通过代码还原业务规则会越来越复杂且随着时间推移,代码逻辑会越来越偏离设计。作为软件系统最核心的部分——业务规则,如果我们仅仅将其从其它任务中剥离,我们的代码将演化如下。(注:以下代码仅演示剥离出来业务逻辑,并非DDD推荐方式,下篇介绍。)

public void signOrder(Order order) {
    assertCanBeSigned(order);

    Double orderTotalAmount = 0d;

    List orderGoodsList = order.getOrderGoods();
    for (OrderGoods orderGoods : orderGoodsList) {
        Double price = (null == orderGoods.getDiscountPrice() ? orderGoods.getOriginalPrice() : orderGoods.getDiscountPrice());
        Integer goodsNum = (null == orderGoods.getDeliveredNum() ? 0 : orderGoods.getDeliveredNum());
        orderTotalAmount += price * goodsNum;

        orderGoods.setReceivedNum(goodsNum);
    }

    order.setOrderStatus(Integer.valueOf(StatusEnum.ORDER_STATUS.ORDER_SIGN.getCode()));
    order.setOrderTotalAmount(orderTotalAmount);
    order.setPaymentAmount(orderTotalAmount);
    order.setUnpaidAmount(orderTotalAmount);
}

public void assertCanBeSigned(Order order) {
    Assert.notNull(order, "OrderDto can not be null.");

    if (order.getOrderStatus() != Integer.valueOf(StatusEnum.ORDER_STATUS.ORDER_WAIT_RECEIVE.getCode())) {
        throw new BusinessException("订单号:{" + order.getOrderNo() + "}不是待收货状态,不能进行签收。");
    }

    List orderGoodsList = order.getOrderGoods();
    if (!EmptyUtil.isNotEmpty(orderGoodsList)) {
        throw new BusinessException("订单号:" + order.getOrderNo() + ",订单没有包含商品,是一个空的订单,无法签收。");
    }

    for (OrderGoods orderGoods : orderGoodsList) {
        // 该订单下的所有商品的实收数(发货数量)必须都大于0
        if (null == orderGoods.getDeliveredNum() || orderGoods.getDeliveredNum() ) {
            throw new BusinessException("订单号:" + order.getOrderNo() + ",订单下所有商品都已发货才可进行签收操作,请确认。");
        }
    }
}

这段代码反映的业务规则是订单签收规则。

(1)如果订单不是待发货状态,不能签收;
(2)校验订单下所有商品的发货数量都要大于0;
(3)计算订单总金额,并设置收货数量为发货数量;
(4)设置签收状态、总金额、支付金额和未付金额。

你可以发现这段单纯实现业务规则的代码,会更加的简单、清晰,也会使软件更加的容易维护。在DDD的方法论里面,业务规则是在领域层来实现的,领域层的代码仅仅是业务规则,这时候,其分层架构的分层逻辑和基于贫血模型的分层逻辑也会不一样了。

通过以上代码的对比我们发现:

  • 剥离业务规则无关的代码,将更加清晰简单,容易和业务规则保持一致。
  • 贫血模型会导致业务逻辑层混杂了太多代码和逻辑,难以还原业务规则,保证代码与设计一致性,是复杂性根源。

3 DDD如何解决软件复杂性

DDD解决软件复杂性的方法核心为两点:

  • 通过领域模型为业务知识建模,领域模型作为业务、技术团队沟通的统一语言。
  • 确保软件实现与领域模型保持一致。

软件实现与领域模型保持一致是本书的核心思想,DDD构建了一套完整的方法论来支持领域模型驱动程序设计。这套方法论简述如下。

  • 分层架构:业务规则的代码只占软件很少的代码却是最核心的部分代码,将其分离出来作为独立的领域层,使领域层的实现与领域模型保持一致,领域层的业务对象不再是贫血模型。
  • 领域驱动设计:领域驱动设计,即领域模型驱动程序设计。这里给出了如何通过代码表达领域模型的编码模式。这些模式包括:关联、实体、值对象、服务、聚合根、Repository、Factory。它们构建了将领域模型表达成代码的方法论,保证了代码和设计一致。
  • 战略设计:复杂领域模型的实现方法论。

我将在下一篇文章中详细解释DDD的核心思想,让你明白它是如何解决复杂性的。

解构领域驱动设计(一):为什么DDD能够解决软件复杂性

关于iOpenWorksSDK下载和疑问,访问:https://www.cnblogs.com/baihmpgy/p/11818026.html。

本文基于Creative Commons Attribution 2.5 China Mainland License发布,欢迎转载,演绎或用于商业目的,但是必须保留本文的署名道法自然(包含链接)。如您有任何疑问或者授权方面的协商,请给我留言。

本文仅供学习!所有权归属原作者。侵删!文章来源:作者 https://www.cnblogs.com/baihmpgy/p/10259264.html

更多文章:

  1. DDD系列第五讲:聊聊如何避免写流水账代码
  2. 殷浩详解DDD 第三讲 - Repository模式
  3. Java后端18种接口优化技巧
  4. 全链路压测之影子库及ShardingSphere实现影子库源码剖析
  5. 解构领域驱动设计(三):领域驱动设计
  6. 解构领域驱动设计(二):分层架构
  7. 浅谈DDD中的聚合
  8. 关于聚合根、领域事件的那点事——深入浅出理解DDD
  9. 图解Git
  10. 殷浩详解DDD系列 第二讲 - 应用架构
标签: 转载 架构 ddd 领域驱动设计 领域建模 软件复杂性
最后更新:2023-12-07

秋天0261

关注Java领域,后端开发、Netty、Zookeeper、Kafka、ES、分布式、微服务、架构等。分享技术干货,架构设计,实战经验等。

打赏 点赞
< 上一篇
下一篇 >

文章评论

razz evil exclaim smile redface biggrin eek confused idea lol mad twisted rolleyes wink cool arrow neutral cry mrgreen drooling persevering
取消回复

广告
文章目录
  • 1 为什么我要研究领域驱动设计
  • 2 软件复杂性的根源
  • 3 DDD如何解决软件复杂性
最新 热点 推荐
最新 热点 推荐
微服务架构:必懂的6大性能维度 Anthropic Code with Claude 开发者大会:开启 AI Agent 新时代 视频笔记-微服务架构P4:必懂5种设计模式 视频笔记:微服务架构P4 设计模式:每服务数据库、API 网关和事件驱动架构 干货 | 论Elasticsearch数据建模的重要性 马蜂窝消息总线——面向业务的消息服务设计 基于 MySQL Binlog 实现可配置的异构数据同步 视频笔记:Google发布Agent2Agent协议
视频笔记:微服务架构P4 设计模式:每服务数据库、API 网关和事件驱动架构干货 | 论Elasticsearch数据建模的重要性视频笔记-微服务架构P4:必懂5种设计模式Anthropic Code with Claude 开发者大会:开启 AI Agent 新时代微服务架构:必懂的6大性能维度
单体分层应用架构剖析 Redis的BigKey(大key)、HotKey(热key)又引发了线上事故 一次误删除MySQL主库的恢复操作 Elasticsearch 使用误区之五——单次请求获取大量数据 系统设计 | 如何表达技术架构?(规划篇) 聊聊spring事务失效的12种场景,太坑了 系统设计 | 设计和解析 DSL 殷浩详解DDD系列 第二讲 - 应用架构

CRUD (1) Event Sourcing (1) graphql (1) id (1) NoSQL (1) quarkus (1) rest (1) RocketMQ (2) Spring Boot (1) zk (1) zookeeper (1) 上下文 (1) 事务消息 (1) 二级缓存 (1) 值对象 (1) 关系数据库 (1) 分布式缓存 (1) 原子性 (1) 唯一ID (1) 商品 (1) 多对多 (1) 子域 (1) 字符集 (1) 客户端心跳 (1) 幂等 (2) 干货 (1) 并发 (1) 应用场景 (1) 应用架构图 (1) 康威定律 (2) 异步复制 (1) 微服务架构 (3) 总体方案 (1) 技术方案 (2) 技术架构 (2) 技术架构图 (1) 技能 (1) 持续集成 (1) 支撑域 (1) 故障恢复 (1) 数据架构图 (1) 方案选型 (1) 日记 (1) 服务发现 (1) 服务治理 (1) 服务注册 (2) 机房 (1) 核心域 (1) 泄漏 (1) 洋葱架构 (1) 消息队列 (5) 源码剖析 (1) 灰度发布 (1) 熔断 (1) 生态 (1) 画图工具 (1) 研发团队 (1) 线程 (2) 组织架构 (1) 缓存架构 (1) 编码 (1) 视频 (20) 读写分离 (1) 贵州 (1) 软件设计 (1) 迁移 (1) 通用域 (1) 集群化 (1) 雪花算法 (1) 顺序消息 (1)

推荐链接🔗
  • AI工具集
  • 工具箱🛠️

COPYRIGHT © 2014-2025 verysu.com . ALL RIGHTS RESERVED.

Theme Kratos Made By Seaton Jiang

粤ICP备15033072号-2