领域驱动战略设计实践

领域驱动战略设计实践_第1张图片领域驱动战略设计实践_第2张图片

课程内容

访谈录 | 聊聊领域驱动设计(文字版)

相信很多朋友对领域驱动设计会有这样或那样的困惑,比如领域驱动设计是什么?它在工作中有什么作用?为什么国内关于这方面的书籍少之又少?…… 为了解决这些疑惑,有幸邀请到专家张逸老师来聊聊领域驱动设计,下面是 GitChat 独家采访记录。

GitChat:领域驱动设计(Domain Driven Design,DDD)自诞生以来已有十几年时间,这门本已步入老年的方法学却因为微服务的兴起而焕发了第二春。您说过这可能要归功于 DDD 的“坚硬生长”,但不可否认微服务确实也是一个重要因素,能否请您解释一下领域驱动设计微服务这种深层次的匹配关系?

张逸:领域驱动设计是由 Eric Evans 在一本《领域驱动设计》书中提出的,它是针对复杂系统设计的一套软件工程方法;而微服务是一种架构风格,一个大型复杂软件应用是由一个或多个微服务组成的,系统中的各个微服务可被独立部署,各个微服务之间是松耦合的,每个微服务仅关注于完成一件任务并很好地完成该任务。

两者之间更深入的关系,在我写的课程中已有详细讲解。主要体现在领域驱动设计中限界上下文与微服务之间的映射关系。假如限界上下文之间需要跨进程通信,并形成一种零共享架构,则每个限界上下文就成为了一个微服务。在微服务架构大行其道的当今,我们面临的一个棘手问题是:如何识别和设计微服务?领域驱动的战略设计恰好可以在一定程度上解决此问题。

GitChat:如果说轻量化处理、自动部署,以及容器技术的发展使得微服务的兴起成为必然,那么是否可以说领域驱动设计今日的再续辉煌也是一种必然(或者说 DDD 在其诞生之时过于超前)?您能否预测一下 DDD 未来可能会和什么样的新理念相结合?

张逸:好像领域驱动设计就从未真正“辉煌”过,所以谈不上再续辉煌,但确实是因为微服务引起了社区对它的重燃热情。推行领域驱动设计确乎有许多阻力,一方面要做到纯粹的领域驱动设计,许多团队成员的技能达不到;另一方面,似乎领域驱动设计带来的价值不经过时间的推移无法彰显其价值,这就缺乏足够的说服力让一家公司不遗余力地去推广领域驱动设计。微服务似乎给了我们一个推动领域驱动设计的理由!因为软件系统的微服务化已经成为了一种潮流,领域驱动设计又能够为微服务化保驾护航,还有什么理由不推行呢?

我个人认为,未来 DDD 的发展可能会出现以下趋势:

  • 以函数式编程思想为基础的领域建模理念与事件驱动架构和响应式编程的结合,可能在低延迟高并发的项目中发挥作用。这种领域驱动设计思想已经比较成熟,但目前还没有看到太多成功的运用。
  • 以 DDD 设计方法为基础的框架的出现,让微服务设计与领域建模变得更加容易,降低领域驱动设计的门槛。

GitChat:能否尽可能地详细(或举例)说明您在阅读并审校《实现领域驱动设计》一书时所认识到的领域驱动设计的本质—— 一个开放的设计方法体系 ——是什么?

张逸:在《实现领域驱动设计》一书中,Vernon 不仅对整个领域驱动设计过程作了一番有益的梳理,还结合社区发展在书中引入了六边形架构和领域事件等概念,这为当时的我打开了一扇全新的窗户——原来领域驱动设计并不是一套死板的方法,而是一种设计思想、一种开放的设计方法体系,只要有利于领域驱动设计的实践,都可以引入其中。于是,在我的书中我才敢于大胆地引入用例、敏捷实践、整洁架构,以期为领域驱动设计提供补充。

Eric Evans 的《领域驱动设计》是以面向对象设计作为模型驱动设计的基础,但时下被频繁运用的函数式编程思想也给模型驱动设计带来了另一种视角。从开放的设计方法体系的角度讲,我们完全可以把更多的编程范式引入到领域驱动设计中。因为有了更多的选择,针对不同的业务场景就可以选择更适合的 DDD 实践,而不仅仅限于 Eric Evans 最初提出的范畴。

点击了解《领域驱动战略设计实践》。

GitChat:团队内外成员之间的协作与沟通一直以来都是个难题,也是大家经常喜欢调侃的话题之一,能否举例说明一下领域驱动设计是如何解决这一问题的?

张逸:我觉得这个问题问反了。领域驱动设计解决不了这个问题,它只是重视这个问题;相反,我们应该说只有解决了团队内外成员之间的协作与沟通,才能更好地进行领域驱动设计。为此,我尝试用一些敏捷实践来解决这种协作问题。

GitChat:您在学习和实践领域驱动设计的过程中是否有哪些(有趣的)故事可以和读者们分享?

张逸:我在 ThoughtWorks 的时候,公司邀请《实现领域驱动设计》作者 Vaughn Vernon 到北京 Office 给我们做了一次 DDD 培训。借着这次亲炙大师教诲的机会,我向他请教了一个一直缠绕在我心中困惑不解的问题:“如何正确地识别限界上下文?”结果他思考了一会儿,严肃地回答了我:“By experience!” 我唯有无言以对。

GitChat:有很多读者对您即将在课程中给出全真案例“EAS 系统”很感兴趣,能否简单介绍一下这个案例以及它在实际应用中的意义?

张逸:EAS 系统是我之前做过的一个真实项目,之所以选择这个项目来作为这个课程的全真案例,原因如下:

  • 学习 DDD 必须理论联系实际。虽然在我写的课程内容中已经结合理论讲解提供了较多的实际案例,但这些零散的案例无法给读者提供一个整体的印象。
  • EAS 系统的业务知识门槛相对较低,不至于因为不熟悉领域知识而影响对 DDD 的学习。
  • EAS 系统具备一定的业务复杂度,既适合战略设计阶段,又适合战术阶段。

GitChat:您提到这次的 DDD 系列课程分为《领域驱动战略设计实践》和《领域驱动战术设计实践》两部分,这两个课程在内容设计上侧重有什么不同?很多读者关心《领域驱动战术设计实践》何时发布,可否透露一下?

张逸:这两个课程对应于 DDD 的战略设计阶段与战术设计阶段,粗略地说,前者更偏向于架构,后者更偏向于设计与编码。事实上,就我个人的规划来说,计划还有第三部分,是围绕着函数式编程讲解与 DDD 有关的实践,包括 EDA、CQRS、Domain Event 等知识。

目前,《领域驱动战略设计实践》还有最后几个章节没有完成。一旦完成后,就可以开始撰写《领域驱动战术设计实践》内容了。当然,战术设计的相关内容已有部分初稿,我争取能够在 11 月发布这部分内容。

GitChat:您觉得这门课的学员/读者应该是什么样的人?对于这些人,要想掌握领域驱动设计乃至在专业领域更上一层楼,您有哪些学习建议?

张逸:学习课程的学员/读者最好要有一定的软件设计能力,并对 DDD 学习抱有好奇心,希望能够将 DDD 学以致用。

学习建议:

  • 积累领域知识,以提高沟通与协作能力;
  • 以 Eric Evans 的《领域驱动设计》为主体,广泛涉猎与 DDD 相关的书籍与文章,并关注 DDD 社区的最新知识;
  • 要善于总结,理清 DDD 中各个概念之间的区别与应用场景。

GitChat:作为一位曾就职于中兴、惠普、中软、ThoughtWorks 等大型中外企业的架构师/技术总监/首席咨询师,在职业发展方面,您对您的读者们有哪些建议?

张逸:我之前在 ThoughtWorks 的同事郑烨(校长)给我提过一个建议,就是打造自己的技术标签。例如,现在 DDD 就成为了我其中的一个技术标签了。这个说法的内在含义,就是要寻找和定位自己的技术发展方向,然后往更深的方向钻研,最终成为这个方向的技术专家。因此,结合自己的能力特长、兴趣点以及技术发展趋势去规划自己的技术发展方向,才是技术人员最应该思考并践行的。

到这里,访谈录的内容就结束了,大家若对 DDD 的内容比较感兴趣的话,欢迎订阅本课程,有任何关于本课程内容的疑惑可在读者圈留言给张逸老师,张老师会很积极的解答每一个问题~

分享交流

我们为本课程付费读者创建了微信交流群,以方便更有针对性地讨论课程相关问题。入群方式请到第 04 课末尾添加小编的微信号,并注明「DDD」。

阅读文章过程中有任何疑问随时可以跟其他小伙伴讨论,或者直接向作者提问(作者看到后抽空回复)。你的分享不仅帮助他人,更会提升自己。

点击了解《领域驱动战略设计实践》。

开篇词 | 领域驱动设计,重焕青春的设计经典

课程背景

领域驱动设计确实已不再青春,从 Eric Evans 出版的那本划时代的著作《领域驱动设计》至今,已有将近十五年的时间,在软件设计领域中,似乎可以称得上是步入老年时代了。可惜的是,对于这样一个在国外 IT 圈享有盛誉并行之有效的设计方法学,国内大多数的技术人员却并不了解,也未曾运用到项目实践中,真可以说是知音稀少。领域驱动设计似乎成了一门悄悄发展的隐学,它从来不曾大行其道,却依旧顽强地发挥着出人意料的价值。

直到行业内吹起微服务的热风,人们似乎才重新发现了领域驱动设计的价值,并不是微服务拯救了领域驱动设计,是因为领域驱动设计一直在坚硬的生长,然而看起来,确乎因为微服务,领域驱动设计才又焕发了青春。

我从 2006 年开始接触领域驱动设计,一开始我就发现了它的魅力并沉迷其间。从阅读 Eric Evans 的《领域驱动设计》入门,然后尝试在软件项目中运用它,也取得了一定成效。然而,我的学习与运用一直处于摸索之中,始终感觉不得其门而入,直到有机会拜读 Vaughn Vernon 出版的《实现领域驱动设计》一书,并负责该书的审校工作,我才触摸到了领域驱动从战略设计到战术设计的整体脉络,并了解其本质:领域驱动设计是一个开放的设计方法体系

即使如此,许多困惑与谜题仍然等待我去发现线索和答案。设计总是如此,虽然前人已经总结了许多原则与方法,却不能像数学计算那样,按照公式与公理进行推导就一定能得到准确无误的结果。设计没有唯一的真相。

即使如此,如果我们能够走在迈向唯一真相的正确道路上,那么每前进一步,就会离这个理想的唯一真相更近一步,这正是我推出这门课的初衷。也并不是说我贴近了唯一真相,更不是说我已经走在了正确道路上,但我可以自信地说,对于领域驱动设计,我走在了大多数开发人员的前面,在我发现了更多新奇风景的同时,亦走过太多荒芜的分岔小径,经历过太多坎坷与陷阱。我尝试着解答领域驱动设计的诸多谜题,期望能从我的思考与实践中发现正确道路的蛛丝马迹。我写的这门课程正是我跌跌撞撞走过一路的风景拍摄与路径引导,就好似你要去银河系旅游,最好能有一本《银河系漫游指南》在手一样,不至于迷失在浩瀚的星空之中,我期待这门课程能给你带来这样的指导。

课程框架

本课程是我计划撰写的领域驱动设计实践系列的第一部分内容(第二部分内容是领域驱动战术设计实践,后面陆续更新),其全面覆盖了领域建模分析与架构设计的战略设计过程,从剖析软件复杂度的根源开始,引入了领域场景分析与敏捷项目实践,帮助需求分析人员与软件设计人员分析软件系统的问题域,提炼真实表达的领域知识,最终建立系统的统一语言。同时,本课程将主流架构设计思想、微服务架构设计原则与领域驱动设计中属于战略设计层面的限界上下文、上下文映射、分层架构结合起来,完成从需求到架构设计再到构建代码模型的架构全过程。

本课程分为五部分,共计 34 篇。

开篇词:领域驱动设计,重焕青春的设计经典

第一部分(第01~05课):软件复杂度

  • 领域驱动设计的目的是应对软件复杂度。本部分内容以简练的笔触勾勒出了领域驱动设计的全貌,然后深入剖析了软件复杂度的本质,总结了控制软件复杂度的原则,最终给出了领域驱动设计应对软件复杂度的基本思想与方法。

第二部分(第06~10课):领域知识

  • 领域驱动设计的核心是“领域”,也是进行软件设计的根本驱动力。因此,团队在进行领域驱动设计时,尤其需要重视团队内外成员之间的协作与沟通。本部分内容引入了敏捷开发思想中的诸多实践,并以领域场景分析为主线讲解了如何提炼领域知识的方法。

第三部分(第11~20课):限界上下文

  • 限界上下文是领域驱动设计最重要的设计要素,我们需要充分理解限界上下文的本质与价值,突出限界上下文对业务、团队与技术的“控制”能力。
  • 提出了从业务边界、工作边界到应用边界分阶段分步骤迭代地识别限界上下文的过程方法,使得领域驱动设计的新手能够有一个可以遵循的过程来帮助识别限界上下文。
  • 剖析上下文映射,确定限界上下文之间的协作关系,进一步帮助我们合理地设计限界上下文。

第四部分(第21~28课):架构与代码模型

  • 作为一个开放的设计方法体系,本部分引入了分层架构、整洁架构、六边形架构与微服务架构等模式,全面剖析了领域驱动设计的架构思想与原则。
  • 结合限界上下文,并针对限界上下文的不同定义,对领域驱动的架构设计进行了深度探索,给出了满足整洁架构思想的代码模型。

第五部分(第29~33课):EAS 系统的战略设计实践

  • 给出一个全真案例——EAS 系统,运用各篇介绍的设计原则、模式与方法对该系统进行全方位的战略设计,并给出最终的设计方案。

本课程并非是对 Eric Evans《领域驱动设计》的萧规曹随,而是吸纳了领域驱动设计社区的各位专家大师提出的先进知识,并结合我多年来运用领域驱动设计收获的项目经验,同时还总结了自己在领域驱动设计咨询与培训中对各种困惑与问题的思考与解答。本课程内容既遵循了领域驱动设计的根本思想,又有自己的独到见解;既给出了权威的领域驱动知识阐释,又解答了在实践领域驱动设计中最让人困惑的问题。

为什么要学习领域驱动设计

如果你已经能设计出美丽优良的软件架构,如果你只希望脚踏实地做一名高效编码的程序员,如果你是一位注重用户体验的前端设计人员,如果你负责的软件系统并不复杂,那么,你确实不需要学习领域驱动设计!

领域驱动设计当然并非“银弹”,自然也不是解决所有疑难杂症的“灵丹妙药”,请事先降低对领域驱动设计的不合现实的期望。我以中肯地态度总结了领域驱动设计可能会给你带来的收获:

  • 领域驱动设计是一套完整而系统的设计方法,它能带给你从战略设计到战术设计的规范过程,使得你的设计思路能够更加清晰,设计过程更加规范。
  • 领域驱动设计尤其善于处理与领域相关的高复杂度业务的产品研发,通过它可以为你的产品建立一个核心而稳定的领域模型内核,有利于领域知识的传递与传承。
  • 领域驱动设计强调团队与领域专家的合作,能够帮助团队建立一个沟通良好的团队组织,构建一致的架构体系。
  • 领域驱动设计强调对架构与模型的精心打磨,尤其善于处理系统架构的演进设计。
  • 领域驱动设计的思想、原则与模式有助于提高团队成员的面向对象设计能力与架构设计能力。
  • 领域驱动设计与微服务架构天生匹配,无论是在新项目中设计微服务架构,还是将系统从单体架构演进到微服务设计,都可以遵循领域驱动设计的架构原则。

课程寄语

没有谁能够做到领域驱动设计的一蹴而就,一门课程也不可能穷尽领域驱动设计的方方面面,从知识的学习到知识的掌握,进而达到能力的提升,需要一个漫长的过程。所谓“理论联系实际”虽然是一句耳熟能详的老话,但其中蕴含了颠扑不破的真理。我在进行领域驱动设计培训时,总会有学员希望我能给出数学公式般的设计准则或规范,似乎软件设计就像拼积木一般,只要遵照图示中给出的拼搭过程,不经思考就能拼出期待的模型。——这是不切实际的幻想。

要掌握领域驱动设计,就不要被它给出的概念所迷惑,而要去思索这些概念背后蕴含的原理,多问一些为什么。同时,要学会运用设计原则去解决问题,而非所谓的“设计规范”。例如:

  • 思考限界上下文边界的划分,实际上还是“高内聚、低耦合”原则的体现,只是我们需要考虑什么内容才是高内聚的,如何抽象才能做到低耦合?
  • 是否需要提取单独的限界上下文?是为了考虑职责的重用,还是为了它能够独立进化以应对未来的变化?
  • 在分层架构中,各层之间该如何协作?如果出现了依赖,该如何解耦?仍然需要从重用与变化的角度去思考设计决策。
  • 为什么同样遵循领域驱动设计,不同的系统会设计出不同的架构?这是因为不同的场景对架构质量的要求并不一样,我们要学会对架构的关注点做优先级排列,从而得出不同的架构决策。

我强烈建议读者诸君要学会对设计的本质思考,不要只限于对设计概念的掌握,而要追求对设计原则与方法的融汇贯通。只有如此,才能针对不同的业务场景灵活地运用领域驱动设计,而非像一个牵线木偶般遵照着僵硬的过程进行死板地设计。

分享交流

我们为本课程付费读者创建了微信交流群,以方便更有针对性地讨论课程相关问题。入群方式请到第 04 课末尾添加小编的微信号,并注明「DDD」。

阅读文章过程中有任何疑问随时可以跟其他小伙伴讨论,或者直接向作者提问(作者看到后抽空回复)。你的分享不仅帮助他人,更会提升自己。

点击了解《领域驱动战略设计实践》。

第01课:领域驱动设计概览

领域驱动设计(Domain Driven Design,DDD)是由 Eric Evans 最早提出的综合软件系统分析和设计的面向对象建模方法,如今已经发展成为了一种针对大型复杂系统的领域建模与分析方法。它完全改变了传统软件开发工程师针对数据库进行的建模方法,从而将要解决的业务概念和业务规则转换为软件系统中的类型以及类型的属性与行为,通过合理运用面向对象的封装、继承和多态等设计要素,降低或隐藏整个系统的业务复杂性,并使得系统具有更好的扩展性,应对纷繁多变的现实业务问题。

领域驱动设计的开放性

领域驱动设计是一种方法论(Methodology),根据维基百科的定义,方法论是一套运用到某个研究领域的系统与理论分析方法。领域驱动设计就是针对软件开发领域提出的一套系统与理论分析方法。Eric Evans 在创造性地提出领域驱动设计时,实则是针对当时项目中聚焦在以数据以及数据样式为核心的系统建模方法的批判。面向数据的建模方法是关系数据库理论的延续,关注的是数据表以及数据表之间关系的设计。这是典型的面向技术实现的建模方法,面对日渐复杂的业务逻辑,这种设计方法欠缺灵活性与可扩展性,也无法更好地利用面向对象设计思想及设计模式,建立可重用的、可扩展的代码单元。领域驱动设计的提出,是设计观念的转变,蕴含了全新的设计思想、设计原则与设计过程

由于领域驱动设计是一套方法论,它建立了以领域为核心驱动力的设计体系,因而具有一定的开放性。在这个体系中,你可以使用不限于领域驱动设计提出的任何一种方法来解决这些问题。例如,可以使用用例(Use Case)、测试驱动开发(TDD)、用户故事(User Story)来帮助我们对领域建立模型;可以引入整洁架构思想及六边形架构,以帮助我们建立一个层次分明、结构清晰的系统架构;还可以引入函数式编程思想,利用纯函数与抽象代数结构的不变性以及函数的组合性来表达领域模型。这些实践方法与模型已经超越了 Eric Evans 最初提出的领域驱动设计范畴,但在体系上却是一脉相承的。这也是为什么在领域驱动设计社区,能够不断诞生新的概念诸如 CQRS 模式、事件溯源(Event Sourcing)模式与事件风暴(Event Storming);领域驱动设计也以开放的心态拥抱微服务(Micro Service),甚至能够将它的设计思想与原则运用到微服务架构设计中。

领域驱动设计过程

领域驱动设计当然不是架构方法,也并非设计模式。准确地说,它其实是“一种思维方式,也是一组优先任务,它旨在加速那些必须处理复杂领域的软件项目的开发”。领域驱动设计贯穿了整个软件开发的生命周期,包括对需求的分析、建模、架构、设计,甚至最终的编码实现,乃至对编码的测试与重构。

领域驱动设计强调领域模型的重要性,并通过模型驱动设计来保障领域模型与程序设计的一致。从业务需求中提炼出统一语言(Ubiquitous Language),再基于统一语言建立领域模型;这个领域模型会指导着程序设计以及编码实现;最后,又通过重构来发现隐式概念,并运用设计模式改进设计与开发质量。这个过程如下图所示:

领域驱动战略设计实践_第3张图片

这个过程是一个覆盖软件全生命周期的设计闭环,每个环节的输出都可以作为下一个环节的输入,而在其中扮演重要指导作用的则是“领域模型”。这个设计闭环是一个螺旋式的迭代设计过程,领域模型会在这个迭代过程中逐渐演进,在保证模型完整性与正确性的同时,具有新鲜的活力,使得领域模型能够始终如一的贯穿领域驱动设计过程、阐释着领域逻辑、指导着程序设计、验证着编码质量。

如果仔细审视这个设计闭环,会发现在针对问题域和业务期望提炼统一语言,并通过统一语言进行领域建模时,可能会面临高复杂度的挑战。这是因为对于一个复杂的软件系统而言,我们要处理的问题域实在太庞大了。在为问题域寻求解决方案时,需要从宏观层次划分不同业务关注点的子领域,然后再深入到子领域中从微观层次对领域进行建模。宏观层次是战略的层面,微观层次是战术的层面,只有将战略设计与战术设计结合起来,才是完整的领域驱动设计。

战略设计阶段

领域驱动设计的战略设计阶段是从下面两个方面来考量的:

  • 问题域方面:针对问题域,引入限界上下文(Bounded Context)上下文映射(Context Map)对问题域进行合理的分解,识别出核心领域(Core Domain)子领域(SubDomain),并确定领域的边界以及它们之间的关系,维持模型的完整性。
  • 架构方面:通过分层架构来隔离关注点,尤其是将领域实现独立出来,能够更利于领域模型的单一性与稳定性;引入六边形架构可以清晰地表达领域与技术基础设施的边界;CQRS 模式则分离了查询场景和命令场景,针对不同场景选择使用同步或异步操作,来提高架构的低延迟性与高并发能力。

Eric Evans 提出战略设计的初衷是要保持模型的完整性。限界上下文的边界可以保护上下文内部和其他上下文之间的领域概念互不冲突。然而,如果我们将领域驱动设计的战略设计模式引入到架构过程中,就会发现限界上下文不仅限于对领域模型的控制,而在于分离关注点之后,使得整个上下文可以成为独立部署的设计单元,这就是“微服务”的概念,上下文映射的诸多模式则对应了微服务之间的协作。因此在战略设计阶段,微服务扩展了领域驱动设计的内容,反过来领域驱动设计又能够保证良好的微服务设计。

一旦确立了限界上下文的边界,尤其是作为物理边界,则分层架构就不再针对整个软件系统,而仅仅针对粒度更小的限界上下文。此时,限界上下文定义了技术实现的边界,对当前上下文的领域与技术实现进行了封装,我们只需要关心对外暴露的接口与集成方式,形成了在服务层次的设计单元重用。

边界给了实现限界上下文内部的最大自由度,这也是战略设计在分治上起到的效用,我们可以在不同的限界上下文选择不同的架构模式。例如,针对订单的查询与处理,选择 CQRS 模式来分别处理同步与异步场景;还可以针对核心领域与子领域重要性的不同,分别选择领域模型(Domain Model)和事务脚本(Transaction Script)模式,灵活地平衡开发成本与开发质量。在宏观层面,面对整个软件系统,我们可以采用前后端分离与基于 REST 的微服务架构,保证系统具有一致的架构风格。

战术设计阶段

整个软件系统被分解为多个限界上下文(或领域)后,就可以分而治之,对每个限界上下文进行战术设计。领域驱动设计并不牵涉到技术层面的实现细节,在战术层面,它主要应对的是领域的复杂性。领域驱动设计用以表示模型的主要要素包括:

  • 值对象(Value Object)
  • 实体(Entity)
  • 领域服务(Domain Service)
  • 领域事件(Domain Event)
  • 资源库(Repository)
  • 工厂(Factory)
  • 聚合(Aggregate)
  • 应用服务(Application Service)

Eric Evans 通过下图勾勒出了战术设计诸要素之间的关系:

领域驱动战略设计实践_第4张图片

领域驱动设计围绕着领域模型进行设计,通过分层架构(Layered Architecture)将领域独立出来。表示领域模型的对象包括:实体值对象领域服务领域逻辑都应该封装在这些对象中。这一严格的设计原则可以避免业务逻辑渗透到领域层之外,导致技术实现与业务逻辑的混淆。在领域驱动设计的演进中,又引入了领域事件来丰富领域模型。

聚合是一种边界,它可以封装一到多个实体值对象,并维持该边界范围之内的业务完整性。在聚合中,至少包含一个实体,且只有实体才能作为聚合根(Aggregate Root)。注意,在领域驱动设计中,没有任何一个类是单独的聚合,因为聚合代表的是边界概念,而非领域概念。在极端情况下,一个聚合可能有且只有一个实体。

工厂资源库都是对领域对象生命周期的管理。前者负责领域对象的创建,往往用于封装复杂或者可能变化的创建逻辑;后者则负责从存放资源的位置(数据库、内存或者其他 Web 资源)获取、添加、删除或者修改领域对象。领域模型中的资源库不应该暴露访问领域对象的技术实现细节。

演进的领域驱动设计过程

战略设计会控制和分解战术设计的边界与粒度,战术设计则以实证角度验证领域模型的有效性、完整性与一致性,进而以演进的方式对之前的战略设计阶段进行迭代,从而形成一种螺旋式上升的迭代设计过程,如下图所示:

领域驱动战略设计实践_第5张图片

面对客户的业务需求,由领域专家与开发团队展开充分的交流,经过需求分析与知识提炼,以获得清晰的问题域。通过对问题域进行分析和建模,识别限界上下文,利用它划分相对独立的领域,再通过上下文映射建立它们之间的关系,辅以分层架构与六边形架构划分系统的逻辑边界与物理边界,界定领域与技术之间的界限。之后,进入战术设计阶段,深入到限界上下文内对领域进行建模,并以领域模型指导程序设计与编码实现。若在实现过程中,发现领域模型存在重复、错位或缺失时,再进而对已有模型进行重构,甚至重新划分限界上下文。

两个不同阶段的设计目标是保持一致的,它们是一个连贯的过程,彼此之间又相互指导与规范,并最终保证一个有效的领域模型和一个富有表达力的实现同时演进

点击了解《领域驱动战略设计实践》。

第02课:深入分析软件的复杂度

软件复杂度的成因

Eric Evans 的经典著作《领域驱动设计》的副标题为“软件核心复杂性应对之道”,这说明了 Eric 对领域驱动设计的定位就是应对软件开发的复杂度。Eric 甚至认为:“领域驱动设计只有应用在大型项目上才能产生最大的收益”。他通过 Smart UI 反模式逆向地说明了在软件设计与开发过程中如果出现了如下问题,就应该考虑运用领域驱动设计:

  • 没有对行为的重用,也没有对业务问题的抽象,每当操作用到业务规则时,都要重复这些业务规则。
  • 快速的原型建立和迭代很快会达到其极限,因为抽象的缺乏限制了重构的选择。
  • 复杂的功能很快会让你无所适从,所以程序的扩展只能是增加简单的应用模块,没有很好的办法来实现更丰富的功能。

因此,选择领域驱动设计,就是要与软件系统的复杂作一番殊死拼搏,以降低软件复杂度为己任。那么,什么才是复杂呢?

点击了解《领域驱动战略设计实践》。

什么是复杂?

即使是研究复杂系统的专家,如《复杂》一书的作者 Melanie Mitchell,都认为复杂没有一个明确得到公认的定义。不过,Melanie Mitchell 在接受 Ubiquity 杂志专访时,还是“勉为其难”地给出了一个通俗的复杂系统定义:由大量相互作用的部分组成的系统,与整个系统比起来,这些组成部分相对简单,没有中央控制,组成部分之间也没有全局性的通讯,并且组成部分的相互作用导致了复杂行为。

这个定义庶几可以表达软件复杂度的特征。定义中的组成部分对于软件系统来说,就是我所谓的“设计单元”,基于粒度的不同可以是函数、对象、模块、组件和服务。这些设计单元相对简单,然而彼此之间的相互作用却导致了软件系统的复杂行为。

Jurgen Appelo 从理解力与预测能力两个维度分析了复杂系统理论,这两个维度又各自分为不同的复杂层次,其中,理解力维度分为 Simple 与 Comlicated 两个层次,预测能力维度则分为 Ordered、Complex 与 Chaotic 三个层次,如下图所示:

领域驱动战略设计实践_第6张图片

参考复杂的含义,Complicated 与 Simple(简单)相对,意指非常难以理解,而 Complex 则介于 Ordered(有序的)与 Chaotic(混沌的)之间,认为在某种程度上可以预测,但会有很多出乎意料的事情发生。显然,对于大多数软件系统而言,系统的功能都是难以理解的;在对未来需求变化的把控上,虽然我们可以遵循一些设计原则来应对可能的变化,但未来的不可预测性使得软件系统的演进仍然存在不可预测的风险。因此,软件系统的所谓“复杂”其实覆盖了 Complicated 与 Complex 两个方面。要理解软件复杂度的成因,就应该结合理解力预测能力这两个因素来帮助我们思考。

理解力

在软件系统中,是什么阻碍了开发人员对它的理解?想象团队招入一位新人,就像一位游客来到了一座陌生的城市,他是否会迷失在阡陌交错的城市交通体系中,不辨方向?倘若这座城市实则是乡野郊外的一座村落,不过只有房屋数间,一条街道连通城市的两头,还会疑生出迷失之感吗?

因而,影响理解力的第一要素是规模

规模

软件的需求决定了系统的规模。当需求呈现线性增长的趋势时,为了实现这些功能,软件规模也会以近似的速度增长。由于需求不可能做到完全独立,导致出现相互影响相互依赖的关系,修改一处就会牵一发而动全身。就好似城市的一条道路因为施工需要临时关闭,此路不通,通行的车辆只能改道绕行,这又导致了其他原本已经饱和的道路,因为涌入更多车辆,超出道路的负载从而变得更加拥堵,这种拥堵现象又会顺势向这些道路的其他分叉道路蔓延,形成一种辐射效应的拥堵现象。

软件开发的拥堵现象或许更严重:

  • 函数存在副作用,调用时可能对函数的结果作了隐含的假设;
  • 类的职责繁多,不敢轻易修改,因为不知这种变化会影响到哪些模块;
  • 热点代码被频繁变更,职责被包裹了一层又一层,没有清晰的边界;
  • 在系统某个角落,隐藏着伺机而动的 bug,当诱发条件具备时,则会让整条调用链瘫痪;
  • 不同的业务场景包含了不同的例外场景,每种例外场景的处理方式都各不相同;
  • 同步处理与异步处理代码纠缠在一起,不可预知程序执行的顺序。

当需求增多时,软件系统的规模也会增大,且这种增长趋势并非线性增长,会更加陡峭。倘若需求还产生了事先未曾预料到的变化,我们又没有足够的风险应对措施,在时间紧迫的情况下,难免会对设计做出妥协,头疼医头、脚疼医脚,在系统的各个地方打上补丁,从而欠下技术债(Technical Debt)。当技术债务越欠越多,累计到某个临界点时,就会由量变引起质变,整个软件系统的复杂度达到巅峰,步入衰亡的老年期,成为“可怕”的遗留系统。正如饲养场的“奶牛规则”:奶牛逐渐衰老,最终无奶可挤;然而与此同时,饲养成本却在上升。

结构

不知大家是否去过迷宫?相似而回旋繁复的结构使得本来封闭狭小的空间被魔法般地扩展为一个无限的空间,变得无穷大,仿佛这空间被安置了一个循环,倘若没有找到正确的退出条件,循环就会无休无止,永远无法退出。许多规模较小却格外复杂的软件系统,就好似这样的一座迷宫。

此时,结构成了决定系统复杂度的关键因素。

结构之所以变得复杂,在多数情况下还是因为系统的质量属性决定的。例如,我们需要满足高性能、高并发的需求,就需要考虑在系统中引入缓存、并行处理、CDN、异步消息以及支持分区的可伸缩结构。倘若我们需要支持对海量数据的高效分析,就得考虑这些海量数据该如何分布存储,并如何有效地利用各个节点的内存与 CPU 资源执行运算。

从系统结构的视角看,单体架构一定比微服务架构更简单,更便于掌控,正如单细胞生物比人体的生理结构要简单数百倍;那么,为何还有这么多软件组织开始清算自己的软件资产,花费大量人力物力对现有的单体架构进行重构,走向微服务化?究其主因,不还是系统的质量属性在作祟吗?

纵观软件设计的历史,不是分久必合、合久必分,而是不断拆分、继续拆分、持续拆分的微型化过程。分解的软件元素不可能单兵作战,怎么协同、怎么通信,就成为了系统分解后面临的主要问题。如果没有控制好,这些问题固有的复杂度甚至会在某些场景下超过因为分解给我们带来的收益。

无论是优雅的设计,还是拙劣的设计,都可能因为某种设计权衡而导致系统结构变得复杂。唯一的区别在于前者是主动地控制结构的复杂度,而后者带来的复杂度是偶发的,是错误的滋生,是一种技术债,它可能会随着系统规模的增大而导致一种无序设计

在 Pete Goodliffe 讲述的《两个系统的故事:现代软件神话》中详细地罗列了无序设计系统的几种警告信号:

  • 代码没有显而易见的进入系统中的路径;
  • 不存在一致性、不存在风格、也没有统一的概念能够将不同的部分组织在一起;
  • 系统中的控制流让人觉得不舒服,无法预测;
  • 系统中有太多的“坏味道”,整个代码库散发着腐烂的气味儿,是在大热天里散发着刺激气体的一个垃圾堆;
  • 数据很少放在使用它的地方,经常引入额外的巴罗克式缓存层,目的是试图让数据停留在更方便的地方。

我们看一个无序设计的软件系统,就好像隔着一层半透明的玻璃观察事物一般,系统中的软件元素都变得模糊不清,充斥着各种技术债。细节层面,代码污浊不堪,违背了“高内聚、松耦合”的设计原则,导致许多代码要么放错了位置,要么出现重复的代码块;架构层面,缺乏清晰的边界,各种通信与调用依赖纠缠在一起,同一问题域的解决方案各式各样,让人眼花缭乱,仿佛进入了没有规则的无序社会。

预测能力

当我们掌握了事物发展的客观规律时,我们就具有了一定的对未来的预测能力。例如,我们洞察了万有引力的本质,就可以对我们能够观察到的宇宙天体建立模型,较准确地推测出各个天体在未来一段时间的运行轨迹。然而,宇宙空间变化莫测,或许因为一个星球的死亡产生黑洞的吸噬能力,就可能导致那一片星域产生剧烈的动荡,这种动荡会传递到更远的星空,从而干扰了我们的预测。坦白说,我们现在连自己居住的地球天气都不能做一个准确的预测呢。之所以如此,正是因为未知的变化的产生。

变化

未来总会出现不可预测的变化,这种不可预测性带来的复杂度,使得我们产生畏惧,因为我们不知道何时会发生变化,变化的方向又会走向哪里,这就导致心理滋生一种仿若失重一般的感觉。变化让事物失去控制,受到事物牵扯的我们会感到惶恐不安。

在设计软件系统时,变化让我们患得患失,不知道如何把握系统设计的度。若拒绝对变化做出理智的预测,系统的设计会变得僵化,一旦变化发生,修改的成本会非常的大;若过于看重变化产生的影响,渴望涵盖一切变化的可能,一旦预期的变化不曾发生,我们之前为变化付出的成本就再也补偿不回来了。这就是所谓的“过度设计”。

从需求的角度讲,变化可能来自业务需求,也可能来自质量属性。以对系统架构的影响而言,尤以后者为甚,因为它可能牵涉到整个基础架构的变更。George Fairbanks在《恰如其分的软件架构》一书中介绍了邮件托管服务公司 RackSpace 的日志架构变迁,业务功能没有任何变化,却因为邮件数量的持续增长,为满足性能需求,架构经历了三个完全不同系统的变迁:从最初的本地日志文件,到中央数据库,再到基于 HDFS 的分布式存储,整个系统几乎发生了颠覆性的变化。这并非 RackSpace 的设计师欠缺设计能力,而是在公司草创之初,他们没有能够高瞻远瞩地预见到客户数量的增长,导致日志数据增多,以至于超出了已有系统支持的能力范围。俗话说:“事后诸葛亮”,当我们在对一个软件系统的架构设计进行复盘时,总会发现许多设计决策是如此的愚昧。殊不知这并非愚昧,而是在设计当初,我们手中掌握的筹码不足以让自己赢下这场面对未来的战争罢了。

这就是变化之殇!

如果将软件系统中我们自己开发的部分都划归为需求的范畴,那么还有一种变化,则是因为我们依赖的第三方库、框架或平台、甚至语言版本的变化带来的连锁反应。例如,作为 Java 开发人员,一定更垂涎于 Lambda 表达式的简洁与抽象,又或者 Jigsaw 提供的模块定义能力,然而现实是我们看到多数的企业软件系统依旧在 Java 6 或者 Java 7 中裹足不前。

这还算是幸运的例子,因为我们尽可以满足这种故步自封,由于情况并没有到必须变化的境地。当我们依赖的第三方有让我们不得不改变的理由时,难道我们还能拒绝变化吗?

许多软件在版本变迁过程中都尽量考虑到 API 变化对调用者带来的影响,因而尽可能保持版本向后兼容。我亲自参与过系统从 Spring 2.0 到 4.0 的升级,Spark 从 1.3.1 到 1.5 再到 1.6 的升级,感谢这些框架或平台设计人员对兼容性的体贴照顾,使得我们的升级成本能够被降到最低;但是在升级之后,倘若没有对系统做全方位的回归测试,我们的内心始终是惴惴不安的。

对第三方的依赖看似简单,殊不知我们所依赖的库、平台或者框架又可能依赖了若干对于它们而言又份属第三方的更多库、平台和框架。每回初次构建软件系统时,我都为漫长等待的依赖下载过程而感觉烦躁不安。多种版本共存时可能带来的所谓依赖地狱,只要亲身经历过,就没有不感到不寒而栗的。倘若你运气欠佳,可能还会有各种古怪问题接踵而来,让你应接不暇、疲于奔命。

如果变化是不可预测的,那么软件系统也会变得不可预测。一方面我们要尽可能地控制变化,至少要将变化产生的影响限制在较小的空间范围内;另一方面又要保证系统不会因为满足可扩展性而变得更加复杂,最后背上过度设计的坏名声。软件设计者们就像走在高空钢缆的技巧挑战者,惊险地调整重心以维持行动的平衡。故而,变化之难,在于如何平衡。

点击了解《领域驱动战略设计实践》。

第03课:控制软件复杂度的原则

虽然说认识到软件系统的复杂本性,并不足以让我们应对其复杂,并寻找到简化系统的解决之道;然而,如果我们连导致软件复杂度的本源都茫然不知,又怎么谈得上控制复杂呢?既然我们认为导致软件系统变得复杂的成因是规模结构变化三要素,则控制复杂度的原则就需要对它们进行各个击破。

点击了解《领域驱动战略设计实践》。

分而治之、控制规模

针对规模带来的复杂度,我们应注意克制做大、做全的贪婪野心,尽力保证系统的小规模。简单说来,就是分而治之的思想,遵循小即是美的设计美学。

丹尼斯·里奇(Dennis MacAlistair Ritchie)从大型项目 Multics 的失败中总结出 KISS(Keep it Simple Stupid)原则,基于此原则,他将 Unix 设计为由许多小程序组成的整体系统,每个小程序只能完成一个功能,任何复杂的操作都必须分解成一些基本步骤,由这些小程序逐一完成,再组合起来得到最终结果。从表面上看,运行一连串小程序很低效,但是事实证明,由于小程序之间可以像积木一样自由组合,所以非常灵活,能够轻易完成大量意想不到的任务。而且,计算机硬件的升级速度非常快,所以性能也不是一个问题;另一方面,当把大程序分解成单一目的的小程序,开发会变得很容易。

Unix 的这种设计哲学被 Doug McIlroy、Elliot Pinson 和 Berk Tague 总结为以下两条:

  • Make each program do one thing well. To do a new job, build a fresh rather than complicate old programs by adding new “features.”
  • Expect the output of every program to become the input to another, as yet unknown, program.

这两条原则是相辅相成的。第一条原则要求一个程序只做一件事情,符合“单一职责原则”,在应对新需求时,不会直接去修改一个复杂的旧系统,而是通过添加新特性,然后对这些特性进行组合。要满足小程序之间的自由组合,就需要满足第二条原则,即每个程序的输入和输出都是统一的,因而形成一个统一接口(Uniform Interface),以支持程序之间的自由组合(Composability)。利用统一接口,既能够解耦每个程序,又能够组合这些程序,还提高了这些小程序的重用性,这种“统一接口”,其实就是架构一致性的体现。

保持结构的清晰与一致

所有设计质量高的软件系统都有相同的特征,就是拥有清晰直观且易于理解的结构。

Robert Martin 分析了这么多年诸多设计大师提出的各种系统架构风格与模式,包括 Alistair Cockburn 提出的六边形架构(Hexagonal Architecture),Jeffrey Palermo 提出的洋葱架构(Onion Architecture),James Coplien 与 Trygve Reenskaug 提出的 DCI 架构,Ivar Jacobson 提出的 BCE 设计方法。结果,他认为这些方法的共同特征都遵循了“关注点分离”架构原则,由此提出了整洁架构的思想。

整洁架构提出了一个可测试的模型,无需依赖于任何基础设施就可以对它进行测试,只需通过边界对象发送和接收对应的数据结构即可。它们都遵循稳定依赖原则,不对变化或易于变化的事物形成依赖。整洁架构模型让外部易变的部分依赖于更加稳定的领域模型,从而保证了核心的领域模型不会受到外部的影响。典型的整洁架构如下图所示:

领域驱动战略设计实践_第7张图片

整洁架构的目的在于识别整个架构不同视角以及不同抽象层次的关注点,并为这些关注点划分不同层次的边界,从而使得整个架构变得更为清晰,以减少不必要的耦合。要做到这一点,则需要合理地进行职责分配,良好的封装与抽象,并在约束的指导下为架构建立一致的风格,这是许多良好系统的设计特征。

拥抱变化

变化对软件系统带来的影响可以说是无解,然而我们不能因此而消极颓废,套用 Kent Beck 的话来说,我们必须“拥抱变化”。除了在开发过程中,我们应尽可能做到敏捷与快速迭代,以此来抵消变化带来的影响;在架构设计层面,我们还可以分析哪些架构质量属性与变化有关,这些质量属性包括:

  • 可进化性(Evolvability)
  • 可扩展性(Extensibility)
  • 可定制性(Customizability)

要保证系统的可进化性,可以划分设计单元的边界,以确定每个设计单元应该履行的职责以及需要与其他设计单元协作的接口。这些设计单元具有不同的设计粒度,包括函数、对象、模块、组件及服务。由于每个设计单元都有自己的边界,边界内的实现细节不会影响到外部的其他设计单元,我们就可以非常容易地替换单元内部的实现细节,保证了它们的可进化性。

要满足系统的可扩展性,首先要学会识别软件系统中的变化点(热点),常见的变化点包括业务规则、算法策略、外部服务、硬件支持、命令请求、协议标准、数据格式、业务流程、系统配置、界面表现等。处理这些变化点的核心就是“封装”,通过隐藏细节、引入间接等方式来隔离变化、降低耦合。一些常见的架构风格,如基于事件的集成、管道—过滤器等的引入,都可以在一定程度上提高系统可扩展性。

可定制性意味着可以提供特别的功能与服务。Fielding 在《架构风格与基于网络的软件架构设计》提到:“支持可定制性的风格也可能会提高简单性和可扩展性”。在 SaaS 风格的系统架构中,我们常常通过引入元数据(Metadata)来支持系统的可定制。插件模式也是满足可定制性的常见做法,它通过提供统一的插件接口,使得用户可以在系统之外按照指定接口编写插件来扩展定制化的功能。

点击了解《领域驱动战略设计实践》。

第04课:领域驱动设计对软件复杂度的应对(上)
第05课:领域驱动设计对软件复杂度的应对(下)
第06课:软件开发团队的沟通与协作
第07课:【案例】版本升级系统的先启阶段
第08课:运用领域场景分析提炼领域知识(上)
第09课:运用领域场景分析提炼领域知识(下)
第10课:建立统一语言
第11课:理解限界上下文
第12课:限界上下文的控制力(上)
第13课:限界上下文的控制力(下)
第14课:识别限界上下文(上)
第15课:识别限界上下文(下)
第16课:理解上下文映射
第17课:上下文映射的团队协作模式
第18课:上下文映射的通信集成模式
第19课:辨别限界上下文的协作关系(上)
第20课:辨别限界上下文的协作关系(下)
第21课:认识分层架构
第22课:分层架构的演化
第23课:领域驱动架构的演进
第24课:【案例】层次的职责与协作关系(图文篇)
第25课:限界上下文与架构
第26课:限界上下文对架构的影响
第27课:领域驱动设计的代码模型
第28课:代码模型的架构决策
第29课:【实践】先启阶段的需求分析
第30课:【实践】先启阶段的领域场景分析(上)
第31课:【实践】先启阶段的领域场景分析(下)
第32课:【实践】识别限界上下文
第33课:【实践】确定限界上下文的协作关系
第34课:【实践】EAS 的整体架构

阅读全文: http://gitbook.cn/gitchat/column/5b3235082ab5224deb750e02

你可能感兴趣的:(领域驱动战略设计实践)