DDD—上下文映射图

一个项目的上下文映射图(Context Map)可以用两种方式表示,
(1)比较容易的一种是画一个简单的框图来表示两个或多个限界上下文之间的映射关系。该框图表示了不同的限界上下文在解决方案空间中是如何通过集成相互关联的。

(2)另一种更详细的方式通过限界上下文集成的源代码实现来表示。

1、上下文映射图为什么重要

在开始采用DDD时,首先应该为你当前的项目绘制一个上下文映射图,其中应该包含你项目中当前的限界上下文和它们之间的集成关系。下图表示一个抽象的上下文映射图,后文将不断地向里面添加细节内容。

在这里插入图片描述

比如,当你为一个大型企业进行限界上下文之间的集成时,你可能需要与大泥球进行交互。大泥球的维护团队才不关心你的项目呢,因为你依赖于他们的API、因此,他们并不会深入到你的上下文映射图中,然而你的映射图依然需要反映出和他们的集成关系,因为这样可以使你了解到映射图的内部,并且可以指明在哪些地方需要与其他团队进行交流。这样对你团队的成功是有帮助的。

假定你期望大泥球维护团队提供一套新的API。然而,他们并不打算这样做。此时你的团队和大泥球的维护团队的关系便成了客户方-供应方的关系。由于大泥球团队决定维持现状,你的团队不得不陷入一种遵奉者关系中。尽早绘制上下文映射图,这样可以迫使呢仔细思考你的项目和你所依赖项目之间的关系。

CollabOvation团队应该在建模之初就使用上下文映射图,然而当时他们对于战略建模一无所知。后来,通过经验积累,在之后的ProjectOvation中,尝到了建模的甜头。

上下文映射图表现的是项目当前的状态,如果项目发生变化,你可以更新上下文映射图。注意上下文映射图不需要画得太正式了,手绘即可。

2、产品和组织关系

这一节主要重复下,整个专栏的案例说明,详情见:DDD案例说明。

SaaSOvation公司正在开发的3个产品:

1、CollabOvation——一款社交协作软件。该产品允许注册用户发布对业务有价值的内容,发布方式是一些流行的基于Web的工具,比如论坛、共享日历、博客和wiki等。这是SaaSOvation公司的旗舰产品,也是该公司的第一个核心域。开发团队后来从CollabOvation中提取了IdOvation模型。对于CollabOvation来说,IdOvation是一个通用子域,而CollabOvation本身又作为ProjectOvation的支撑子域。

2、IdOvation——一款可重用的身份和访问管理产品。IdOvation为注册用户提供安全的、基于角色的访问管理。这些功能一开始和CollabOvation混合在一起,这样导致的问题是:实现受到了限制,功能不可重用。SaaSOvation对CollabOvation进行了重构,引入了一个新的、结构清晰的限界上下文。SaaSOvation公司决定支持多个租户,这种功能对于SaaS产品来说是至关重要的。对于消费方来说,IdOvation扮演着通用子域的角色。

3、ProjectOvation——一个敏捷项目管理产品。这是SaaSOvation公司的核心域。ProjectOvation采用基于Scrum的项目运行框架,用户可以创建项目管理资产,同时对项目资产进行分析和设计,还能跟踪项目进度。和CollabOvation一样,ProjectOvation将IdOvation作为一个通用子域来使用。该产品的一大创新是将CollabOvation引入了敏捷项目管理,这样用户可以围绕Scrum的产品、发布、冲刺和待定项展开讨论。

这些限界上下文之间的关系如何,不同开发团队之间的关系又如何?让我们看下各种关系之间的定义:

  • 合作关系(partnership):如果两个限界上下文的团队要么一起成功,要么一起失败,此时他们需要建立起一种合作关系。他们需要协调开发计划和集成管理。

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

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

  • 遵奉者(conformist):在存在上游-下游关系时,如果上游团队已经没有动力提供下游团队之所需,下游团队便孤军无助了。只能盲目地使用上游团队的模型。

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

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

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

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

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

在与身份与访问上下文集成时,协作上下文和敏捷项目管理上下文均没有采用另谋他路的手法。诚然,在上下文范围之内,另谋他路的手法可以应用在特殊的系统上,同时它也可以用于一些个例。比如,一个团队可能拒绝使用集中式的安全管理系统,但他们依然会与另外类型的安全管理系统集成。

在集成时,他们将会用到开放主机服务和发布语言,有可能还会用到防腐层。虽然这两者都会在限界上下文之间创建开放的标准,但是它们并不矛盾。通过使用下游上下文中的基本原则,他们依然可以得到由独立翻译所带来的好处,并且不会像与大泥球集成那样复杂。翻译层将变得简单、优雅。

在上下文映射图中,我们使用以下缩写来表示各种关系:

  • ACL 表示防腐层
  • OHS 表示开放主机服务
  • PL 表示发布语言

3、映射3个示例限界上下文

当CollabOvation团队意识到自己已经陷入僵局时,他们研究发现“上下文映射图”的工具非常实用。

从团队设计的第一张映射图可以看出,他们已经知道应该创建一个名为“协作上下文”的限界上下文。从图中怪异的边界又可以看出,他们可能希望创建第二个限界上下文,但是却不知道如何将这个上下文从核心域中分离出来。
在这里插入图片描述

团队成员意识到安全、用户和权限并不属于协作上下文,需要将这些概念从核心域中分离出来,是这些概念只有在得到同意的时候才能进入核心域。

在对子域进行了分析,或对问题空间进行了评估之后,团队所绘制的上下文映射图如图所示,从一个限界上下文中分离出了两个子域。由于子域和限界上下文最好保持一对一的关系,我们应该将原来的协作上下文分离成两个限界上下文。

在这里插入图片描述

对子域和边界的分析要求我们做出决定。当人们需要使用CollabOvation的功能时,他们扮演者时参与者、作者和主持者的身份。有了这样的角色分离,我们便可以绘制一个更高层次的上下文映射图,如图所示。团队使用了分离内核对系统进行重构。

在这里插入图片描述

当下一个ProjectOvation项目启动时,团队将使用这个新的核心域——敏捷项目管理上下文来增强已有的上下文映射图,如图所示。

在这里插入图片描述

SaaSOvation的开发团队已经对核心的模型分离有了很好的理解。与CollabOvation相似,当ProjectOvation的用户扮演的是产品负责人或团队成员的角色。身份与访问上下文已经从核心域中分离出去了。协作上下文对于ProjectOvation来说只是一个支撑子域。任何时候,这个新的模型都受到了上下文边界的保护,外部概念需要通过翻译才能进入核心域中。

身份与访问上下文位于最上游,它对协作上下文和敏捷项目管理上下文均会产生影响。同时,协作上下文又是敏捷项目管理上下文的上游,因为后者的模型依赖于前者的模型和服务。

在限界上下文中我们提到,ProjectOvation将自治地运行,而不会依赖于周边的环境。这并不是说自治服务就可以完全独立于上游模型,而是我们的设计应该尽可能地限制实时依赖性。虽然ProjectOvation是自治的,但是它依然属于其他系统的下游。

上图中,上游系统的连接框,都标以OHS/PL,分别表示开放主机服务和发布语言。所有下游的连接框都标以ACL,即防腐层。简单地来说,这些集成模式采用以下技术:

  • 开放主机服务:该模式可以通过REST实现。通常来讲,我们可以将开放主机服务看成是远程过程调用(RPC)的API。同时,他也可以通过消息机制实现。

  • 发布语言:发布语言可以通过多种形式实现,但最常见的是使用XML Schema。在使用REST服务时,发布语言用来表示领域概念,此时可以使用XML和JSON。发布语言既可以使用标准的媒体类型进行发布,也可以使用自定义类型。同时,发布语言还可以用于事件驱动架构(Event-Driven Architecture),其中领域事件以消息的形式发送给订阅方。

  • 防腐层:在下游上下文中,我们可以为每个防腐层定义相应的领域服务(Domain Service)。同时,你也可以将防腐层用于资源接口。在使用REST时,客户端的领域服务将访问远程的开放主机服务,远程服务以发布语言的形式返回,下游的防腐层将返回内容翻译成本地上下文的领域对象。比如,协作上下文向身份与访问上下文请求“具有Moderator角色的用户”。所返回的数据可能是XML格式或JSON格式,然后防腐层将这些数据翻译成协作上下文中的Moderator对象,该对象是一个值对象。这个Moderator实例反映的是下游模型中的概念,而不是上游模型。

由于协作上下文是第一个核心域,让我们把它放大来看看。首先我们将接触到一些简单的集成方式,然后再是更高级的。

4、协作上下文

协作上下文作为SaaSOvation公司的第一个核心域。开发团队已经对其有很好的理解了。这里他们使用的集成方式比较简单,但是在可靠性和自治性上还稍逊一筹。要将此上下文映射图进行放大还是相对容易的。

身份与访问上下文通过REST的方式向外发布服务。作为该上下文的客户,协作上下文通过传统类似于RPC的方式来获取外部资源。协作上下文并不会永久性地记录下从身份与访问上下文中获取来的数据,而是在每次需要数据时重新向系统发出请求。显然,协作上下文高度依赖于远程服务,它不具有自治性。但目前SaaSOvation公司愿意采取这种方式集成,以满足交付计划。由于之后的ProjectOvation项目将使用自治性服务,CollabOvation到时可以借鉴ProjectOvation的做法。

在这里插入图片描述

上图是放大后的上下文映射图,下游系统的边界对象采用同步的方式向上游系统获取到资源。当获取到远程数据模型数据之后,边界对象取出所需数据,再将其翻译成适当的值对象实例。

在这里插入图片描述

在上图中,翻译图(Translation Map)将所获取数据转化成一个值对象。这里,身份与访问上下文中一个具有Moderator角色的User被翻译成了协作上下文中的Moderator值对象。

不幸的是,如果由于远程系统不可用而导致同步请求失败,那么本地系统也将跟着失败。虽然REST并不是真正意义上的RPC,但它却具有与RPC相似的特征。彻底的系统失败并不多见,但它却是一个潜在的问题。CollabOvation团队急切地希望解决这个问题。

5、敏捷项目管理上下文

为了达到比RPC更高的自治性,敏捷项目管理上下文团队将尽量限制对RPC的使用,此时他们可以选择异步请求,或者事件处理等方式。

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

被同步的状态应该是本地模型所需远程模型的最小属性集。这里并不只是限制我们的对数据的需求,还应该对概念进行恰当的建模。

6、和身份与访问上下文集成

在这里插入图片描述

在图中我们看到,对于身份与访问上下文的领域事件,系统将以URI的方式向外发布事件通知。这种功能是通过NotificationResource提供的,它向外发布REST资源。这里的通知资源是一组被发布的领域事件。对于消费方来说,每个事件总是可用的。消费方应该避免对事件的重复消费。

一个自定义的媒体类型表明客户可以请求两种资源:
在这里插入图片描述

通过第一个资源URI,客户可以(使用HTTPGET请求)获取当前的通知日志(一个固定大小的通知集合)。对于自定义的媒体类型:
在这里插入图片描述

可以看出,这里的URI是全新的,并且是稳定的,因此它不会改变。无论当前的通知日志中包含了什么样的内容,该URI都会将其发布。

事实上,ProjectOvation团队并不打算全盘使用REST。比如,他们目前正与CollabOvation团队协商是否可以使用消息机制,例如rabbitMQ。但就目前而言,它们和身份与访问上下文的集成依然是基于REST的。

现在,让我们忽略技术细节,看看映射图中各个交互对象所扮演的角色。如图表示集成过程的序列图:

在这里插入图片描述

  • MemberService是一个领域服务,它向本地模型提供ProductOwner和TeamOwner对象,同时作为基本防腐层的接口。maintainService方法用于周期性地检查身份与访问上下文所发出的通知。该方法不由模型的正常用户调用,而是由通知组件周期性地调用,在上图中,MemberSynchronizer表示这样的同步组件,它会把请求委派给MemberService。
  • MemberService进一步把请求委派给IdentityAccessNotificationAdapter,该类在领域服务和远程开放主机服务之间扮演着适配器的角色。该适配器作为远程系统的客户端而存在。与远程系统NotificationResource交互并没有显示在图中。
  • 一旦适配器从远程的开放主机服务获取到了数据,它将调用Member
    Translator的toMember()方法将发布语言中的媒体数据翻译成本地系统中的领域对象Member。如果该对象在本地系统中已经存在,则更新该对象。MemberService的updateMember()方法用于更新一个Member
    对象,此时它把委托操作委派给了自己。Member的子类有ProductOwner和TeamMember,它们反映了本地系统中的上下文概念。

我们不应该将重点放在技术实现和集成产品上,而应该放在限界上下文之间的分离上,这样我们可以保持每个上下文的纯洁性,同时将一个上下文中的数据用于另一个上下文的概念中。

让我们看看敏捷管理上下文是如何与协作上下文集成的。同样,我们关注的是系统的自治性,但这给集成带来了更多的困难与挑战。

ProjectOvation将使用CollabOvation所提供的附加功能,比如论坛和共享日历等。ProjectOvation用户并不直接与CollabOvation交互。对于某租户来说,ProjectOvation必须决定出哪些CollabOvation的功能时该租户可用的。然后,ProjectOvation将协调对CollabOvation资源的创建。

比如,Forum和Discussion必须在协作上下文进行创建的。在身份与访问上下文中,对象是先前存在的。而对于敏捷项目管理上下文来说,对象是不会预先存在的,直到被请求为止。这对于实现系统自治性来说是一个潜在的障碍,因为我们依赖于协作上下文来远程地创建资源。

在使用领域事件和事件驱动架构时,我们应该仔细思考最终一致性。本地系统产生的事件通知并不是只能由远程系统消费。在ProjectOvation中,当ProjectInitiated事件产生时,该事件将由本地系统进行处理。本地系统要求Forum和Discussion在远程完成创建。这可以通过RPC或消息机制完成。

如果项目负责人是图使用一个不存在的Discussion会发生什么问题吗?有时是由于调用失败,有时是由于没有预先付款导致协作功能不可用,这不一定是一个技术原因。对于最终一致性的处理我们应该将其考虑在建模访问之内。

处理资源不可用的一个好办法便是将其显现出来。考虑以下由标准类型实现的状态和模式。此时的状态是一个值对象。
在这里插入图片描述
在该设计中,由DiscussionAvailability定义的状态将对象Discussion其保护作用。当有人是图参加关于一个Product的讨论时,该设计将正确地处理Discussion的状态。如果状态不为READY,参与者将得到以下消息之一:
(1)要使用团队协作功能,你需要购买附加功能。
(2)产品负责人还没请求创建产品讨论。
(3)讨论启动失败,请稍后再试。

此时,ProjectOvation团队还不清楚采用哪种方式与CollabOvation进行集成。在讨论了客户方-供应方关系后,它们得到了下图:
在这里插入图片描述

敏捷项目管理上下文可以使用第二个防腐层来处理与协作上下文之间的集成。事实上CollaborationAdapter并不是单个,此处只是一个占位符,表示有多个适配器。

敏捷项目上下文在本地有DiscussionService和SchedulingService,它们是领域服务,用于管理协作系统中的讨论和日历条目。

以上的例子已经展示了上下文映射图的某些细节。

对于一个非常详细的上下文映射图,我们很有可能无法对其进行实时更新。保持简单性和敏捷性,拒绝繁文缛节,这样我们创建的上下文映射图将对项目起推动作用,而不是阻碍作用。

©️2020 CSDN 皮肤主题: 黑客帝国 设计师:上身试试 返回首页