张逸说

出口成张,逸派胡言

0%

领域驱动建模与面向对象建模的差异

即便采用面向对象建模范式,领域驱动设计的建模仍与面向对象建模存在较大差异,原因在于领域驱动设计引入了限界上下文(Bounded Context)聚合(Aggregate),使得建模的风景变得迥然不同。二者为领域模型引入了边界的约束,使得建模者不能随心所欲地建模,亦改变了模型的面貌。

在我的《解构领域驱动设计》一书中,提炼了领域驱动设计之精髓,在于对边界的控制。其中,限界上下文与聚合是解空间边界控制中最为重要的两个要素。我们构建的领域模型必然位于限界上下文中,构成领域模型中的主力军——实体和值对象——又必然位于聚合的边界内:

虽然限界上下文只是逻辑边界,但它的自治特性需要保证其内部领域模型的独立性,建模时,必须避免出现跨限界上下文之间领域模型的直接引用。至于聚合之间的协作,社区的大多数声音认为:聚合之间需通过根实体的ID建立协作关系。

限界上下文与聚合边界的约束,使得我们需要重新正视领域模型类之间的关系,在面向对象世界中,设计者耳熟能详的继承、组合、依赖关系,可能需要做出改变。

以教育领域之用户模型为例。学生、教师、家长是3种不同类型的用户,各自业务不同,又有一些共用业务,如:修改密码。从面向对象的角度思考模型的建立,自然会想到通过为它们建立继承关系,将用户定义为基类,封装共同的领域逻辑满足复用的要求。由此形成如下的领域模型:

我将限界上下文视为领域模型的知识语境,通过它形成领域知识的逻辑边界。对相同的一个领域概念因为观察视角的不同,需要关注的领域知识(属性与行为)各有不同。犹如盲人摸象,在此,并非讽刺盲人只见大象之局部而错以为是整头大象,相反,代表了一种正面的含义,即从当前上下文观之,虽然明知此乃大象之局部,我仍然认为它就是大象之整体。

无论现实世界还是软件世界,谁又能穷尽任意一个领域概念的全部呢?就以“人”这一领域概念而言,在居民身份管理系统中,关注公民这一身份,除了姓名、性别、出生年月等基本信息之外,需关注籍贯、户籍所在地、照片等与公民身份有关的重要属性;在员工管理系统中,关注的内容又变成角色、职位以及教育背景、工作经验等与员工能力息息相关的属性。如果将一个“人”的所有属性加在一起,可以认为是一头完整的“大象”的话,居民身份管理系统与员工管理系统看到的“人”,何尝不像是盲人看到的大象局部呢?而在各自的上下文中,公民与员工就是当前需要关注的全部了。

系统如此,限界上下文亦如此,只是边界更小罢了。因而在教育领域的身份上下文,用户的全部就是诸如姓名、账号、密码以及身份认证等领域知识;切换到学籍上下文,知识语境发生了变化,用户变为了学生,关注的领域知识不再是对身份的认证,而是对学生信息如学籍号的管理,此时的家长,作为学生附带属性的一部分,表达了学生的家庭关系,抽象其概念,可以表示为“家庭成员”;再切换到教务上下文,则需要建立以教师、课程、年级、班级为核心的领域模型,此时的学生概念,变成了教学活动的参与者,建立了班级与学生之间的关系,需要明确学生的“学号”而非“学籍号”,抽象其概念,实则代表了“班级成员”。

在各自知识语境的界定下,如果仍然要保持学生、家长、教师与用户之间的继承关系,就显得“不合时宜”了。本质上,它们是同一个概念在不同语境下的局部知识,在切换上下文时,每个建模者看到的局部概念,都应视为一个整体,如果让学籍管理上下文的学生继承自身份管理上下文的用户,就好似让一头“局部的大象”去继承另外一头“局部的大象”,真是匪夷所思了!正确的领域模型应该如下图所示:

因此,在领域驱动设计的领域建模中,需要建立上下文为王的意识。形成的一个个自治的限界上下文,就是领域模型的“独立王国”,除了需要调用必须的对外公开的业务能力之外,它们几乎是”老死不相往来“的。在跨限界上下文的领域模型之间,即便是相对弱耦合的依赖关系也当避免,更不用说继承、合成与聚合关系了。除非,我们将某个限界上下文设计为“共享内核(shared kernel)”。

在领域驱动设计获得的设计模型中,一个聚合作为边界封装了实体和值对象。我们可以将聚合“伪装”为一个完整的类(其实质是领域模型的边界),与之对应的设计要素还包括管理聚合生命周期的资源库(repository)和负责协作外部资源或多聚合的领域服务(domain service)。多数情况下,一个聚合、一个资源库和一个领域服务可能都需要定义,它们的关系如下图所示:

资源库和领域服务在操作聚合时,是将聚合当作一个完整的整体概念来看待的。领域驱动设计尤其重视聚合对领域模型的边界控制。在设计领域模型时,需要清晰定义出聚合的边界,然后再由此推导出资源库和领域服务。在学籍管理上下文中,我将家长放到了以学生为根实体的聚合中,如下图所示:

一方面,家长信息如家庭住址一般都是学生的属性,另一方面,需要约束学生与家长关系的不变量(Invariant),例如,在为学生添加家长信息时,总不能出现两个父亲吧?——谁愿意呢!

由此可知,当限界上下文作为领域模型的边界时,一方面它限制了跨限界上下文之间领域模型的关系,另一方面它作为知识语境,分离了同一个领域概念的不同视角。我将限界上下文称为战略设计的基本架构单元

聚合作为领域模型的边界,维护的是当前上下文领域概念的完整性,并将其作为一个不可分割的整体,由资源库管理其生命周期。它平衡了领域对象粒度与数量的矛盾,既可以确保每个领域概念的细粒度,又可通过聚合根的封装在形式上减少领域对象的数量。通过聚合边界的控制,减少了领域对象之间不必要的依赖,并通过约束聚合之间的关系来降低耦合。我将聚合称为战术设计的基本设计单元

对象建模范式的领域建模确乎是建立在面向对象思想之上的,但领域驱动设计考虑了软件世界与理想的对象世界之差异,不只是考虑领域模型的关系与协作,还考虑领域模型与外部资源的关系,这就需要施加恰当的约束,进一步保证领域模型的质量。这是我们在进行领域建模时务必谨记的要点。