领域驱动设计对依赖的控制

2021-11-16 From 张逸说 By 张逸
<p> 我在《解构领域驱动设计》一书中分析软件复杂度的成因,一曰规模,一曰结构,还有一个则是变化的影响。规模与结构存在一定的矛盾关系:解决规模复杂度的有效方法为“分而治之”,一旦系统被分解为多个更为细小的软件元素,结构复杂度就会增加。结构与变化之间存在互相影响的关系:如果结构控制不合理,变化带来的影响就会更强,使得系统更加复杂。 p> <p> 认真分析结构和变化系统复杂度的影响,一个关键是对依赖的控制。当我们对系统进行分解时,依赖会成为我们无法绕开的问题,它是技术债的重要组成部分,是不可避免的。如果没有控制好依赖,系统架构就会随着时间的推移不可避免地腐化下去,如人不可避免的老去。 p> <p> 要合理控制依赖,只有两个可行的思路: p>
  • 从多到少:减少依赖而非彻底消除依赖,其核心原理是做好职责的合理分配
  • 从强到弱:如果依赖不可避免,则要想办法降低依赖,其核心原理是封装与抽象

减少依赖数量

<p> 领域驱动设计通过引入限界上下文和聚合,在战略层次和战术层次分别提供了减少依赖的控制手法。 p>

限界上下文

<p> 我在《解构领域驱动设计》书中总结限界上下文的两个特征: p> <p> 此两大特征既充分阐释了限界上下文的本质,又提供了减少依赖的思路。 p>

领域模型知识语境

<p> 限界上下文提供领域概念知识边界,在特定的上下文之下,领域概念体现的是一种局部的全貌。所谓“局部的全貌”听起来颇为矛盾,然而就观察者而言,在你所处的“上下文边界内,由于你并不关心其他上下文知识,因而你看到的虽然只是“局部”,却可以理解为这是你需要的“全貌”。 p> <p> 譬如在一家软件企业,假设一个员工作为我们要了解的全貌,在不同部门看到的却只是各自的局部: p> <p> 以财务部门为例,我并不需要知道该员工技能水平如何,也不需要知道他在哪一个团队,只需要知道该员工薪资构成,然后按照企业规章核算工资并按时发放即可员工薪资与社保等信息就在此时构成了在财务上下文下财务人员关心的全貌。 p> <p> 这一设计的好处在于引入了领域知识的控制边界,倘若分配合理,就能减少软件元素之间不必要的依赖关系。这也是限界上下文与模块之间的不同之处。 p> <p> 如果以业务模块对系统进行分解,一种直观的设计方案是单独分解出一个员工模块,然后将该员工的所有属性与行为(构成了领域知识)都分配员工模块。当财务部门进行工资核算与支付时,需要员工模块中获取它与薪资、社保相关的领域知识,带来了财务模块与员工模块之间的依赖关系。 p> <p> 限界上下文则不同,由于员工与财务相关的领域知识都根据上下文分配给了需要关注这些领域知识的财务上下文,在核算工资与支付工资时,财务上下文就无需求助于员工上下文了。这是让依赖减少的最佳模式p>

业务能力的纵向切分

<p> 限界上下文与模块之间的不同之处,还在于限界上下文不止限于封装了领域知识。它是对业务能力的纵向切分,如此切分出来的每一块,都是相对独立而完整的。准确的说法,就是先根据领域维度对整个系统进行纵向切分,然后再到限界上下文内部,根据技术维度对其进行横向切分,将限界上下文领域层独立出来。 p> <p> 模块的划分不是这样,业务模块和基础功能模块泾渭分明。业务模块自身不具备支持业务能力的功能,如访问数据库、网络通信或消息队列,于是引入了业务模块与其他基础功能模块之间的依赖。在限界上下文中,这样的依赖(领域与基础设施之间的依赖)虽然依旧存在,但由于系统的划分边界是整个限界上下文,依赖发生在限界上下文内部,从架构层次看,相当于消除了依赖,变相地减少了依赖。 p>

聚合

<p> 聚合在模型粒度与依赖之间引入了平衡。遵循对象建模的思维,建议为每一个领域概念定义一个领域模型类,哪怕是email、quantity、address这样细小的基础概念,也当如此。Martin Fowler在重构中也提到,编码时要注意规避“基本类型偏执”坏味道。不用基本类型说明这些细小的领域概念,自然就需要定义对应的模型类了。 p> <p> 一旦为非常细小的领域概念定义领域模型类,粒度就变得非常小,整个领域模型的类数量就会增加。增加了类的数量,必然也就会增加依赖。这时,聚合引入的边界就起到了很好的控制作用,它要求: p>
  • 聚合内的领域模型由实体和值对象组成,形成一棵树
  • 一棵树只有一个根,只有实体才能作为根
  • 根实体作为聚合的唯一出口和唯一入口
  • 跨聚合之间只能通过根实体建立关系
<p> 通过边界的控制,保障根实体之间才能建立关联关系,就去掉了许多非根元素跨聚合之间的关系,减少了领域模型类之间的依赖。根实体体现了对内部领域模型的封装,由它代表整个聚合内的所有领域模型,站在聚合边界之外,就可以认为领域模型类的数量减少了,调用者也无需关心聚合内部的其他实体和值对象p> <p> 限界上下文通过知识语境和业务能力形成的边界控制,减少了战略层面(也就是架构层面)软件元素之间的依赖关系;聚合则通过规定的设计原则设计约束形成的边界控制,减少了战术层面领域模型类之间的依赖关系。 p> <p> 正因为如此,我才在《解构领域驱动设计》书中指出: p> <p> 要做好领域驱动设计,在架构层面,限界上下文是不可或缺的,在设计层面,聚合才是不可或缺的。 p>

降低依赖强度

<p> 当依赖不可避免时,需要将强依赖降低为弱依赖,也即所谓的“降低耦合度”。限界上下文作为基本的架构单元,要降低依赖强度,实则就是合理地管理限界上下文之间的协作关系,这是领域驱动设计上下文映射模式所要处理的。防腐层(ACL)与开放主机服务(OHS)都降低了下游对上游的依赖,而发布语言(PL)则作为开放主机模式的补充,引入了对领域模型的封装。 p> <p> 上下文映射模式降低了限界上下文之间的耦合,强调了对内部领域模型的封装;对于限界上下文内部,则通过分层架构,凸显了领域模型的核心地位,利用层次(Layer)来分离关注点,并适当引入封装和抽象,解除了外部资源对领域模型,以及领域模型对外部资源的依赖。可概括为: p> <p> 在《解构领域驱动设计》书中,我通过引入菱形对称架构将上述上下文映射模式、分层架构模式,以及应用服务抽象资源库等内容全部囊括其中。开放主机服务属于北向网关,其中也涵盖了应用服务;防腐层属于南向网关,其中也涵盖了资源库,同时扩大了防腐层的外延,将所有对外部资源的访问都视为南向网关。至于发布语言,则介于外部网关层与内部领域层之间,就其本质而言,仍然属于外部网关层的一部分。 p>

自治性

<p> 减少依赖数量,降低依赖强度,一言以蔽之,其实就是我们耳熟能详的六字法则“高内聚低耦合”。不管是架构原则还是设计原则,都是知易行难,知道“高内聚低耦合”的原则,并不能确保你做出符合该原则设计领域驱动设计通过限界上下文与聚合的核心模式提供了相对可行的方法。若要解密领域驱动设计,此二者应为解密的钥匙。 p> <p> 为了更好地解密二者,我总结了它们共同的特性,将其名为“自治性”。要设计限界上下文与聚合,就需要确保它们的自治性。 p>

自治的限界上下文

<p> 自治的限界上下文需要具备如下四个特征: p> <p> 最小完备和自我履行结合起来,可以合理地减少依赖数量;稳定空间与独立进化配合起来,可以有效地降低依赖强度。显然,限界上下文的自治性满足了“高内聚低耦合”的架构原则p>

自治的聚合

<p> 自治的聚合需要具备如下特征: p> <p> 完整性、不变量一致性体现了聚合之“合”的一面,它意味着合入到聚合边界模型对象是“高内聚”的;独立性体现了聚合之“分”的一面,意味着不要将生命周期不一致、数据不一致的模型对象放在同一个聚合里,聚合边界具有一种排斥能力,要将“异质体”推出去,通过聚合根实体来维持彼此之间的关系,保证聚合之间是“低耦合”的。 p>

本文来源:张逸说,转载请注明出处!

来源地址:http://zhangyi.xyz/control-dependencies-in-DDD/

发表感想

© 2016 - 2022 chengxuzhixin.com All Rights Reserved.

浙ICP备2021034854号-1    浙公网安备 33011002016107号