领域驱动实战思考(二):用分段思想改进那些混乱的战略设计和战术设计

前言

2019年,在我对DDD进行基准化的过程中,遇到过非常多的挑战,其中一个便是:

DDD的设计过程,到底应该分为多少个阶段?每个阶段做什么事情?

这个困惑来自于书本上,以及其他咨询师在咨询过程中对于DDD设计和操作的差异:

  • 有的人会从电梯演讲和用户地图开始做设计和分析;
  • 有的人会从事件风暴开始做设计和分析;
  • 有的人会从子域开始做设计和分析;
  • 有的人会从限界上下文开始做设计和分析;
  • 有的人会直接从领域建模的聚合、实体、值对象开始做设计和分析;
  • 当然,还有的人会使用“名词动词法”直接从用例描述开始做设计和分析。

对于实际的学习者和使用者来说,这种混乱的操作手法所形成的不一致和不流畅体验,对于坚持进行DDD设计和减少吵架来说,简直是一种毁灭性的影响。

在这个过程中,最让我感触深刻的,是在于大家在落地DDD的时候,使用了大量具有“二义性”的词汇,讽刺的是,这与DDD所强调的统一语言是背道而驰的。

其中对于上述混乱影响最大的因素之一,就是大家对于DDD的“战略设计”和“战术设计”认知不一致(甚至说是混沌)的问题。

所以,这篇文章,就是围绕这两个概念,来说一说我是如何在基准化的过程中解决这个问题,统一并形成较为流畅的分段式的设计过程的。

混乱的战略设计和战术设计

一谈起“战略设计”和“战术设计”,你一定会听到“问题域”、“方案域”、“实现域”等名词。

我的同事肖然在经过反复理解和提炼之后,曾经提出过一个用于解释“问题与方案”、“战略与战术”的“元模型”:

从问题/解决方案和战略/战术维度分析DDD元模型的元素

这个模型其实对于具备多维思维和对DDD有着深入理解的人来说,是一种不错的可视化辅助理解手段(划分的合理性仅供参考)。但是在成为更多的咨询师做DDD咨询或培训时开篇必用的材料之后,事情就变得诡异了。因为这个元模型对于咨询师或者培训师是一个比较好的记忆工具,而不是用来给别人解释的适合的工具。

当没有意识到这一点的人在使用这个模型给客户解释的时候,会向客户描述成:“问题域的战略设计”、“问题域的战术设计”、“解决方案域的战略设计”以及“解决方案的战术设计”。

经过实战证明,这种方式不到1分钟,就能让客户及对DDD理论没有深入了解的人晕菜,屡试不爽。

它们是从哪儿来的?

在搞清楚这个问题之前,我们先要追本溯源,看看这些概念是从哪里来的。

问题域 / 方案域 / 实现域

什么是问题域?什么是方案域?什么是实现域?

非常有意思的是,以上名词在我搜索的过程中,能够搜到的结果绝大多数都是和软件系统的业务分析及领域驱动设计相关的,当然他们还有很多不同的翻译或者别称。具体到底最初是哪里定义了这三个词汇,我目前还无从查证。但仅从字面上来说,这是其实是一个非常自然的,人类解决问题的过程

我不想用书本上或者别人博客里的概念进行重复介绍,我更喜欢用我在做咨询的时候总是会用一个屡试不爽的小测试来让大家体会。

首先,我会问:

我:“我饿了。”

然后,我会让大家根据这个问题来接一句话去将对话接下去,这时候很多人会这样说:

客户A:“你吃什么?”
客户B:“走,一起吃饭去?”
客户C:“你想什么时候吃?”
……

以上就是绝大多数人所习惯的基于方案而非问题进行回答的方式。但一个更正确的,体现问题驱动的答法应该是:

某善于问题驱动的客户:“你为什么饿了?”

这时候,接下来我的回答有可能是:

我:“我中午加班开会,时间比较紧,下午还要来给大家做工作坊,所以中午没吃饭,现在感觉有些低血糖。”

最后,得到的答案往往就会跟之前有很大不同:

客户A:“要不要我给你找点吃的你先垫一垫?”
客户B:“要不我们先暂停工作坊你去吃点东西?”
……

在这个例子中,包含几个不同的部分:

  • 待解决问题:我饿了
  • 问题澄清的结果:中午加班开会,时间比较紧,下午还要来给大家做工作坊,所以中午没吃饭,现在感觉有些低血糖
  • 解决方案:先垫一垫 or 暂停工作坊去吃东西
  • 实现细节:先吃块儿巧克力 or 去外面吃快餐

让我给予一个简单而非严谨的总结:

问题需要被澄清,问题澄清的结果决定解决方案,问题往往会有多种解决方案,每种解决方案的落地过程是实现细节。

那么,所谓的“问题域”无非就是“待解决问题的集合”,“方案域”便是“解决方案的集合”,“实现域”便是“实现细节的集合”。而“领域驱动”,其实就是“问题驱动”——从澄清关键业务问题开始,逐渐寻找适合的解决方案,再到确定实现细节的过程。

战略设计 / 战术设计

说到“战略设计”和“战术设计”,这两个词的来源也非常有意思,让我们来说说。

(以下仅以国内翻译过的书籍来介绍)

Eric Evans最初的解释

“战略设计(strategic design)”一词最早来自于2004年Eric Evans最初的《领域驱动设计:软件核心复杂性应对之道》一书的最后一个部分(第四部分:战略设计)。

《领域驱动设计:软件核心复杂性应对之道》

他的原意是:

战略设计:一种针对系统整体的建模和设计决策。这样的决策影响整个项目,而且必须由团队来制定。

在Eric Evans的书中,战略设计会优先关注限界上下文以及限界上下文之间的协作关系,然后通过精炼的方式得到核心域和通用域等“模式”。而在整个书中,其实有关于统一语言和建模的细节都是在全书的前三个部分讨论的,

也就是说,在最初的这本书中,并没有“战术设计”一词,而Eric Evans是先说的“战术”后说的“战略”。因为他认为:

随着系统的增长,它会变得越来越复杂,当我们无法通过分析对象来理解系统的时候,就需要掌握一些操纵和理解大模型的技术了。本书的这一部分将介绍一些原则。遵循这些原则,就可以对非常复杂的领域进行建模。大部分这样的决策都需要由团队来制定,甚至需要多个团队共同协商制定。这些决策往往是把设计和策略综合到一起的结果。

换句话说,其实他先是介绍的“简单”的系统的领域驱动设计和建模方法,而在最后才介绍的是“复杂”的系统的领域驱动设计和分析方法。

Vaughn Vernon的发展

2013年,Vaughn Vernon在实施了很长时间以“实现领域驱动设计”为核心内容的工作坊之后,出版了《实现领域驱动设计》一书,在这本书中,他按照从宏观到微观,从问题到方案再到实现的方式进行了叙述,发展并完善了DDD的理论和概念,当然也引入了“问题空间”和“解决方案空间”这两个词。

《实现领域驱动设计》

他认为,在DDD的“战略设计中”,还需要考虑“问题空间”和“解决方案空间”这样的维度,其中子域(他在“核心域”和“通用域”之间加入“支撑域”来避免两个极端)属于“战略设计”对于“问题空间”的分析方式,而限界上下文则是属于“战略设计”对于“解决方案空间”的分析方式。

至于“战术设计”一词,hum……当时还没出现呢……

那么“战术设计”这个词,到底是什么时候才出现的呢?

Scott Millett与Nick Tune被“忽略”的澄清

说实话,“战术设计”这个大家看到现在基本都已经感受到是什么了的东西,到底什么时候由谁提出的,已经难以追溯,但是,至少有一本书出现了……

在2015年,Scott Millett和Nick Tune出版了《领域驱动设计模式、原理与实践》一书,这本书更加全面和细致的介绍了在实现DDD的过程中的思考和方法。

《领域驱动设计模式、原理与实践》

然而很可惜的是,这一本书的中文版已经“绝版了”,说明至少在中国,大家的注意力都被《实现领域驱动设计》带跑了,逢人便提IDDD(书名的缩写),却很少有人提到前述的这一本(当然,我强烈建议对照看英文版,中文版翻译的实在是……)。

在这本书中,作者将全书分为了四个部分:

  1. 领域驱动设计的原则与实践
  2. 战略模式:在限界上下文之间通信
  3. 战术模式:创建有效的领域模型
  4. 有效应用程序的设计模式

至此,在DDD的系列书籍中,终于出现了“战术设计/模式”一词,Scott Millett和Nick Tune把领域模型的设计全部清晰的纳入到了“战术设计”之中。

为什么大多数人会难以理解?

好吧,大家能看到这里已经很不容易了,让我来为大家做总结并回答一下为什么大多数人会难以理解前述的两个维度的概念呢?

我是这么看的,之所以难以理解,问题来自于两个方面,让我们分别来说。

DDD“依然年轻”

DDD虽然从2004年开始,截止到刚刚过去的2019年,已经有15年的发展,但是15年来,虽然软件架构和设计的新思想层出不穷,复杂度也越来越高。

直到2014年,以Martin Fowler为代表,在博客上彻底点燃微服务设计这个“燎原之火”之后,微服务所带来的软件系统的复杂度成倍提升才使得人们又重新关注,并开始根据新的形势认真思考DDD如何落地(到如今,几乎逢领微服务设计的书,必谈DDD)。

从前面的介绍和几本书的内容发展我们可以看到,DDD思想发展的时间跨越很大,语言和理解的统一也并不顺畅,很多概念的清晰化都是最近几年才密集出现。

尤其在中国,发展更为滞后。这个圈子里,有机会深入思考和深耕DDD规模化落地的人也不多。

概念理解有难度

让我们回顾这些概念和抽取其中的关键词语,我们会看到这两波东西是完全不同维度的东西:

  • 解决问题的层次:战略、战术
  • 解决问题的步骤:问题、方案、实现

之所以难以理解,是因为这些概念在现有的书籍、文章和实际操作中,是交织在一起的(可以想象一个概念的“大泥球”),而绝大多数人并不擅长(或者说并不熟练)以下三种思维方式:

  • 问题驱动的思考
  • 分层思维
  • 多维思考

所以我们需要甩开这些概念上的反复纠缠,以更加清晰的阶段划分和渐进式的方法来降低门槛,从而让大多数人能够更加容易的理解和入门。

更合理的“DDD分段式设计”

为了更好的对DDD的设计过程进行优化,我们必须要重新审视DDD希望解决的问题:

软件核心复杂性

所谓复杂(Complex),按照Cynefin框架的解释是这样的(这里使用了我的同事李彤欣的版本):

“复杂系统:代表可能有,也可能没有解决方案的系统,充满未知和不确定性。因果关系往往在事后才能感知,刚开始可能毫无头绪。应对方式是探索 - 感知 - 响应。在允许试错的前提下,先做小范围实验和尝试,等待某些规律和指导涌现出来后,再来认知和评估,然后响应。”

而从我的观察和理解来说,这种复杂问题最直接的影响就是:其复杂程度已经超出了个人所能够理解、分析和解决的范围。我总结了三个针对“复杂性”的典型特征:

  • 业务流程长
  • 业务场景多
  • 业务概念多

要想解决复杂的问题,首当其冲的不是如何进行分析,而是引入更多的人,利用更多的大脑来共同解决复杂问题。当从一个人变成多个人的时候,那么问题就来了:别人的大脑长在别人的脑袋里,我怎么知道他想的是什么?他所理解的和我理解的一样吗?

正因为多人共同做事情时,会出现沟通和理解上的障碍,所以,DDD给出了一个关键思想:统一语言(Ubiquitous Language)

而统一语言的方式,则是通过领域专家与技术专家通力合作,针对业务问题进行协作分析和设计,通过迭代式的探索、发现和碰撞得来的

所以,以统一语言为核心,通过协作设计的方式,对业务问题进行分析和澄清,就应该是DDD设计过程的第一个阶段。在这个阶段中,我们围绕业务问题、场景和流程来进行探索,通过限界上下文寻找概念二义性的边界,划分问题子域,从而在宏观(战略)层面降解系统理解上的复杂度。

我将这第一个阶段称之为:DDD分段式协作设计的战略设计阶段

在这个过程中,我们首先需要使用“事件风暴工作坊”的方式,对业务流程进行系统实现视角下的抽象分析。然后,根据分析的结果,对领域概念的二义性边界进行识别,最终划定问题子域。

DDD分段式协作设计:战略设计

当这一宏观层的分析和设计结束后,我们就需要降低(细化)一个层面,对问题子域和限界上下文内部进行更细化的分析和抽象,通过领域建模的方式,将业务抽象为以“聚合(Aggregate)”进行封装和隔离的一系列领域模型进行承载。然后,基于系统实现的高阶视角出发,参考云原生的弹性边界需求等输入,初步识别出业务服务的划分,并且识别每个业务服务的对外接口能力。

我将这第二个阶段,称之为:DDD分段式协作设计的战术设计阶段

DDD分段式协作设计:战术设计

需要注意的是,在前两个阶段,我们都依然是通过从宏观到微观(从战略到战术)的方式,对业务问题进行了分层的分析和设计。所以在这两个阶段中,我们是忽略了一切技术实现细节,以防止技术实现细节干扰“领域驱动”的。

同时,由于“协作设计”需要投入一定的人天进行集中式的封闭设计,所以在成本上难以长时间进行投入,而技术实现的维度和方式又是种类繁多和及其详细的(例如API详细设计、UML设计、数据库设计、运维与部署方案……等等)。

所以,可以将前两个阶段的产出,交由根据“逆康威定律”划分的不同特性/产品团队,由这些团队进行进一步的详细分析和设计,最终予以实现。

我将这第三个阶段,称之为:DDD分段式协作设计的技术实现阶段

DDD分段式协作设计:技术实现

至此,便有了更加平顺的DDD设计方法。

总结

根据实践中的思考,以“统一语言”为解决复杂问题的核心目标,以“协作设计”为关键手段,我将DDD的设计过程重新梳理和优化为了以下三个阶段:

  1. 战略设计阶段:对业务问题进行宏观分析和设计。
  2. 战术设计阶段:根据战略设计分析结果进行细化和建模。
  3. 技术实现阶段:根据实现需要进行细化分析和设计。

DDD分段式协作设计总览

在这个过程中,主要的改进方式,首先就是利用分层的手法,通过聚焦从宏观到微观再到具体的方式,降低对抽象的分类方法的关注,化解对两类概念维度的交叉。然后,再通过定义每个阶段要解决的问题,内聚相关的分析方法,使得每个阶段都能有一些在阶段内闭环性的产出。

这样的话,因为阶段目标清晰且产出内聚,所以可以根据实际需要选择协作设计的深入程度——我遇到过有些客户,因为种种原因,只希望能够先快速的知道我需要根据DDD的思想拆分多少个模块,那么就可以先只做战略设计阶段。如果其中某个模块希望进一步细化指导建模和接口设计,那么就可以继续局部进入到战术设计阶段。最后,再根据实际需要去分团队进行技术实现。

我们一定要记住:DDD是通过协作来达成统一语言从而应对复杂性的,组织背景下的软件开发是一种团队集体运动

所以基于以上观点,我会比较刻意的不提倡以下几种DDD的设计过程:

  • 先分析子域 / 先分析限界上下文:业务都未澄清,理解和语言都还没统一,怎么能拆明白?
  • 名词动词法 / 直接进行领域建模:大规模吵架和拍脑袋……

对于以上的分段式设计方法,经过了一年多的实战检验,证明能够被在绝大多数场景下适配和快速复用。在这里给大家分享了我在这个过程中的思考,抛砖引玉,希望能够有更多DDD的推动者提出宝贵的建议,共同推动DDD在中国的成熟和发展。

参考资料


欢迎关注我的个人公众号

微信搜索:枪炮与代码,或者搜索公众号ID:guns_n_code

枪炮与玫瑰