领域驱动设计的基本概念 领域驱动设计作为一个针对大型复杂业务系统的领域建模方法体系(不仅限于面向对象的领 域建模),它改变了传统软件开发工程师针对数据库建模的方式,通过面向领域的思维方式,将要 解决的业务概念和业务规则等内容提炼为领域知识,然后借由不同的建模范式将这些领域知识抽象 为能够反映真实世界的领域模型。
1 领域驱动设计元模型
领域驱动设计元模型是以模式的形式呈现在大家眼前的,由诸多松散的模式构成, 这些模式 在领域驱动设计中的关系如图2-1所示。
领域驱动设计的核心是模型驱动设计,而模型驱动设计的核心又是领域模型, 领域模型必须 在统一语言(参见第4章)的指导下获得。为整个业务系统建立的领域模型要么属于核心子领域(参 见第6章),要么属于通用子领域②。之所以区分子领域, 一方面是为了将一个不易解决的庞大问题 切割为团队可以掌控的若干小问题,达到各个击破的目的,另一方面也是为了更好地实现资产(人 力资产与财力资产)的合理分配。
为了保证定义的领域模型在不同上下文表达各自的知识语境, 需要引入限界上下文 (参见《解构领域驱动设计》第9 章)来确定业务能力的自治边界,并考虑通过持续集成来维护模型的统一。上下文映射 (《解构领域驱动设计》参见第10 章)清晰地表达了多个限界上下文之间的协作关系。根据协作方式的不同,可以将上下文映射分为如下8种模式① :
- 客户方/供应方;
- 共享内核;
- 遵奉者;
- 分离方式;
- 开放主机服务;
- 发布语言;
- 防腐层;
- 大泥球。
文章图片
图2-1 领域驱动设计元模型
模型驱动设计可以在限界上下文的边界内部进行,它通过分层架构(layeredarchitecture)将领域独立出来,并在统一语言的指导下,通过与领域专家的协作获得领域模型。表示领域模型的设计要素(参见第15章)包括实体(entity)、值对象(valueobject)、领域服务(domainservice)和领域事件(domainevent)。领域逻辑都应该封装在这些对象中。这一严格的设计原则可以避免领域逻辑泄露到领域层之外,导致技术实现与领域逻辑的混淆。
聚合 (aggregate)(参见第15章)是一种边界,它可以封装一到多个实体与值对象,并维持该 边界范围之内的业务完整性。聚合至少包含一个实体,且只有实体才能作为聚合根(aggregate root)。 工厂 (factory)和资源库 (repository)(参见第17章) 负责管理聚合的生命周期。前者负责聚合的 创建,用于封装复杂或者可能变化的创建逻辑;后者负责从存放资源的位置(数据库、内存或者其 他Web资源)获取、添加、删除或者修改聚合。
2 问题空间和解空间
哲学家常常会围绕真实世界和理念世界的映射关系探索人类生存的意义, 即所谓“两个世界” 的哲学思考。软件世界也可一分为二,分为构成描述需求问题的真实世界与获取解决方案的理念世 界。整个软件构建的过程,就是从真实世界映射到理念世界的过程。
如果真实世界是复杂的,在映射为理念世界的过程中,就会不断受到复杂度的干扰。根据Allen Newell和Herbert Simon的问题空间理论:“人类是通过在问题空间 (problem space) 中寻找解决方 案来解决问题的”[9],构建软件(世界) 也就是从真实世界中的问题空间寻找解决方案,将其映射 为理念世界的解空间(solution space) 来满足问题空间的需求。因此,软件系统的构建实则是对问 题空间的求解,以获得构成解空间的设计方案,如图2-2所示。
文章图片
图2-2 从问题空间到解空间
为什么要在软件构建过程中引入问题空间和解空间?
实际上, 随着IT技术的发展, 软件系统正是在这两个方向不断发展和变化的。在问题空间, 我们要解决的问题越来越棘手,空间规模越来越大,因为随着软件技术的发展, 许多原本由人 来处理的线下流程慢慢被自动化操作所替代,人机交互的方式发生了翻天覆地的变化,IT化的 范围变得更加宽广,涉及的领域也越来越多。问题空间的难度与规模直接决定了软件系统的复 杂度。
针对软件系统提出的问题, 解决方案的推陈出新自然毋庸讳言, 无论是技术、工具,还是设 计思想与模式,都有了很大变化。解决方案不是从石头里蹦出来的,而必然是为了解决问题而生的。 面对错综复杂的问题,解决方案自然也需要灵活变化。软件开发技术的发展是伴随着复用性和扩展性发展的。倘若问题存在相似性,解决方案就有复用的可能。通过抽象寻找到不同问题的共性时, 相同的解决方案也可以运用到不同的问题中。同时,解决方案还需要响应问题的变化,能在变化发 生时以最小的修改成本满足需求,同时保障解决方案的新鲜度。无疑,构成解空间的解决方案不仅 要解决问题,还要控制软件系统的复杂度。
问题空间需要解空间来应对,解空间自然也不可脱离问题空间而单独存在。对于客户提出的 需求,要分清楚什么是问题,什么是解决方案,真正的需求才可能浮现出来。在看清了问题的真相 之后,我们才能有据可依地寻找真正能解决问题的解决方案。软件构建过程中的需求分析,实际就 是对问题空间的定位与探索。如果在问题空间还是一团迷雾的时候就贸然开始设计,带来的灾难性 结果是可想而知的。徐锋认为,“要做好软件需求工作,业务驱动需求思想是核心。传统的需求分 析是站在技术视角展开的, 关注的是‘方案级需求’;而业务驱动的需求思想则是站在用户视角展 开的,关注的是‘问题级需求’。”
怎么区分方案级需求和问题级需求? 方案级需求就好比一个病人到医院看病, 不管病情就直 接让医生开阿司匹林, 而问题级需求则是向医生描述自己身体的症状。病情是医生要解决的问题, 处方是医生提供的解决方案。
那种站在技术视角展开的需求分析, 实际就是没有明确问题空间与解空间的界限。在针对问 题空间求解时,必须映射于问题空间定义的问题,如此才能遵循恰如其分的设计原则,在问题空间 的上下文约束下寻找合理的解决方案。
领域驱动设计为问题空间与解空间提供了不同的设计元模型。对于问题空间, 强调运用统一 语言来描述需求问题,利用核心子领域 、通用子领域与支撑子领域来分解问题空间,如此就可以 “揭示什么是重要的以及在何处付出努力”[11]9。除去统一语言与子领域,其余设计元模型都将运用 于解空间,指导解决方案围绕着“领域”这一核心开展业务系统的战略设计与战术设计。
3 战略设计和战术设计
对于一个复杂度高的业务系统, 过于辽阔的问题空间使得我们无法在深入细节的同时把握系 统的全景。既然软件构建的过程就是对问题空间求解的过程,那么面对太多太大的问题,就无法奢 求一步求解,需要根据问题的层次进行分解。不同层次的求解目标并不相同:为了把握系统的全景, 就需要从宏观层次分析和探索问题空间,获得对等于软件架构的战略设计原则;为了深入业务的细 节,则需要从微观层次开展建模活动,并在战略设计原则的指导下做出战术设计决策。这就是领域 驱动设计的两个阶段:战略设计阶段和战术设计阶段。
战略设计阶段要从以下两个方面来考量。
问题空间:对问题空间进行合理分解,识别出核心子领域、通用子领域和支撑子领域, 并 确定各个子领域的目标、边界和建模策略。
解空间:对问题空间进行解决方案的架构映射,通过划分限界上下文,为统一语言提供知 识语境,并在其边界内维护领域模型的统一。每个限界上下文的内部有着自己的架构, 限界上下文之间的协作关系则通过上下文映射来体现和表达。
子领域的边界明确了问题空间中领域的优先级,限界上下文的边界则确保了领域建模的最大 自由度。这也是战略设计在分治上起到的效用。当我们在战略层次从问题空间映射到解空间时,子 领域也将映射到限界上下文,即可根据子领域的类型为限界上下文选择不同的建模方式。例如为处 于核心子领域的限界上下文选择领域模型(domain model)模式[12]116 ,为处于支撑子领域(supporting sub domain)的限界上下文选择事务脚本(transaction script)模式[12]110,这样就可以灵活地平衡开 发成本与开发质量。
战术设计阶段需要在限界上下文内部开展领域建模,前提是你为限界上下文选择了领域模型 模式。在限界上下文内部,需要通过分层架构将领域独立出来,在排除技术实现的干扰下,通过与 领域专家的协作在统一语言的指导下逐步获得领域模型。
战术设计阶段最重要的设计元模型是聚合模式。虽然聚合是实体和值对象的概念边界, 然而 在获得了清晰表达领域知识的领域模型后,我们可以将聚合视为表达领域逻辑的最小设计单元。如 果领域行为是无状态的,或者需要多个聚合的协作,又或者需要访问外部资源,则应该将它分配给 领域服务。至于领域事件,则主要用于表达领域对象状态的迁移,也可以通过事件来实现聚合乃至 限界上下文之间的状态通知。
战略设计与战术设计并非割裂的两个阶段,而是模型驱动设计过程在不同阶段展现出来的不 同视图。战略设计指导着战术设计,这就等同于设计原则指导着设计决策。Eric Evans就明确指出, “战略设计原则必须把模型的重点放在捕获系统的概念核心, 也就是系统的‘远景’上。”[8]231当一 个业务系统的规模变得越来越庞大时,战略设计高屋建瓴地通过限界上下文规划了整个系统的架 构。只要维护好限界上下文的边界,管理好限界上下文之间的协作关系,限制在该边界内开展的战 术设计所要面对的就是一个复杂度得到大幅降低的小型业务系统。
人们常以“只见树木,不见森林”来形容一个人不具备高瞻远瞩的战略眼光,然而,若是“只 见森林,不见树木”,也未见得是一个褒扬的好词语, 它往往可以形容一个人好高骛远,不愿意脚 踏实地将战略方案彻底落地。无论战略的规划多么完美,到了战术设计的实际执行阶段,团队在开 展对领域的深层次理解时, 总会发现之前被遗漏的领域概念,并经过不断的沟通与协作,“碰撞” 出对领域的新的理解。对领域概念的新发现与完善除了能帮助我们将领域模型突破到深层模型,还 可能促进我们提出对战略设计的修改与调整,其中就包括对限界上下文边界的调整,从而使战略设 计与战术设计保持统一。
从战略设计到战术设计是一个自顶向下的设计过程,体现为设计原则对设计决策的指导;将 战术设计方案反馈给战略设计,则是自底向上的演化过程,体现为对领域概念的重构引起对战略架 构的重构。二者形成不断演化、螺旋上升的设计循环。
4 领域模型驱动设计
【开发语言|什么是DDD(领域驱动设计)()】领域驱动设计是一种思维方式[8]2,而模型驱动设计则是领域驱动设计的一种设计元模型。因此,模型驱动设计必须在领域驱动设计思维方式的指导下进行, 那就是面向领域的模型驱动设计, 或者更加准确地将其描述为领域模型驱动设计 。
领域模型驱动设计通过单一的领域模型同时满足分析建模、设计建模和实现建模的需要, 从 而将分析、设计和编码实现糅合在一个整体阶段中,避免彼此的分离造成知识传递带来的知识流 失和偏差。它树立了一种关键意识,就是开发团队在针对领域逻辑进行分析、设计和编码实现时, 都在进行领域建模,产生的输出无论是文档、设计图还是代码,都是组成领域模型的一部分。Eric Evans将那些参与模型驱动设计过程并进行领域建模的人员称为“亲身实践的建模者”(hands-on modeler)。
模型驱动设计主要在战术阶段进行, 换言之,整个领域建模的工作是在限界上下文的边界约 束下进行的,统一语言的知识语境会对领域模型产生影响,至少,建模人员不用考虑在整个系统范 围下领域概念是否存在冲突,是否带来歧义。由于限界上下文拥有自己的内部架构,一旦领域模型 牵涉到跨限界上下文之间的协作,就需要遵循限界上下文与上下文映射的架构约束了。
既然模型驱动设计是面向领域的,就必须明确以下两个关键原则。
- 以领域为建模驱动力:在建模过程中,针对领域知识提炼抽象的领域模型,并不断针对领 域模型进行深化与突破,直到最终以代码来表达领域模型。
- 排除技术因素的干扰:领域建模与技术实现的关注点分离有助于保证领域模型的纯粹性, 也能避免混淆领域概念和其他只与技术相关的概念。
领域驱动书籍推荐 领域驱动设计 软件核心复杂性应对之道 修订版
文章图片
领域驱动设计(DDD)之父作品,众多声名显赫软件师鼎力推荐,深度剖析构建高质量复杂系统的核心技术。
本书有两个前提:
(1)在大多数软件项目中,主要的焦点应该是领域和领域逻辑;
(2)复杂的领域设计应该基于模型。
领域驱动设计是一种思维方式,也是一组优先任务,它旨在加速那些必须处理复杂领域的软件项目的开发。为了实现这个目标,本书给出了一整套完整的设计实践、技术和原则。
全书围绕着设计和开发实践,结合若干真实的项目案例,向读者阐述如何在真实的软件开发中应用领域驱动设计。书中给出了领域驱动设计的系统化方法,并将人们普遍接受的一些实践综合到一起,融入了作者的见解和经验,展现了一些可扩展的设计新实践、已验证过的技术以及便于应对复杂领域的软件项目开发的基本原则。
解构领域驱动设计
文章图片
DDD领域驱动设计教程,进一步精化领域驱动设计方法体系,通过实战案例演示统一过程的实施,可帮助读者提高领域建模及软件设计能力。
有些读者可能不理解本书为什么这么厚。网络上有大量碎片式的领域驱动设计文章,一个案 例只有几页, 市场上也有不少领域驱动设计方面的培训,两天就能帮我们“搞定”领域驱动设计, 领域驱动设计的知识体系似乎并没有我们想象的那么丰满。但事实上,这本书将告诉我们,领域驱 动设计背后完整的知识体系并没有那么简单,我们需要掌握的是从业务到技术的整个技能栈。我们 必须接受的事实是:领域驱动设计是有一定学习曲线的。所以,不要拒绝一本足够厚的书,这恰恰 是其价值的体现。这本书的各个部分不是泛泛而谈,而是通过展开细节,层层推进,帮助读者建 立扎实的理论基础,并通过大量翔实的案例,让读者能灵活运用理论知识。对于初学者,本书尽 可能详尽地把问题展开、讲透;对于有一定经验的老手,本书也有更多有深度的细节思考和理论 拓展。相信这本书会成为国内领域驱动设计技术书籍的一个标杆。
该如何阅读这样一本厚书呢?
若你时间足够充裕,又渴望彻底探索领域驱动设计的全貌, 我建议还是按部就班、循序渐进 地进行阅读。或许在阅读开篇的3章时,你会因为太多信息的一次性涌入而产生迷惑、困扰和不解, 这只是因为我期望率先为读者呈现领域驱动设计的整体面貌。在获得领域驱动设计的全貌之后,哪 怕你只是在脑海中存留了一个朦胧的轮廓,也足以开启自己对设计细节的理解和认识。
若你追求高效阅读,又渴望寻求领域驱动设计问题的答案,可以根据目录精准定位你最为关心的技术讲解。
本书全面阐释了领域驱动设计(domain-driven design,DDD)的知识体系,内容覆盖领域驱动设计的主要模式与主流方法,并在此基础上提出“领域驱动设计统一过程”(domain-driven design unified process,DDDUP),将整个软件构建过程划分为全局分析、架构映射和领域建模3个阶段。除给出诸多案例来阐释领域驱动设计统一过程中的方法与模式之外,本书还通过一个真实而完整的案例全面展现了如何进行领域驱动设计统一过程的实施和落地。为了更好地运用领域驱动设计统一过程,本书还开创性地引入了业务服务、菱形对称架构、领域驱动架构、服务驱动设计等方法与模式,总结了领域驱动设计能力评估模型与参考过程模型。本书提出的一整套方法体系已在多个项目中推广和落地。
推荐阅读
- c语言|在ARM64下编程的常见陷阱(C语言常见陷阱)
- java|6月,值得推荐的计算机专业新书有哪几本()
- java|如何写出高质量的代码()
- c++|动手打造深度学习框架(基本数据结构与算法)
- linux|ROS中catkin_make的OpenCV冲突的解决(踩坑小记,报错分析)
- C语言|(师承郝斌老师)C语言——结构体
- java|2022年深圳杯数学建模
- Python入门|【Python】面向对象版学生管理系统(文末有源代码)
- Java入门|【Java】你会使用 Lambda 表达式吗()