译者 | 李睿
审校 | 重楼
领域驱动设计(DDD)是软件开发的一种重要战略方法。它涉及对业务领域的深入理解和建模,尤其适用于具有复杂业务规则、流程和交互的复杂领域。然而,有效地实现DDD需要纪律性、领域的深刻理解,以及避免可能导致次优设计和技术债务的常见陷阱。本文中将探讨在DDD中应该避免的10件事,并举例说明这些陷阱。
1.过于关注技术模式
(1)示例场景
团队在开始开发项目时,在还没有完全理解业务领域的情况下,就过度创建了存储库、聚合和值对象。例如,他们开发一个复杂的存储库来管理客户实体,而不了解客户在业务中是如何表示和利用的。因此,这个存储库包含了许多与领域的实际用例和需求不一致的不必要的方法。
(2)避免技巧
DDD的主要重点应该是理解领域。这涉及一个协作过程,团队与领域专家紧密合作,以建立对基本业务概念的共同理解和共同语言。像存储库和聚合这样的技术模式应该自然地从领域模型中出现,而不是在开始时过早地实现或过分强调。
2.过度设计模型
(1)示例场景
团队严格遵守DDD原则,创建了一个详细的领域模型,该模型包含每个可能的领域概念的单独类。例如,他们为“客户名称”(CustomerName)、“客户邮箱”(CustomerEmail)和“客户地址”(CustomerAddress)创建了单独的类,而这些本来可以组合成一个更直接的“客户”(Customer)值对象。结果模型变得过于复杂,难以维护,几乎没有附加价值。
(2)避免技巧
为了防止潜在的问题,团队有责任维护一个简单且准确反映领域的领域模型。这种谨慎的方法对于专注于对具有战略重要性的领域组件进行建模以及简化或排除不太重要的元素至关重要。需要记住,DDD主要关注的是战略设计,而不是通过不必要的复杂性来不必要地使领域模型复杂化。
3.忽视通用的语言
(1)示例场景
在协作环境中,开发人员和领域专家使用不同的技术词汇是很常见的。例如,虽然领域专家通常使用“采购订单”这一术语,但开发人员可能会在他们的代码库中使用OrderEntity。这种术语差异可能导致各种挑战,包括误解、处理不当的实现,以及代码与具体业务需求之间的同步需求。这些差异可能会阻碍有效沟通,并妨碍将业务逻辑准确转换为技术实现。因此,确保开发人员和领域专家之间对术语有共同的理解,对于促进有效协作并使技术解决方案与业务需求保持一致至关重要。
(2)避免技巧
建立和维护一种通用语言对于确保项目各个方面的清晰沟通和理解至关重要。这个过程需要与领域专家密切合作,以开发和维护一致的词汇表。语言应该统一地应用于代码、文档、对话和所有形式的交流中。通过这样做,可以防止误解,并保证模型准确地反映业务需求。这种方法有助于所有利益相关者保持一致,并促进了整个项目生命周期的凝聚力。
4.对有限语境的误解
(1)示例场景
当团队试图在例如“账单”(Billing)、“客户服务”(Customer Service)和“营销” (Marketing)各种子域中使用单个“客户”实体时,会导致应用程序不同部分之间的歧义和不必要的互连。例如,在“账单”语境中对“客户”实体所做的修改可能会无意中影响“营销”语境,从而导致不可预见的行为和数据差异。
(2)避免技巧
在定义有界语境时,明确划分具有不同职责的领域区域至关重要。每个有界语境都应该拥有自己的模型和明确定义的边界。为了维护每个语境模型的完整性,必须通过定义良好的接口或反腐败层促进语境之间的集成。这些措施确保保留每个有界语境的职责和完整性。
5.不符合商业战略
(1)示例场景
团队对DDD的采用纯粹是技术性的,他们专注于对领域所有方面的建模,而没有考虑业务战略。他们投入了大量精力来建模领域的周边元素,例如“员工出勤”和“办公用品管理”,而忽视了提供最大价值的核心业务流程:“订单履行”。 由于采用这种方法,生成的模型很复杂,但需要与业务的战略目标保持一致。
(2)避免技巧
利用DDD深入分析和专注于领域中最重要和最有影响力的部分是至关重要的。确定向业务交付最高价值的方面,并确保建模工作与业务的总体优先事项和战略目标紧密结合。积极与关键业务利益相关者合作,以全面了解对他们而言最具价值的领域,并在建模工作中优先考虑这些领域。这种方法将最佳地反映业务的关键需求,并有助于成功实现战略目标。
6.过度使用实体而不是值对象
(1)示例场景
许多软件开发人员将“货币”(Currency)概念化为具有唯一标识符的实体本质上将其视为独立对象。但是,将“货币”视为由其属性(例如“美元”或“欧元”)定义的值对象可能更有效。通过坚持使用前一种方法,团队无意中引入了不必要的复杂性,例如需要管理“货币”实体的生命周期、状态和身份。这不必要地使代码库变得复杂,使其更加笨重并增加了其冗余性。
(2)避免技巧
在设计软件时,尽可能地利用值对象是有益的,特别是对于保持不变且不需要唯一标识的类型和对象。值对象是有利的,因为它们更易于管理和预测,通常导致代码更易于维护。这些对象可以有效地表示特定于领域的值,例如日期、货币值、测量值和其他基本概念。
7.忽略聚合及其边界
(1)示例场景
在DDD的语境中,聚合表示作为数据更改的单个单元的相关对象的集群或组。当团队将“产品”(Product)建模为独立实体时,他们可能会忽略其聚合边界,从而允许多个服务独立地修改它。这可能导致不同服务对同一“产品”的更新冲突,从而导致数据不一致和违反业务规则。定义和尊重聚合边界对于维护数据完整性和确保跨不同系统部分的一致性至关重要。
(2)避免技巧
聚合是DDD中的一个基本概念,它涉及将一组相关对象视为数据更改的单元。聚合包含一个特定的对象,称为聚合根,它作为聚合内所有修改的入口点。在聚合中封装相关对象并通过聚合根进行修改,可以更容易地执行业务规则并维护数据一致性和完整性。这种方法有助于确保所有操作和更改都发生在聚合的定义边界内,从而可以更好地控制和管理复杂的数据结构。
8.未能有效地使用域事件
(1)示例场景
团队忽略域事件并直接调用服务。这导致他们的系统变得紧密耦合,增加了不同系统部分之间的依赖关系。因此,修改或扩展系统变得更具挑战性,因为一个服务中的任何更改都会直接影响其他服务,从而导致更改的多米诺骨牌效应,并使系统更加脆弱。
另一方面,在另一种情况下,其他的一个团队过度使用领域事件,为每一个小的变化都发出一个事件,例如“客户创建”(CustomerCreated)、“客户更新”(CustomerUpdated)和“客户删除”(CustomerDeleted),即使系统的其他部分并不需要这些事件。这将导致生成和处理过多的事件,从而导致性能下降、复杂性增加和不必要的资源消耗。系统中充斥着毫无实际意义的事件,导致事件疲劳,使识别和响应关键事件变得更加困难。
(2)避免技巧
在开发系统时,利用领域事件来捕获领域内的重大变化是至关重要的。这些事件应该经过精心设计,以服务于明确的目的,并在系统内传达有意义的状态变化。通过使用领域事件,可以有效地解耦系统的各个部分,从而促进系统的可扩展性和改进可维护性。
然而,谨慎行事并避免过度使用领域事件是至关重要的,因为这样做可能导致不必要的复杂性和潜在的性能问题。至关重要的是要取得平衡,只使用真正有益的领域事件,而不是不加选择地将其纳入整个系统。这种方法最终将有助于建立一个更加精简和可管理的系统架构。
9.忽视了与领域专家合作的重要性
(1)示例场景
开发团队在没有征求贷款经理或该领域其他专家的意见的情况下,开始创建“贷款审批”流程。因此,该模型省略了关键的业务规则,包括特定的风险评估标准、验证步骤和监管要求。这一重大疏忽导致软件解决方案需要与业务需求保持一致,导致其被利益相关者拒绝。
(2)避免技巧
在设计和开发过程的每个阶段与领域专家建立密切合作是至关重要的。定期与他们接触以确认对该领域的理解是准确的很重要,可以让领域专家参与讨论、设计会议和模型评审,以收集他们具有价值的见解。诸如事件风暴和领域故事讲述等技术可以促进协作建模,确保模型忠实地代表领域。
10.将DDD视为灵丹妙药
(1)示例场景
团队错误地认为DDD普遍适用于所有软件项目,而无论领域的复杂性如何。这导致他们将DDD原则应用于简单的CRUD应用程序,例如“待办事项列表”或“联系人管理”系统。其结果是,代码库过于复杂,难以维护,开发成本高昂,远远超过了项目的需求。
(2)避免技巧
DDD最适合以复杂性著称的领域,特别是那些以复杂的业务规则和流程为特征的领域。在如此复杂的领域中,业务团队和技术团队之间的紧密结合对于成功至关重要。相比之下,DDD可能并不最适合简单的应用程序或领域,在这些领域,可以采用更直接的方法。仔细评估领域的复杂性和项目的具体要求以确定最合适的方法非常重要。
结论
DDD是一种强大的软件开发方法,旨在构建与复杂业务领域相匹配的软件。然而,就像任何强大的工具一样,必须明智地使用。通过避免这10个常见的陷阱,可以充分利用DDD的潜力,创建出准确反映并支持业务目标的软件。需要记住,DDD不仅仅是关于模式和实践;它关于促进协作、建立对领域的共同理解,以及构建能够提供真正战略价值的解决方案,这种价值正是团队协同努力的重要贡献。
通过专注于理解领域、避免过度设计、与业务战略保持一致,以及保持清晰一致的语言,团队可以创建不仅在技术上合理,而且与业务需求高度一致的模型。
原文标题:10 Things To Avoid in Domain-Driven Design (DDD),作者:Reza Ganji