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

“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洞见

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,126评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,254评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,445评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,185评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,178评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,970评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,276评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,927评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,400评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,883评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,997评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,646评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,213评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,204评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,423评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,423评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,722评论 2 345

推荐阅读更多精彩内容