DDD诊所——聚合过大综合症

news2025/1/18 3:48:52

“DDD诊所”是Thoughtworks DDD社区的一项活动,通过对同事们在实施DDD过程中遇到的问题进行分析和解答,共同提高开发水平。我们将其中一些典型案例整理成文供大家参考。之后也会考虑在适当的时候将这一形式对外部开放。

就诊日期:2022年6月8日

患者:某DevOps平台持续集成模块

诊金:0元(免费义诊)

【患者主诉】

疑似问题设计

某DevOps平台需提供持续交付流水线设计功能,使用户可在界面上规划完整流程。因此,流水线设计页面功能繁杂,涵盖阶段规划、前置触发条件设置、质量门禁控制等。随着功能扩展,页面复杂度逐渐提升。此类功能旨在协助用户优化持续交付流水线管理,提升交付效能与质量。

持续交付流水线界面原

功能介绍:

  1. 设计不同的阶段:用户可以根据需要设置不同的阶段,例如开发阶段、测试阶段等。对于每个阶段,用户可以设置不同的步骤,例如Checkout、编译、构建镜像和部署等。

  2. 设计前置触发条件:用户可以选择两种触发方式,一种是某个代码仓库提交触发,另一种是定时任务。用户可以设置多个前置触发条件。

  3. 设置质量门禁:用户可以选择给阶段设置质量门禁,例如单元测试覆盖率大于80%等。这些门禁指标可以在质量门禁管理功能中设置。在流水线执行时,如果不满足门禁指标,会阻止流水线进入下一个阶段。

项目架构师在接到需求后,根据需求设计了一个名为“持续交付流水线领域模型”。该模型的目的是方便用户在界面上完成一系列操作,如代码质量检查、编译代码、构建镜像和部署等。这些操作被看作是一个整体,并被组织在一个叫做“流水线”的聚合中。这个聚合包括质量门禁、不同的阶段和触发规则等。这种设计旨在保证流水线中各个组成部分的一致性。

请注意,图中的“<>”是一种自定义的衍生关系,意味着该对象映射自其他上下文。

持续交付流水线领域模型

团队按照这个模型落地了代码,随着交付的深入,这个模型的缺点也浮现出来。

引发问题

1. 认知负载上升

这个聚合包含了7个实体(不包括抽象类),每个实体都有自己相关的业务,因此这个聚合的认知负载相对较大。此外,该部分业务需要集成不同的外部依赖系统,如Sonar(用于实现质量门禁)、定时任务组件(用于定时任务触发器)、GitLab或GitHub(用于代码提交触发器)等。尽管可以通过使用Repository模式和依赖倒置原则来分离功能和实现,但开发人员或维护人员仍需要掌握相关知识。如果某个人接手了这个功能(例如修改质量门禁相关功能),他/她基本上需要了解整个系统的工作方式。

2. 部分可用性难以实现

这个功能集成了多个第三方系统,并被设计为一个聚合。由于这些功能中的任何一部分不可用时,整个功能都将受到影响,因此难以实现部分可用性。例如,当Sonar服务暂时不可用时,代码触发和阶段维护的部分也无法为用户提供服务。如果后续用户提出了部分可用性需求,要求Sonar不可用的情况下,要提示质量门禁设置失败,但同时,其他部分仍需为用户提供服务,那么根据这个设计就很难实现了,只能推倒重建,成本非常高。

不幸的是,在设计过程中我们不知不觉地构建了一个分布式单体。分布式单体架构是对一种设计糟糕的微服务架构的戏称。一般指那种由于架构师没有充分考虑和掌握分布式的优势和代价,凭感觉设计出的微服务架构。这种架构导致设计出的系统既不能享受分布式的弹性优势,又丢失了单体服务易于实现ACID事务强一致性方面的便利。通常出现在未经良好设计的微服务风格的分布式软件中。

3. 牺牲了性能和并发性

当前的交互设计是用户在设计界面对流水线的各部分进行设计,设计完成后按提交按钮提交所有部分。实际上,用户在每次修改时,不一定需要同时修改质量门禁、阶段、触发规则等所有的部分。然而由于聚合模式的特点,我们每次都需要对整个聚合的所有实体进行整存整取。即使用户只想修改其中一部分(如“触发规则”),我们仍需要更新名为“流水线”的聚合的整个7个模型,这将导致巨大的性能浪费。此外,如果两个用户同时操作业务上互不影响的两部分如“触发规则”和“质量门禁”时,会相互冲突,有一个用户要被提示“设计已变更,修改失败”,降低了系统的吞吐量。

【诊断】

初步诊断,患者的病情主要是聚合过大综合症,即聚合设计不合理,导致聚合过大。在DDD实践中,合理划分聚合是个比较有挑战的问题。

聚合过大综合症的病理分析

在DDD的落地实践过程中,聚合的大小经常被描述为一个不可言说的知识。很多时候,凭经验和感觉会导致比较差的设计。然而,在实践中,识别大聚合仍有迹可循,一般来说,出现了这三种情况时,就需要警惕聚合过大综合症:

1. 宽聚合

一个聚合聚合了多个同级实体,一个“父亲”多个 “儿子”。如图所示:一旦超过三就有大聚合的风险。

2. 深聚合

聚合的深度过深,例如聚合根有儿子实体,也有孙子实体,也有重孙实体。当层级达到三层时,就存在大聚合的风险。

3. 胖聚合

尽管结构简单,但实体对象实例多。例如订单和订单行作为了一个聚合,而实际业务经常出现有几千个订单行的订单。这种也存在大聚合的风险。

聚合过大的三个征兆

正如该案例所表现的那样,这是一个具有宽聚合和深聚合的聚合过大综合症症状。聚合过大综合症会导致一系列问题,例如认知负荷增加、部分可用性下降以及性能问题。而在该案例中,这些问题正是由聚合过大综合症所导致的。

【治疗建议】

出现聚合过大综合症,大多数情况是由于聚合划分不合理所导致的。为了解决这个问题,我们可以回顾《领域驱动设计》一书中有关聚合模式的定义,以了解如何合理地划分聚合。

识别聚合的方法

在《领域驱动设计:软件核心复杂性应对之道》一书中,聚合的定义如下:

在具有复杂关联的模型中,要想保证对象更改的一致性是很困难的。需要维护适用于密切相关的对象组的Invariant,而不仅仅是离散的对象。然而,过于谨慎的锁定机制又会导致多个用户之间毫无意义地互相干扰,从而使系统不可用。

我们应该将 Entity和 Value Object分门别类地聚集到Aggregate中并定义每Aggregate的边界。在每Aggregate中,选择一个Entity作为根,并通过根来控制对边界内其他对象的所有访问。只允许外部对象保持对根的引用。对内部成员的临时引用可以被传递出去,但仅在一次操作中有效。由于根控制访问,因此不能绕过它来修改内部对象。这种设计有利于确保Aggregate中的对象满足所有固定规则,也可以确保在任何状态变化时Aggregate作为一个整体满足固定规则。

根据聚合模式的定义,我们可以得出判断两个实体(Entity)是否属于一个聚合的依据,需要把握两个条件:

1. 存在整体部分关系

当某个Entity是另一个Entity的部分时,称为整体部分关系。例如:

  • 汽车轮胎是汽车的一部分
  • 学生是班级的一部分
  • 订单行是订单的一部分

2. 实体之间存在变更时需要遵守的固定规则(Invariants)

固定规则或称为不变量不变式,来自契约式设计。

在计算机科学中,不变量是指在计算机程序执行的某一阶段始终为真的逻辑论断。例如,循环不变量是一个条件,在一个循环的每个迭代开始和结束时都是真的。
— wiki <https://en.wikipedia.org/wiki/Invariant_(mathematics)>

在划分聚合时,我们关注的是一些由业务原因所约束的固定规则。这些规则通常是通过与业务人员沟通,了解“A更新的时候,会不会引起B的某个属性的更新”,“如果两个实体暂时不一致,是否会导致难以承受的业务后果”等问题来得出的。

例如,在订单系统中,假设需求是整个订单的总价等于订单行的总价之和,且总价必须小于3000(左图)。在这种情况下,订单与订单行之间就存在固定规则。因此,可以把它们放在同一个聚合中,并通过整存整取来维护这个固定规则。然而,如果业务场景是订单仅作为订单行的分组,用户需要按照订单行逐一结账(右图)那么订单和订单行就无需划分成一个聚合。

固定规则是划分聚合的重要条件

需要注意的是,固定规则中的一部分是由技术实现所约束的固定规则,例如数据库ID不重复、订单编号唯一等。这些规则通常是全局性的固定规则,需要在整个系统范围内遵循。然而,由于这些全局性的固定规则通常与特定的业务逻辑无关,因此在划分聚合时,它们通常不是参考因素。

大聚合都必须拆小么?

尽管利用业务固定规则通常能够确定较小的业务一致性边界,从而得出比较合适的聚合规模,但并不是所有业务都适用这一原则。在有些业务场景中,大聚合可能是不可避免的。在这种情况下,如果想维护固定规则,是否采用聚合模式,需要权衡使用大聚合带来的成本是否可以接受。聚合模式是一种设计模式,而非万能的解决方案,在不适用的场景下强行应用聚合可能会导致收益不成正比。

聚合模式是为了解决复杂业务中的一致性问题,将具备固定规则的一组对象整存整取的一种方案。采用聚合模式的优点在于比较简单地就能实现固定规则约束,代价就是整存整取带来的性能损失等问题。

【治疗方案】

在案例中,尽管流水线各部分之间存在整体部分关系,通过对业务进行分析,我们确定了以下固定规则:

  1. 阶段内的步骤之间存在严格的顺序依赖,因为下一个步骤通常依赖上一个步骤的产出物。因此,存在一个固定规则:某个步骤的执行顺序必须按照阶段的步骤列表中的顺序关系。
  2. 阶段质量门禁和门禁项之间存在固定规则,即当在门禁项发生变更时,门禁版本也必须随之更新门禁的版本有一定的业务含义(例如,门禁版本更新需要在界面上进行明确提示)。

整改后的聚合

因此,根据上述整改方案,原本的一个大聚合被拆分成了四个小聚合,每个聚合只包含少量实体。这种改动可以有效地降低聚合的规模,从而更好地平衡性能和业务一致性之间的关系。

【总结】

在领域驱动设计实践中,聚合的划分确实是一个难以把握的问题。聚合本身是一种有代价的模式,不合理的聚合划分可能导致严重的问题,从而引发一系列疑问,例如“为什么使用DDD后仍然遇到各种问题?”聚合过大综合症是聚合划分中的一种常见问题,当模型中出现宽聚合、深聚合或胖聚合时,我们需要警惕聚合过大综合症及其带来的难以维护、牺牲可用性和性能损失等问题。

要解决这个问题,我们需要回顾聚合解决的核心问题:如何维护对象之间的固定规则。再次考虑聚合的划分时,需要满足两个条件:整体-部分关系和实体间需要遵循的固定规则。当使用聚合模式的代价过大时,可以考虑其他方法来实现,例如通过锁机制锁定需要维护一致性的对象方法。

总之,在实践DDD时,我们应该关注聚合的划分和优化,以确保在保持业务一致性和完整性的同时,避免因聚合过大导致的性能和可用性问题。在面临聚合模式代价过大的情况时,可以灵活选择其他方法来实现业务一致性和完整性。


文/Thoughtworks 付施威
原文链接:DDD之聚合过大-Thoughtworks洞见

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

基于SpringBoot的在线视频教育平台的设计与实现【附ppt|开题|万字文档(LW)和搭建文档】

主要功能 前台界面&#xff1a; ①首页、课程信息推荐、课程信息展示、查看更多等 ②课程信息类型、课程名称、关键词、教师姓名等 ③添加购物车、立即购买、评论、点我收藏、视频播放等 ④个人中心、我的订单、我的地址、我的收藏、客服等 后台登录&#xff1a; ①首页、个人…

BERT、ERNIE、Grover、XLNet、GPT、MASS、UniLM、ELECTRA、RoBERTa、T5、C4

BERT、ERNIE、Grover、XLNet、GPT、MASS、UniLM、ELECTRA、RoBERTa、T5、C4 ELMOBERTERNIE![在这里插入图片描述](https://img-blog.csdnimg.cn/274e31d0f8274c748d05abe2ec65fc73.png)GroverXLNetGPTMASSUniLMELECTRARoBERTaT5C4ELMO BERT

小航助学GESP_C++一级模拟测试卷第3套(含题库答题软件账号)

需要在线模拟训练的题库账号请点击 小航助学编程在线模拟试卷系统&#xff08;含题库答题软件账号&#xff09;_程序猿下山的博客-CSDN博客 需要在线模拟训练的题库账号请点击 小航助学编程在线模拟试卷系统&#xff08;含题库答题软件账号&#xff09;_程序猿下山的博客-CSD…

Spring中的IOC与DI-细胞内物质与传递

对IOC的认识 Spring Inversion of Control简称Spring IOC&#xff0c;是一种设计原则&#xff0c;通过它可以实现对象之间的解耦。通过Spring DI(Dependency Injection)依赖注入实现对象生命周期管理&#xff0c;为开发者提供对象创建、使用方式。 Spring中的Bean 在Spring框…

Ubuntu安装最新版neovim

Ubuntu安装最新版neovim 一、前言 对于neovim版本很重要&#xff0c;有很多插件几乎都要要求neovim版本在0.8或者0.9。但是有一个很严重的问题就是&#xff0c;Ubuntu使用sudo apt install neovim的版本很低达不到要求&#xff08;写文章时是0.7&#xff09; 二、解决方法 …

类的加载过程和反射以及注解

类的加载过程 三个阶段&#xff1a;加载-链接-初始化&#xff0c; 类的初始化只会执行1次。 加载 把字节码文件以流的形式加载到jvm中 链接 验证&#xff1a;验证字节码的合法性等以保证jvm的安全准备&#xff1a;为静态变量赋予初始值&#xff0c;为静态常量赋予有效值。…

【FusionInsight 问题】FusionInsight HD 6.5.1 集群中遇到的问题(01)

FusionInsight HD 6.5.1 集群中遇到的问题&#xff08;01&#xff09; FusionInsight HD 6.5.1 集群中遇到的问题&#xff08;01&#xff09;Spark-on-HBase认证问题Failed to find any Kerberos tgt服务端配置修改客户端配置修改 Spark-on-HBase依赖包问题phoenix-core-4.13.1…

2023年CRM系统排行榜

很多企业想要选择一款CRM系统来进行管理&#xff0c;提高效率。那么&#xff0c;2023年国内有哪些优秀的CRM系统呢&#xff1f;这里有一份2023最新国内crm系统排名&#xff0c;希望可以帮助到正在选型的您。 1、Zoho CRM Zoho CRM是一款知名的SaaS云端CRM系统&#xff0c;拥有…

使用 OpenTelemetry 构建可观测性 02 - 埋点

这是讲解 OpenTelemetry 系列博客的第二篇。在上一篇博客中&#xff0c;我们介绍了 OpenTelemetry 是什么以及由什么组成。现在我们将讨论如何使用 OTel 准确收集遥测数据和链路追踪数据。 手动埋点 我们这里谈论“埋点”(代码插桩)&#xff0c;是指通过技术手段采集链路追踪…

网络协议详解之BGP

目录 BGP&#xff08;边界网关路由协议&#xff09; 一、基础知识囊括&#xff1a; 1.1 IGP协议追求&#xff1a; 1.2 EGP协议的追求&#xff1a; 二、BGP特点&#xff1a; 2.1 BGP数据包 2.2 BGP的工作过程 2.3 结构突变** 2.4 名词解析 2.5 BGP的路由黑洞问题 2.6 …

Plecs电力电子仿真专业教程-第一季 第四节 C语言脚本模块介绍

Plecs电力电子仿真专业教程-第一季 目录 第一章 Plecs是什么 第二节 Plecs 的功能介绍 第三节 Plecs界面介绍 补充课程 Plecs的安装教程与软件下载 第四节 Plecs C语言脚本模块介绍 鉴于很多学习的小伙伴私信我要安装软件&#xff0c;特此添加如下链接&#xff1a; Plecs4.6…

我记不住的Linux那些时间及区别

背景&#xff1a;总是搞不明白Linux系统中这些相关的时间及区别&#xff0c;通过查看网上的资料发现很多都是人云亦云&#xff0c;没有任何新意&#xff0c;所以这里根据自己的试验及资料&#xff0c;再次总结一下&#xff0c;在这里记录一下。 一、介绍 标准的POSIX文件有三…

spring如何进行依赖注入,通过set方法把Dao注入到serves

1、选择Generate右键鼠标 你在service层后面方法的这些: 2、UserService配置文件的写法是怎样的&#xff1a; 3、我们在UserController中执行一下具体写法&#xff1a; 最后我们执行一下 &#xff1a; 4、这里可能出现空指针&#xff0c;因为你当前web层,因为你new这个对象根…

计算机竞赛 医学大数据分析 - 心血管疾病分析

文章目录 1 前言1 课题背景2 数据处理3 数据可视化4 最后 1 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 &#x1f6a9; 基于大数据的心血管疾病分析 该项目较为新颖&#xff0c;适合作为竞赛课题方向&#xff0c;学长非常推荐&#xff01; &#x1f9…

神经网络为什么可以学习

本资料转载于B站up主&#xff1a;大模型成长之路,仅用于学习和讨论&#xff0c;如有侵权请联系 动画解析神经网络为什么可以学习_哔哩哔哩_bilibilis 1、一个神经网络是由很多神经元形成的 1.1 也可以是一层&#xff0c;也可以是多层 2 层和层之间的连接就跟一张网一样 2.1 每…

AutoCompany模型的概念设计,涵盖了AI智能公司的各个角色

AutoCompany模型的概念设计&#xff0c;涵盖了AI智能公司的各个角色 自动化企业概念设计与设想&#xff0c;文本将介绍AutoCompany模型的概念设计&#xff0c;涵盖了AI智能公司的各个角色&#xff0c;并结合了GPT-4接口来实现各个角色的功能&#xff0c;设置中央控制器&#xf…

【Redis】Redis哨兵模式

【Redis】Redis哨兵模式 Redis主从模式当主服务器宕机后&#xff0c;需要手动把一台从服务器切换为主服务器&#xff0c;需要人工干预费事费力&#xff0c;为了解决这个问题出现了哨兵模式。 哨兵模式是是一个管理多个 Redis 实例的工具&#xff0c;它可以实现对 Redis 的监控…

动力电池系统介绍(十三)——高压互锁(HVIL)

动力电池系统介绍&#xff08;十三&#xff09; 一、高压互锁梗概1.1 高压互锁原理1.1 高压互锁内部结构1.2 高压互锁分类1.3 高压互锁原则 二、高压互锁常见故障2.1 高压互锁开关失效2.2 端子退针导致开路2.3 互锁端子对地短路2.4 动力电池内部故障 三、高压互锁故障排查 一、…

孟德尔随机化:Steiger Test避免反向因果关系

Steiger Test避免反向因果关系 以下steiger test方法是一个新的方法学&#xff0c;增加后提升提取工具变量的稳健性&#xff0c;没有的话也不影响文章内容。 看自己的需要。跑分析过程中&#xff0c;如果是GWAS数据没有samplesize&#xff0c;自己又需要这个内容&#xff0c;…