- 领域驱动设计:业务建模与架构实践
- 王红亮
- 5197字
- 2024-03-04 17:23:28
1.1 DDD的初心
本节首先讨论软件项目成败的关键,引出两个我们一直忽略却很重要的事实,这两个事实构成我们下一步讨论领域驱动设计基本原则的理论基础。
1.1.1 软件项目成败的关键
开发一个软件系统时,我们究竟在做什么?哪些因素决定了项目的成败?大致归纳一下,无非是以下三件事:
1)构建人机接口。所有的系统都是为人服务的,人机接口的事似乎没有办法省掉。它一般承担收集输入和展示信息两项任务。当然,你的系统也可能服务于其他的系统而不是人,那么人机接口就变成了接口。但是在每个调用链条的最后,必然都是有人机接口的。这构成了我们开发工作的第一部分。
2)打造系统的生存环境。更一般的表达是“与基础设施打交道”,这项工作的本质是为系统在这个客观世界的存在创造物质条件,包括持久化、驱动硬件、网络通信等任务。选择越高级的语言和平台,这一部分要承担的任务就越少,反过来,开发者在这一部分可能就需要花大量工夫。
3)构建软件的灵魂。这绝不是故弄玄虚,仔细想想,在我们打造了系统的生存环境之后,接下来就是系统的灵魂——那些系统所特有的、区别于别的系统的东西。那么,它是什么呢?它是系统的业务逻辑。
对于前两件事,开发人员都不陌生,他们可以轻松地把日常工作归类到其中的一项,甚至工程师也被明确地分为“前端”和“后端”。但是对于第三件事,有的人可能得犹豫一下。“你是在开发业务逻辑吗?”“哦,你是说写代码?没问题,我的代码能完成那些任务。”这段对话听起来再自然不过,但是它正常吗?稍后你自己就会有答案。
现在回到项目成败的问题。构建人机接口会导致项目的失败吗?几乎不可能。这一部分的构建有一个明显的特点——用户的反馈是最快、最及时的,你根本不可能在错误的道路上走很久。他们会把清晰的需求画给你,甚至所有的交互细节和布局。而且,另一个隐性的原因是,用户交互这方面的需求其实是被技术的发展所训练出来的,只有接触过的产品,他们才会提出类似的需求。用户只会用命令行而不是VR眼镜来操作DOS系统就是这个道理。既然如此,这个问题就变得简单了,其复杂性只在于你有没有相关经验的技术人才,只要舍得花钱招聘,这个问题就解决了。
打造系统的生存环境,即构建基础设施会导致项目失败吗?这个可能性微乎其微。所有的开发框架、操作系统、中间件产品都经过成千上万项目的检验,问题不可能单独出现在你的项目上。退一步讲,开发团队在尝试某种新技术(比如新的PaaS)时,可能它还不够成熟,那么即使出了问题,也非常容易测试和定位。及早更换或者等待补丁,只要你不优柔寡断,它不可能给你带来问题。
那么是什么导致如此多的项目失败呢?据统计,软件系统建设项目的成功率均在30%以下,超过70%的项目由于项目延期、超出预算、功能缺失等失败甚至取消。站在开发团队的角度,这个数字可能感觉难以置信,但是站在客户的角度,这个数字绝对没有夸张的成分。
答案只剩下了最后一个选项——构建业务逻辑,这也是唯一正确的答案。问一个简单的问题你就明白了——什么使你的系统与众不同?是因为它的业务逻辑不同、所在的领域不一样、传递的商业价值有差别,还是因为你选择的开发语言和数据库不同?答案是不言而喻的。系统中最复杂也是最模糊的地方就是领域逻辑,这是最需要投入工作量的地方。之所以有的项目成功,有的项目失败,是因为它们解决“如何构建领域逻辑”这个问题的重视程度和做法不同。而失败的项目无一例外都是在这个环节出了问题。
业务逻辑不就是一些if…else的逻辑开关吗?这比搭建一个高可用架构简单多了。有些开发者可能有不同意见,他们可能会说,不是构建业务逻辑出了问题,而是它们与技术的结合,也就是实现环节出了问题。那么,接下来的问题就清晰了,我们构建业务逻辑为什么要受到技术因素的影响呢?这合理吗?难道它不是独立于技术而存在的吗?
只要跳出技术思维的窠臼(本质上是技术人员对掌握新技术的焦虑感),你就会发现,我们一直没有认真地考虑过构建业务逻辑这个问题,或者说脱离技术实现思考过这个问题。有一些显而易见的事实一直在我们眼前,却一直被忽视。
1.1.2 两个亟须验证的事实
(1)第一个事实
既然成败的关键在业务逻辑,那么如此高的失败率是因为业务逻辑很复杂吗?是,也不是。毕竟再复杂的业务,在它所在的领域里也是由人来完成的,开发人员的智商应该比较高,没有理由理解不了。出问题的原因并不是业务复杂度本身,而是我们不合理的做法——我们构建的软件复杂度远远超过了业务本身的复杂度。
这句话是什么意思呢?我们来看下面这个熟悉的对话:
业务人员:“我这里有一个新的需求,业务上只是微调,不需要大的改动,你看能不能……”
开发人员:“这个需求实现难度太大了,做不了,因为……”(一串实现逻辑的解释和技术术语。)
业务人员惊讶地张大了嘴巴。
从业务人员的角度来看,开发人员的说法很难理解。因为从业务的角度来看,逻辑并没有发生大的变化,只是一个小小的调整。但是,开发人员明白是怎么回事——因为开发人员并没有按照业务的方式来组织程序,而是另外有一套设计。虽然这个设计能够满足之前的需求,但这个设计模型和业务人员头脑中的模型并不是一回事,对于改动大小的度量也完全不一样,一个看起来小小的需求改动,实现起来可能是一个大工程。需求到代码之间,业务专家并不知道发生了什么。另一个与之类似的场景是在敏捷开发中,产品经理面对开发人员给出的那些虚高的故事点数,却无法质疑。
为什么软件会变得比业务逻辑还复杂,实现模型会比业务模型还麻烦?首先,业务中的复杂关系是不可能在设计中省略的,就好像加密的信息并不会比原始信息量更少一样,前者的复杂度不会比后者低。比如,有多少个关注点、多少条路径、多少组对应关系,都会体现在设计和代码中。如果设计和实现在业务模型上又包裹了一层新的设计,添加了技术组件,那么至少多了一层多对多的对应关系,这就产生了额外的复杂度。
如图1-1所示,在业务模型上包裹的每一层设计,都会使复杂度增加一分。这里的设计包括从需求沟通的语言到需求说明书、从需求说明书到概要设计、从概要设计到详细设计的每个环节,因为信息每一次从上层流转到下一层都会被二次包装和转译。
图1-1 开发过程中的熵增
有些读者可能会认为,业务模型可能是一个形而上的概念,它并不客观存在,而是在开发过程中逐渐变得清晰。但即使如此,我们仍然需要区分“逐步清晰”是发生在领域专家的头脑中还是发生在你的设计中。如果领域专家头脑中逐渐清晰的模型和你的设计不匹配,那么这种复杂度的增加并没有消失。
此外,有些开发人员会说他们的设计简化了业务逻辑,降低了业务的复杂度。这是一种常见的说法,但恕我直言,这只是一种误解。并非你的系统颠覆了之前的业务逻辑,而是用户自己决定这么做。并非你的系统改变了用户的行为,而是改变的决策由你的系统来体现。你能想象用户脑子里一片混乱,最后上线的程序颠覆了行业逻辑的情形吗?这里面的因果关系不能弄反了。如果用户没有对应的期望,而你的系统给他带来了“惊喜”,相信我,这并不会是一个好的结果,很可能你在做无用功。因此,系统的复杂度只能大于或接近于业务的复杂度,而不可能更低。
那么,既然设计会带来额外的复杂度,我们是不是应该不设计呢?不要急,事实上,所有的实现都是基于一定的设计的,无论它是规范的还是下意识的。我们想要说明的是:不同的设计引入的复杂度并不相同,设计并不是越高大上越好,而是应该从它引入的复杂度上优先考虑。
现在我们得出结论,任何系统设计的复杂度只能大于或等于业务的复杂度。那么我们想问:是否存在一种设计方法,可以实现系统复杂度最小化,也就是和业务的复杂度保持在一个水平,而不需要额外的包装和转译,从而避免增加不必要的复杂度?
如果有这种方法,那么也可以保证软件可以随着业务的发展而自然演进,不会深陷不必要的复杂度的泥潭,失去灵活性,也不会再出现由于技术原因而无法满足简单需求的情况。
进一步来讲,研发团队想求证这样一个事实:我们可以基于业务本身的复杂度来构建软件,而不需要更复杂的方法。
(2)第二个事实
除了设计增加的复杂度,传统的设计方式往往还有另外一个严重的问题——不能够分离业务复杂度和技术复杂度,即我们无法“单独”构建领域逻辑。
这是什么意思呢?请看下面常见的对话。
领域专家:我可以验证一下业务逻辑的实现是否正确吗?
开发人员:很抱歉,现在还不行,因为:第一,有个数据库脚本的编译出现了问题;第二,我们的云平台资源还未就绪;第三,旧的系统还未集成,缺少测试数据;第四,还有个浏览器的兼容性错误没修复。(可以选一个回答,或者都用上。)
领域专家瞪大了眼睛。
只要在开发团队中待过的人,上面的对话就是耳熟能详的。但仔细想一想,其实是荒谬的,列举的原因中哪一个和业务逻辑有关系呢?
业务复杂度叠加技术复杂度的结果如图1-2所示。更多关于两种复杂度的定义稍后还会讨论。
图1-2 传统的工作方式与软件复杂度
传统项目中,我们协调领域专家加入团队,通过一两次集中的会议来向团队讲解领域知识。之后,开发团队并不能继续利用这个资源。因为没有分离业务复杂度与技术复杂度,业务逻辑深耦合在各个技术组件中,领域专家无法也没有能力了解其中的关系,也就无法及时给出反馈。我们只有在开发出第一个版本并部署到测试环境后,才能再把领域专家重新请回来,得到反馈和验证。而这个环节,实际执行过程中还会被各种技术原因所干扰、打断,领域专家“王者归来”的机会少得可怜,只能由测试人员代行其职。
在有产品经理的敏捷团队中,情况可能稍好一些——可以在开发或测试环境验证需求,这已经比等到系统发布快捷多了。这导致了支持快速发布的方法论的大流行,比如DevOps。但依然没有人怀疑过其中的不合理性——为什么业务逻辑的验证要依赖那么多不相关的技术因素?
难道还能单独地开发业务逻辑吗?是的,这从道理上是说得过去的,只要我们找到合适的载体,完全可以单独构建领域逻辑而不用关心系统采用什么技术。这里并非说撇开编程语言谈构建业务逻辑(事实上,面向对象语言就是我们之后提到的载体),而是说业务逻辑不再和不相关的技术因素相耦合,比如界面、基础设施、既有的技术组件。同时也意味着业务逻辑不应该散布在技术组件的丛林里,它应该有自己的独立性和完整性。
既然如此,那么我们想问:能不能找到一种设计方法,业务逻辑可以单独被构建、测试和验收,而不依赖于系统架构的其他部分?
如果有这种方法,那么团队就可以专心应对领域的复杂度,而不被技术复杂度所影响。更大的好处在于我们得到了一个强大的业务逻辑模型,它可以被快速测试和验证,甚至在不同的应用中重用这一逻辑模型。
更进一步,研发团队想求证这样一个事实:我们能单独构建、测试和验收业务逻辑,而不依赖于系统架构的其他部分。
(3)关于两个事实的进一步论述
我们对上述看似烧脑的论述作一个小结:项目成败的关键因素取决于业务逻辑能否构建成功,进一步来讲取决于业务领域逻辑采用的方法。
开发团队希望求证的事实是:第一,我们能找到一种方法,可以基于业务本身的复杂度来构建系统的逻辑而不需要更复杂的方法;第二,我们可以单独构建、测试和验收领域逻辑而不依赖于系统架构的其他部分。
为什么一本讲DDD的书要讨论这些话题呢?因为它们和我们接下来要讲的DDD的基本原则相关。DDD的所有战略战术都是为了维护其基本原则,而基本原则是为了保证我们能按上述两个事实来开发软件。它们是DDD的底层逻辑。也就是说,坚持DDD的基本原则和它的一套战略战术,最终得到的收益是:你可以基于业务本身的复杂度来设计软件,你的设计不会比你和领域专家沟通的逻辑更复杂,因为语言与设计甚至与代码是一致的;你可以独立于技术架构,单独开发、测试和验收领域逻辑,因为领域层是独立且内聚的。
如果你读过Evans的原著,就知道他并没有如此深入地提到这两个事实,或者强调DDD的基本原则。他只告诉了读者应该怎么去做,这些做法被他冠以“模式”的标签,比如原著中标题采用“模式:通用语言”“模式:分离领域”等形式。所谓模式就是大家可以效仿的套路。这可能是DDD落地困难的原因之一,因为读原著需要具备天才的思维能力来理解Evans想说但没有说出的话,需要从他举的众多案例中理解DDD背后的道理。
本书会把“所以然”展示给大家,将DDD的底层逻辑清晰化。这样做的目的,一方面是对Evans天才型创新性思维的赞赏,他想说但没有说出来的话应该被更多从业者听到;最重要的另一方面是避免许多团队走上错误应用DDD的道路,为DDD正名。许多团队只知道套路而不知其所以然,往往是南辕北辙。例如,应用DDD却没有通用语言,因为不理解其功用。应用各种战术模式,领域层却无法独立开发、测试和验收。单元测试是如此强大的工具,却不知道如何使用。这些实践是否成功地应用了DDD?显然没有。没有了原则衡量标准,团队就无法获得相应的收益。如果因此怀疑DDD的价值,那将是天底下最大的误解,这是最可惜的。
下面就让我们来了解DDD的基本原则。随着学习的深入,对于上述观点也会有更深入的认识。