上一篇学习了基本概念,对DDD有了一个整体的把控,那么,这一篇就是对战略建模进行介绍了,首先什么是战略,战略,是一种从全局考虑谋划实现全局目标的规划,战术只为实现战略的手段之一。实现战略胜利,往往有时候要牺牲部分利益,去获得战略胜利。战略是一种长远的规划,是远大的目标,往往规划战略、制定战略、用于实现战略的目标的时间是比较长的。争一时之长短,用战术就可以达到!如果是“争一世之雌雄”,就需要从全局出发去规划,这就是战略!以上内容来自百度。总而言之,战略是大框架,大方向,如果架子搭错了,方向走错了,战术再犀利,也没有什么作用。那么对于DDD来说,战略的实现主要有以下两个方面:1,限界上下文,2,上下文映射

图片来自https://www.cnblogs.com/Leo_wl/p/4343177.html


该图片详细表明了在战略建模的时候各方面需要做的事情,核心所在就是解决问题空间,说白了就是,限界上下文的圈定和上下文映射的方式。接下里详述这两方面的内容

限界上下文(Bounded Context)

定义

它是一个限定边界的环境,在该环境中,每一个模型的概念(包括它的属性和操作)都具有特殊的含义。它是战略建模的核心。 换句话说,在每一个限界上下文里都共用且只有一套通用语言,不存在二义性。

概念解析

学习自《实现领域驱动设计》

与子域最好一对一

一般一个限界上下文对应一个子域。另外一个限界上下文有可能包含的不只一个子域。

分阶段创建上下文

如一个图书出版系统,图书生命周期的不同阶段(不同的上下文环境)
概念设计,计划出书
联系作者,签订合同
管理图书的编辑过程
将图书翻译成其他语言
出版纸质版或电子版图书
市场营销
将图书卖给销售商或直接卖给读者
将图书发送给销售商或读者
以上所有阶段,我们用一个单一的概念对图书建模显然不行,每个阶段“图书都有不同的定义”。一本书只有和作者签订了合同之后才能拥有书名,而书名可能在编辑过程进行修改。在编辑过程中,图书包含了一系列的稿件,其中包括注释和校正,走到最终定稿。图书印刷方使用页面布局和封面板式印制图书。营销员不需要编辑和印制,只需要图书的简介,图书售后物流,匀人需要图书的标识码、物流目的地、数目、尺寸和重量等。
如果我们用单一模型来处理所有这些阶段会发生什么?概念混淆、意见分歧和争论是不可避免的。即使有时可能会得到一个正确的公共模型,但这种模型并不具有持久性。
为解决这个问题,
我们应该为每个阶段创建各自的限界上下文,在每个限界上下文 ,都存在某种类型的图书。在所有的上下文中,不同类型的图书对象将共享一个身份标识,这个标识可能是在概念设计阶段创建的。然而,不同上下文中的图书模型却是不同的。
如果在不同的限界上下文看到了完全相同的对象,这通常意味着你的模型是错误的,除非这些限界上下文使用了共享内核(Shared Kernel)。

不仅限于包含模型

限界上下文不仅仅只包含模型。虽然领域模型是限界上下文的主要“公民”。但是限界上下文并不只局限于容纳模型,它通常标定一个系统、一个应该程序或一种业务服务(表示一系统用于实现业务用例的复杂组件)。有时,限界上下文所包含的内容可能比较少,比如,一个通用子域便可以只包含领域模型。举例来说, 当模型驱动着数据库Schema时,此时的Schema也应该位于该模型所处的上下文边界之内。换言之,数据库中表和列名字应该和模型的名字保持一致。模型应该用来驱动数据库和用户界面的设计,而不是反向驱动。

不要随意拆分上下文

不要为了分配任务而拆分限界上下文。我们没有必要为了管理技术资源而创建一些假的上下文边界。

使用语言驱动

采用语言驱动来实施DDD,这里底线:如果没有采用语言驱动,那么你就不算在和领域专家一起工作来创建限界上下文。

认真设计大小,不要急于小型化

认真考虑限界上下文的大小,不要急于将其小型化。

与技术组件保持一致

与技术组件保持一致。技术组件并不能定义限界上下文。一个限界上下文通常就一个工程项目。VS中在同一个解决方案中将用户界面、应用服务和领域对象分离在不同的子项目中是合理的。项目源代码可以只包含领域模型,也可以包含一些周边的层或六边形区域。

上下文映射图(Context Mapping)

基本概念

上下文映射图(Context Map):可以进行拆分理解,上下文指的就是限界上下文,映射的意思就是关联、联系,就像 ORM 中,对象与关系的映射,图就是把限界上下文之间的关联与联系表现出来,具体的展示就是类似上面的图。

上游与下游的概念,上游是服务提供者,下游是服务接收者

上下文映射图主要帮助我们从解决方案空间的角度看待问题。

九大关系

基本有九个定义,也可以理解为九种关系

  • 合作关系(Partnership):如果两个限界上下文的团队要么一起成功,要么一起失败,要么一起成功,此时他们需要建立起一种合作关系。他们需要一起协调开发计划和集成管理。两个团队应该在接口的演化上进行合作以同时满足两个系统的需求。应该为相互关联的软件功能定制好计划表,这样可以确保这些功能在同一个发布中完成。

  • 共享内核(Shared Kernel):对模型和代码的共享将产生一种紧密的依赖性,对于设计来说,这种依赖性可好可坏。我们需要为共享的部分模型指定一个显式的边界,并保持共享内核的小型化。共享内核具有特殊的状态,在没有与另一个团队协商的情况下,这种状态是不可改变的。我们应该引入一种持续集成过程来保证共享内核和通用语言的一致性。

  • 客户方-供应方开发(Customer-Supplier Development):当两个团队处于一种上游-下游关系时,上游团队可能独立于下游团队完成开发,此时下游团队的开发可能会受到很大的影响。因此,在上游团队的计划中,我们应该顾及到下游团队的需求。记住:上游是服务提供者,下游是服务使用者

  • 遵奉者(Confoemist):在存在上游-下游的关系的两个团队中,如果上游团队已经没有动力提供下游团队之所需,下游团队便孤立无援了。出于利于他主义,上游团队可能向下游团队做出种种承诺,但是有很大的可能是:这些承诺是无法实现的。下游团队只能盲目的使用上游团队的模型。

  • 防腐层(Anticorruption Layer):在集成两个设计良好的限界上下文时,翻译层可能很简单,甚至可能很优雅的实现。但是,当共享内核、合作关系或客户方-供应方关系无法顺利实现时,此时的翻译将变得复杂。对于下游客户来说,你需要根据自己的领域模型创建一个单独的层,该层作为上游系统的委派向你的系统提供功能。防腐层通过已有的接口与其他系统交互,而其他系统只需要做很小的修改,甚至无须修改。在防腐层内部,它在你自己的模型和他方模型之间翻译转换。

  • 开放主机服务(Open Host Service):定义一种协议,让你的子系统通过该协议来访问你的服务。你需要将该协议公开,这样任何想与你集成的人都可以使用该协议。在有新的集成需求时,你应该对协议进行改进或扩展。对于一些特殊的需求,你可以采用一次性的翻译予以处理,这样可以保持协议的简单性和连贯性。

  • 发布语言(Published Language):在两个限界上下文之间翻译模型需要一种公用的语言。此时你应该使用一种发布出来的共享语言来完成集成交流。发布语言通常与开放主机服务一起使用

  • 另谋他路(SpeparateWay):在确定需求时,我们应该做到坚决彻底。如果两套功能没有显著的关系,那么它们是可以被完全解耦的。集成总是昂贵的,有时带给你的好处也不大。声明两个限界上下文之间不存在任何关系,这样使得开发者去另外寻找简单的、专门的方法来解决问题。

  • 大泥球(Big Ball of Mud):当我们检查已有系统时,经常会发现系统中存在混杂在一起的模型,它们之间的边界是非常模糊的。此时你应该为整个系统绘制一个边界,然后将其归纳在大泥球范围之列。在这个边界之内,不要尝试使用复杂的建模手段来化解问题。同时,这样的系统有可能会向其他系统蔓延,你应该对此保持警觉。大泥球其实是一种解决方案,不让混乱蔓延,同时屏蔽混乱

其中ACL表示防腐层,OHS表示开放主机服务,PL表示 发布语言。

绘制上下文映射图

初步绘制

明确需要创作协作上下文,同时需要创建第二个限界上下文,但不知道如何将此上下文从核心域分离

分离子域

在对子域分析和问题空间评估后,从一个限界上下文中分离出了两个子域,由于子域和限界上下文最好是一对一的关系,我们应该将原先的协作上下文分离成两个限界上下文

分离内核

由于角色的分离,有必要进行内核的分离

最终版本

最终版本确定了三个上下文之间的关系

上图可以看出,核心的敏捷项目管理位于下游,也就是表示核心模型位于其他系统的下游,上游模型会对下游模型产生影响,不论是积极的还是消极的。

技术要点

开放主机服务

该模式可以通过REST来实现,通常来讲,可以将开放主机服务看成是远程过程调用API,同时,它也可以通过消息机制实现。

发布语言

实现方式有多种,发布语言可以有多种实现方式,最常用的就是XML和Schema

1,在使用REST服务时,发布语言用来表示领域概念,此时可以使用XML和JSON。

2,如果你打算发布web界面,也可以使用HTML。

3,同时发布语言还可以用于事件驱动框架,领域事件以消息的形式发送给订阅方

防腐层

在下游上下文中,我们可以为每个防腐层定义相应的领域服务。同时你可以将防腐层用于资源库接口。

1,在使用REST时,客户端的领域服务将访问远程的开放主机服务

2,远程服务器以发布语言的形式返回

3,下游的防腐层将返回的内容翻译成本地上下文的领域对象。

比如,协作上下文向身份与访问上下文请求“具有Moderator角色的用户”。所返回的数据可能是XML格式或者JSON格式,然后防腐层将这些数据翻译成协作上下文中的Moderator对象,该对象是一个值对象。之歌Moderator实例反映的是下游模型中的概念,而不是上游模型。

如何自治

非自治

接上边的例子,协作上下文向身份与访问上下文请求“具有Moderator角色的用户”。

这里的请求是同步的,如果远程系统不可用,那么本地系统也将跟着失败,REST通常与RPC相似,彻底的失败并不多见,但仍然是潜在问题。

实现自治

为了达到比RPC更高的自治性,采用如下两种措施

1,选择使用异步请求,或者事件处理的方式

2,如果系统所以来的状态已经存在于本地,那么我们将获得更大的自治性,但这里我们DDD不会采用对依赖对象缓存的方式,而是:在本地创建一些由外部模型翻译而成的领域对象,这些对象保留着本地模型所需的最小状态集。为了初始化这些对象,我们只需有限的进行RPC调用或者REST请求。然而,要与远程模型保持同步,最好的方式时在远程系统中采用面向消息的通知机制,消息可以通过服务总线进行发布,也可以采用消息队列或者REST

敏捷项目管理上下文和身份与访问上下文集成时的映射

在该种自治机制下:

1, MemberService是一个领域服务,它向本地模型提供ProductOwner和TeamOwner对象,同时作为防腐层的接口

2,mantainService()方法用于周期性的检查身份与访问上下文所发出的通知。该方法不由模型的正常客户调用,而是由通知组件周期性调用。如下图的MemberSynchronizer表示这样的通知组件,他会把请求委派给MemberService.

3,MemberService.会进一步把请求委派给IdentityAccesssNotificationAdapter,该类在领域服务和远程的开发逐句服务之间扮演适配器的角色,该适配器作为远程系统的客户端而存在

4,一旦适配器从远程开放逐句服务获得了数据,它将调用MemberTranslator的toMember()方法将发布语言中的媒体数据翻译成本地系统的领域对象Member.如果该对象已存在,则更新之。

敏捷项目管理上下文和协作上下文集成时的映射