引言
领域驱动设计并不是新的架构设计理论,从 Eric Evans 提出至今已经有十多年历史。由于微服务架构的兴起,DDD 常用于指导微服务边界划分,并重新广泛进入软件研发大众的视野。DDD 的理念及应用普及在国外相对成熟,在国内尚处于初期发展阶段。国内的很多社区以及企业组织内部近几年对于 DDD 的探讨和应用逐渐火热,许多架构师以及开发人员对 DDD 充满了学习和实践的热情。而像敏捷一样,不同团队对其认知水平和实践水平不尽相同,有的成功,大多数可能是失败的。
领域驱动设计(Domain Driven Design),简称 DDD, Eric Evans 2004 年的《Domain-Driven Design: Tackling Complexity in the Heart of Software》一书中第一次提出。领域驱动设计是一种用于指导软件设计的方法论,也是一种设计思维方式,用于解决软件复杂性问题,旨在加速那些必须处理复杂领域的软件项目的开发。
实践 DDD 的第一步不在于如何编写代码,而首先需要拉齐对领域驱动设计的认知。后续的系列文章将围绕领域驱动设计进行不同视角探讨,以期帮助大家对其有更深入的认识,并能应用的实际的研发工作中。
聊聊问题空间、解空间、领域模型
问题空间和解决方案空间
问题空间:Problem Space,是当前环境下业务所面临的一系列问题和问题背后的需求,通常是业务和产品领域专家主导问题、需求的收集描述和分析。问题空间框定了我们要解决的问题的上下文,这种上下文环境不是软件工程或是领域驱动所独有的,而是通用的共性的元素。工程实践必然处于某种上下文环境之下。
解决方案空间:Solution Space,解决方案空间是针对问题空间的解决方案,属于工程实现阶段,由技术专家主导方案设计。
软件开发过程,本质上可以看作是问题空间到解决方案空间的一个映射转化:问题空间,找出业务挑战及其对相关需求场景用例分析解空间,通过具体的技术工具手段来进行设计实现
领域、模型和领域模型
领域:Domain
“领域” 是 “知识或活动的集合”,相对于软件系统而言,领域就是软件应用所要解决的现实问题区域。领域对应于问题空间,是一个特定范围边界内的业务需求的总和。
领域模型:Domain Model
抽象是一种化繁为简的能力,是人类认识世界的利器,也是一种生物本能。在有限的脑容量的前提下,人类不可能存储记忆所有的细节,海量信息已经超出人脑存储限制而无法容纳和有效获取。抽象使得人类可以屏蔽无关细节信息,抽取高层的有效信息进行记忆存储。试想,如果脑机接口技术有所突破,在人脑背后链接的是海量的高效的计算机集群,这种无限的存储、计算和检索能力的增强,“抽象能力也许会被弱化”。
模型被用来表述人们所关注的现实或想法的某个方面,本质上是一种抽象过程的产物,把与解决方案密切相关的方面抽象出来,而忽略无关细节。
聚焦在软件工程领域,要想构建满足需求的软件系统,开发团队需要软件面向的领域所涉及的知识可能非常庞大和复杂,而模型正是解决这种信息超载问题的有效工具。
对领域进行模型设计的过程就是领域建模,领域建模的目的并非是要建立一个百分之百符合 “现实” 的模型,理论上,我们也无法实现这种对现实的完全建模,而只能是对现实某种程度的模拟。
领域建模的输出即领域模型,领域模型是针对特定领域里的关键事物及其关系的可视化表现,属于解决方案空间范畴。为了准确定义需要解决问题而构造的抽象模型,为软件系统的构建目标统一认知,是业务功能场景在软件系统里的映射转化。领域模型并非领域专家头脑中的知识,而是对这些知识进行严格的组织和有选择的抽象。
同时,领域模型并非某种特殊的图、文字或者代码,而是他们所传达的思想,图、文字或代码都可以作为模型的表示或传达形式,但他们不是模型,而是不同维度的模型视图。
领域驱动设计
领域驱动设计强调领域模型的重要性,并通过模型驱动设计来保障领域模型与程序设计的一致。领域驱动设计首先从业务需求中提炼出统一语言,并建立领域模型指导着程序设计以及编码实现;最后,又通过重构来发现隐式概念,并不断解决领域领域模型相关的新问题。本质上,领域驱动设计也是从问题空间映射到解决方案空间。
领域驱动设计结合了宏观和微观两个层面的设计,分别对应于领域驱动概念中的战略设计和战术设计。
领域驱动设计:战略设计
战略设计的初衷是要保持模型的完整性,主要从下面两个方面来考量的:
问题域方面:将问题规模进行拆解,划分为不同类型的子域,识别出核心领域与其他子领域。
解决方案层面:划分限界上下文和上下文映射对问题域进行合理的分解,确定上下文边界以及它们之间的关系。
领域驱动设计:战术设计
战略设计的初衷是要保持模型的完整性,通过战略设计将整个软件系统分解为多个限界上下文,然后对每个界限上下文进行战术设计。对每个限界上下文进行战术设计。Eric Evans 提供的模型驱动设计的构造要素以及要素间的关系如下图所示:
- 实体:Entity,不同于通过属性进行定义的传统对象,实体对象通过唯一标识进行区分,且具有持续的生命周期。
- 值对象:Value Object,值对象是具有属性且不可变的对象,但没有唯一标识。
领域事件:Domain Event,领域事件用于记录系统内的模型活动相关的离散事件,虽然系统内所有事件都应该能够被跟踪,但只有被领域专家关心的事件类型才创建领域事件。 - 聚合:Aggregate,聚合对象是实体和值对象的聚合,聚合具有一个唯一的根,即聚合根。外部对象不再直接访问聚合内部的单个对象或者实体,而是直接访问聚合根,并使用聚合根将指令传递给对应的分组。
- 领域服务:Domain Service,某些领域逻辑不适合分配给某个特定的实体对象,可将其这些操作封装成领域服务
- 资源库:Repositories,资源库不是配置库,它提供一个全局接口来访问特定聚合内部所有的实体类和值对象,应该包括创建,修改,删除聚合内部对象的方法。
- 工厂:Factories,工厂封装了创建复杂对象和聚合的逻辑,对客户端屏蔽创建的复杂性
上述 DDD 战术设计的模式标识了进行设计时的一些关键模式,但并非说是一定要严格使用和遵循的,也不是遵循了所有的战术设计模式就是符合领域驱动设计。因为,实践 DDD 关键不在于这种战术层面模式的落地,而是在于其宏观的领域驱动设计思想的遵循,比如统一语言、领域模型与代码间的一致、子域及上下文的拆分以及映射、领域模型与技术关注点的分离等等。另外,随着 DDD 的不断发展,一些新的构建模式已经涌现,老的构造模型不一定能符合团队研发的要求。
领域驱动设计为什么落地这么难?
- 需要鲁棒的领域知识,依靠项目中领域专家的支持
- 如果团队中没有熟悉应用所需领域知识的领域专家,即使具备技术再强的开发人员也无济于事。在某些情形下,领域驱动设计需要一个或多个外部人员在整个软件开发生命周期中扮演领域专家的角色。有些情况下,领域驱动设计需要在整个软件开发生命周期中与外部团队成员 (充当领域专家角色) 进行协作。
- 在创新型业务中应用 DDD 同样存在挑战,由于业务模式的不确定性,业务需求变化的频率和幅度很大,同样也缺乏新领域的领域专家,整个业务都处于一种探索模式,很难建立起相对稳定、高可复用的领域模型。
- 强调不断迭代和持续集成,对缺乏迭代经验而偏重于瀑布模型的团队可能导致障碍
领域驱动设计实践依赖不断迭代和持续集成来构建高可扩展的项目,但是这种基于迭代和持续集成的时间,在某些团队中落地可能会存在阻碍,特别是如果过去经验是建立在僵化的开发模型上,比如瀑布模型。
- 不适合偏向技术型的应用
领域驱动设计适合于具有非常高领域复杂性 (业务逻辑复杂) 的应用,但不适用于领域复杂性很低但技术复杂性很高的领域。DDD 着重强调需要领域专家以便构造出项目依赖的统一语言和领域模型,但是如果项目的技术复杂性很高,领域能否理解是一种挑战。当全体团队成员没有完全理解技术需求或限制时可能会导致问题
- 团队过于重视战术设计,导致实践准线和原则的偏离
团队对领域驱动设计的认知不够,精力没有聚焦在问题域拆分、统一语言、模型与技术关注点分离等核心原则上,而是一开始便从实现的角度触发,过度强调战术设计模式的落地,从而陷入无尽的技术细节之中
作者:倪新明