3

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

 3 years ago
source link: http://zhangyi.xyz/the-difference-between-ddd-and-oo/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

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

发表于

2021-07-20 更新于 2021-07-22 分类于 DDD

阅读次数: 26 Valine: 1 本文字数: 2.4k 阅读时长 ≈ 2 分钟

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

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

boundary.png

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

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

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

OO-user.png

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

elephant.png

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

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

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

model-in-bc.png

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

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

service-repo-agg.png

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

student.png

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

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

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

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


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK