DDD之“道术器”
领域驱动设计(DDD)作为一种软件设计方法从2003年面世以来,伴随着软件行业的系统架构的日趋复杂(软件架构模式也从单体架构逐步演进到SOA,而后再到微服务,详情参见之前写的软件架构模式速览),DDD也从开始时的不温不火到近年来逐步为业界广泛接受。
在金庸名著《神雕侠侣》中剑魔独孤求败在石壁上刻下这么几句:
紫薇软剑,三十岁前所用,误伤义士不祥,悔恨无已,乃弃之深谷。 重剑无锋,大巧不工。四十岁前恃之横行天下。 四十岁后,不滞于物,草木竹石均可为剑。自此精修,渐进于无剑胜有剑之境。
其中的三把剑:紫薇软剑、玄铁重剑、无剑,分别代表剑学的器、术、道。在DDD的实践过程中也难免经历这三个阶段。
对“器”的认知
还记得多年前第一次使用DDD进行系统设计时,我认为DDD最具价值的部分就是它的战术设计,因其对具体代码实现非常有帮助,如战术设计中的聚合、实体及值对象等模式是对类及关系设计的的丰富和完善,是对类设计的进一步分类和区分,能够加深开发人员对代码级设计的思考和理解;另外还在设计讨论中间建立了统一的语言,让彼此更容易理解和找到共鸣。
此外,分层架构则是另一个实用的模式了:
在DDD的分层架构之前基本都是使用的MVC架构,其中的数据Model基本都是数据库表结构(数据库驱动开发)。在掌握DDD的四层架构后我们有了相对MVC更好的选择,会在架构层次中增加一个能够表达业务逻辑的领域层,并使之与具体的实现技术解耦。另外领域层内包含的领域对象(聚合、实体)不应该是贫血模型(仅含数据的类),而应该是具有丰富行为的类。
在该认知阶段的一些常见问题:
- 对“术”(战略设计)的忽视
- 大而全的数据模型
- 复杂的领域模型
这几个问题有一定的关联性,以前所开发的软件一般都是单体架构,由于没有像物理隔离这样的强制约束,所以会导致对战略设计的忽视。这样又会引发所建立的数据模型是需要满足所有情况的“大对象”(虽然满足“小类大对象”这种组合式设计)。大而全的数据模型又会慢慢导致领域模型变得越来越复杂。
在“术”的层次上,面向对象设计与实现做对了就是DDD方法用对了。(When you remember that DDD is really just “OO software done right”, it becomes more obvious that strong OO experience will also stand you in good stead when approaching DDD. From Domain Driven Design: A Step by Step Guide)
对“术”的认知
近年来随着Cloud Native理念和微服务架构的盛行,产生了大量对原有单体系统的服务化拆分的诉求,而这些理念和诉求需要具体的设计方法来指导落地,DDD则是各路大师推荐的首选方法:
Martin Fowler:
DDD divides a complex domain up into multiple bounded contexts and maps out the relationships between them. This process is useful for both monolithic and microservice architectures。 DDD将复杂领域分解为多个限界上下文(后文使用BC来指代)及它们的映射关系。该方法对单体架构和微服务架构都非常有用。
Eric Evans:
Despite microservices being so hyped I still believes there is tremendous value in them, probably giving us the best environment we have ever had for doing Domain-Driven Design (DDD) 尽管微服务这个词现在已经有点炒作的味道,但相信微服务确实蕴含着巨大的价值,它为我们带来了或许是迄今为止最好的实现领域驱动设计(DDD)的环境。
在大师们的背书下,个人也开始重新审视自己对DDD的理解,并重新运用DDD方法来对实际业务系统进行领域建模和服务拆分。通过一系列实战,对之前的理解有了进一步的深化,总结有以下几点:
1 DDD的产生背景
DDD产生的背景有两个,一个是分析模型和实现模型的割裂,一个是软件系统越来越复杂。
模型的割裂
在UML作为建模主流的时代,软件设计被明确分为面向对象分析(OOA),面向对象设计(OOD)和面向对象编码(OOP)阶段。实际操作中OOD的工作往往被OOA和OOP各自承担一部分,并同时存在分析模型和设计模型两个割裂的模型。
而领域驱动设计的核心是建立统一的领域模型。领域模型在软件架构中处于核心地位,软件开发过程中,必须以建立领域模型为中心,以保障领域模型的忠实体现。
复杂性的提升
软件的熵总是倾向于最大化的,程序员们称之为“软件腐烂”。——《程序员修炼之道》
随着迭代开发的不断地进行,当人脑已经不再承受领域复杂性和技术复杂性的进一步交织时,软件系统就会出现无以为继的情况。
2 真实业务场景的迭代引入。
真实业务场景的引入便于我们根据实际需求开展建模,保证模型来自一个正确的起点。通过场景的迭代引入来对已有模型完成侧证和调整,另外每次只考虑一个场景也是分解业务复杂度的一种应用。
3 战略设计是DDD的核心
战略设计影响的是解决方案,对任何应用程序都有用;战术设计则用于实现富领域模型,只有当模型在领域逻辑中足够丰富才有用。
另外战术设计的各种模式是会随着技术的不断发展而演进和变化的。
4 数据模型的完整性和唯一性
在同一个限界上下文中,数据模型必须是完整且无歧义的,如果违背则是上下文划分出现坏味道。
如果不完整则可能需要与与之依赖的上下文重组,如果不唯一那么可以考虑将相同数据模型的上下文合并。
5 限界上下文大小辨析
限界上下文的粒度需要适中。
- 足够大:能够表达它所对应的整套通用语言。
- 足够小:从而使得它所封装的通用语言和领域对象具有清晰的定义。
限界上下文可以通过分析,由上而下,大粒度到小粒度逐渐分解,直至团队达成一致。
5.1 实体、聚合和限界上下文的关系
三者的关系是限界上下文>聚合>实体,一个限界上下文至少有一个聚合,一个聚合至少包含一个实体。
5.1 限界上下文和子域的关系
一个限界上下文并不一定包含在一个子域中,一个子域也可以包含多个上下文。 对于一个领域中的限界上下文不是孤立存在的,是通过多个限界上下文的协作完成业务。
5.1 限界上下文和微服务的关系
在微服务拆分线索中,微服务是根据限界上下文来划分的,因此一个微服务里至少包含一个限界上下文。
6 依赖倒置是具体方案和实现的关键
依赖倒置一般都是代码实现域中的惯用技术:
依赖倒置原则(Dependence Inversion Principle)是程序要依赖于抽象接口,不依赖于具体实现。
而DDD还将该原则运用在问题域和解决方案域中。
6.1 问题域中的依赖倒置
在子域的划分时要考虑核心域不依赖支撑域和通用域,而是支撑域和通用域依赖核心域。这样的好处是让核心域尽量稳定,其他子域的变化尽量不引起核心域发生变化。具体上下文映射模式有:防腐层、遵奉者等。
6.2 解决方案域中的依赖倒置
在使用分层架构实现时需要做到领域层不依赖于其他层次,例如:领域层不依赖于基础设施层。最早的分层架构就是领域层依赖基础设施层的,但新的分层架构都是通过领域层的抽象接口来实现对基础设施层的依赖反转。
除了上述六点感悟外,在最近的DDD回炉实践中对一系列的DDD领域建模方法进行了尝试,包括DDD的通用建模法和事件风暴法,将在后面的文章详细介绍。
在“术”的层次上,微服务的拆分做对了就是DDD方法用对了。(或者说限界上下文划分做对了就是DDD方法用对了)
对“道”的认知
所有“器”和“术”的机缘巧合,背后的“道”都是理所当然,“道”的认知贯穿于“器”和“术”的认知过程之中。软件行业的终极“道”就是“高内聚、低耦合”,它是任何的有价值的思想和方法的具象,因此存在于DDD的背后。如果觉得它太过于抽象的话,我们也可以从更具体的软件的正交设计来思考印证DDD。
正交设计四原则:
- 消除重复
- 分离关注点
- 缩小依赖范围
- 向稳定的方向依赖
这四点不仅仅能对软件的实现域进行原则性指导,同样也存在于DDD整个方法体系之中。例如:
- 消除重复,在问题域的统一语言何尝也不是消除重复的一种运用呢?
- 分离关注点应用在问题域中的子域划分和解决方案域的BC划分中,通过分离关注点来降低复杂度。
- 缩小依赖范围。DDD认为具备复杂业务的软件系统不应该维护一个大而全的领域模型,应该使用BC来破除大模型使得BC对外的依赖尽可能的缩小。
- 向稳定方向依赖这点在问题域的体现在于向价值方向依赖,核心域之所以是核心就是它代表了业务价值,价值如果不稳定那么软件也失去了存在的意义。
在“道”的层次上,软件设计做对了就是DDD理念得到了充分领悟。
总结
上述认知过程是从本人的个人经历中总结,比较偏向开发人员的视角。开发通常会从战术设计开始,在实现域进行各种DDD实践。然而这一过程并不能代表所有人,例如业务专家很可能从问题域和战略设计开始对DDD逐渐理解和掌握的。但如果要全面掌握DDD的话,这一过程注定是殊途同归的,愿各位早得其道,互勉!
参考文献
- Eric Evans谈领域驱动设计、微服务与边界
- 马丁福勒:微服务
- 变化驱动:正交设计
- Domain Driven Design: A Step by Step Guide
- 《Patterns, Principles, and Practices of Domain-Driven Design》
- 版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 3.0 许可协议。转载请注明出处!