通过一个案例引入
案例:客户发票系统共用了一个charge对象,引发了bug。
问题在于这两个团队使用了不同的模型,而他们并没有认识到这一点,也没有用于检测这一问题的过程。每个团队都对Charge对象的特性做了一些假设,使之能够在自己的上下文中使用(一个是向客户收费,另一个是向供应商付款)。
当他们的代码被组合到一起而没有消除这些矛盾时,结果就产生了不可靠的软件。如果他们一开始就意识到这一点,就能决定如何来解决它。他们可以共同开发出一个公共的模型,然后编写自动测试套件来防止以后出现意外。也可以双方商定开发各自的模型,而互相不干扰对方的代码。无论采用哪种方法,首先都要明确边界,各模型只在各自的边界内使用。
模型最基本的要求是它应该保持内部一致。模型的内部一致性又叫做统一。
通过案例引出Bound Context
通过预先决定什么应该统一,并实际认识到什么不能统一,我们就能够创建一个清晰的、共同的视图。确定了这些之后,就可以着手开始工作,以保证那些需要统一的部分保持一致,不需要统一的部分不会引起混乱或破坏模型。
作用?
BOUNDED CONTEXT(限界上下文)定义了每个模型的应用范围,而CONTEXT MAP(上下文图)则给出了项目上下文以及它们之间关系的总体视图。
Bound Context(限定上下文)
举例子说明,限定上下文的作用:细胞之所以能够存在,是因为细胞膜限定了什么在细胞内,什么在细胞外,并且确定了什么物质可以通过细胞膜。
实际问题:
大型项目上会有多个模型共存,在很多情况下这没什么问题。不同的模型应用于不同的上下文中,即使在同一个团队中,也可能会出现多个模型。团队的沟通可能会不畅,导致对模型的理解产生难以捉摸的冲突。
解决之道:
1.为了解决多个模型的问题,我们需要明确地定义模型的范围——模型的范围是软件系统中一个有界的部分,这部分只应用一个模型,并尽可能使其保持统一。团队组织中必须一致遵守这个定义。
2.明确地定义模型所应用的上下文。根据团队的组织、软件系统的各个部分的用法以及物理表现(代码和数据库模式等)来设臵模型的边界。在这些边界中严格保持模型的一致性,而不要受到边界之外问题的干扰和混淆。
3.在CONTEXT中,要保证模型在逻辑上统一,而不用考虑它是不是适用于边界之外的情况。
例子:
一家运输公司的内部项目——为货物预订开发一个新的应用程序。这个应用由一个对象模型驱动。那么这个模型所应用的BOUNDED CONTEXT是什么呢 ?
边界识别
1.预订应用程序在BOUNDED CONTEXT的边界之内。
2.已完成的预订必须传递给用于货物跟踪的遗留系统来处理,原来的货物跟踪系统位于BOUNDED CONTEXT的边界之外。
3.团队正在开发安排货轮航次的模型和应用,他们不在同一个BOUNDED CONTEXT中工作。
作用:
通过定义这个BOUNDED CONTEXT,最终得到了什么?对CONTEXT内的团队而言:清晰!CONTEXT之外的团队获得了:自由。
什么情况会造成BOUNDED CONTEXT中的不一致?
有两种:重复的概念和假同源。
1.重复的概念是指两个模型元素(以及伴随的实现)实际上表示同一个概念。每当这个概念的信息发生变化时,都必须更新两个地方。
2.假同源可能稍微少见一点,但它潜在的危害更大。它是指使用相同术语(或已实现的对象)的两个人认为他们是在谈论同一件事情,但实际上并不是这样。
CONTINUOUS INTEGRATION(持续集成)
问题?
当很多人在同一个BOUNDED CONTEXT中工作时,模型很容易发生分裂。团队越大,问题就越大,但即使是3、4个人的团队也有可能会遇到严重的问题。然而,如果将系统分解为更小的CONTEXT,最终又难以保持集成度和一致性。
解法:
极限编程(XP)+ CONTINUOUS INTEGRATION 过程。
持续继承过程具备的特征:
-
分步集成,采用可重现的合并/构建技术。
-
自动测试套件;
-
有一些规则,用来为那些尚未集成的改动设臵一个相当小的生命期上限。
-
在讨论模型和应用程序时要坚持使用UBIQUITOUS LANGUAGE。
-
多数敏捷项目至少每天会把每位开发人员所做的修改合并进来。
CONTEXT MAP(上下文地图)
CONTEXT MAP位于项目管理和软件设计的重叠部分。按照常规,人们往往按团队组织的轮廓来划定边界。紧密协作的人会很自然地共享一个模型上下文。不同团队的人员(或者在同一个团队中但从不交流的人)将使用不同的上下文。
CONTEXT MAP无需拘泥于任何特定的文档格式。本章的简图在可视化和沟通上下文图方面很有帮助。
CONTEXT MAP 的组织和文档化
(1) BOUNDED CONTEXT应该有名称,以便可以讨论它们。这些名称应该被添加到团队的UBIQUITOUS LANGUAGE中。
(2) 每个人都应该知道边界在哪里,而且应该能够分辨出任何代码段的CONTEXT,或任何情况的CONTEXT。
Bound Context之间的关系
如果团队需要为不同的用户群提供服务,或者团队的协调能力有限,可能就需要采用SHARED KERNEL(共享内核)或CUSTOMER/SUPPLIER(客户/供应商)关系。大多数项目都需要与遗留系统或外部系统进行一定程度的集成,这就需要使用OPEN HOSTSERVICE(开放主机服务)或ANTICORRUPTION LAYER(防护层)。
SHARED KERNEL(共享内核)不能像其他设计部分那样自由更改。在做决定时需要与另一个团队协商。共享内核中必须集成自动测试套件,因为修改共享内核时,必须要通过两个团队的所有测试。SHARED KERNEL通常是CORE DOMAIN,或是一组GENERIC SUBDOMAIN(通用子领域)。
Customer/Supplier Developement team (团队协作模式)
两个不同的团队被分到不同的领域上下文中,团队之间如何协作?
下游团队依赖于上游团队,但上游团队却不负责下游团队的产品交付。要琢磨拿什么来影响对方团队,是人性呢,还是时间压力,亦或其他诸如此类的,这需要耗费大量额外的精力。怎么解决呢?
在极限编程项目中,已经有了实现此目的的机制——迭代计划过程。我们只需根据计划过程来定义两个团队之间的关系。下游团队的代表类似于用户代表,参加上游团队的计划会议,上游团队直接与他们的“客户”同仁讨论和权衡其所需的任务。在两个团队之间建立一种明确的客户/供应商关系。在计划会议中,下游团队相当于上游团队的客户。根据下游团队的需求来协商需要执行的任务并为这些任务做预算,以便每个人都知道双方的约定和进度。两个团队共同开发自动化验收测试,用来验证预期的接口。把这些测试添加到上游团队的测试套件中,以便作为其持续集成的一部分来运行。这些测试使上游团队在做出修改时不必担心对下游团队产生副作用。
关键要素:
1)关系必须是客户与供应商的关系,其中客户的需求是至关重要的。
2)必须有自动测试套件,使上游团队在修改代码时不必担心破坏下游团队的工作,并使下游团队能够专注于自己的工作,而不用总是密切关注上游团队的行动。
conformist (跟随者)
问题:
当两个具有上游/下游关系的团队不归同一个管理者指挥时,CUSTOMER/SUPPLIER TEAM这样的合作模式就不会奏效。勉强应用这种模式会给下游团队带来麻烦。大公司可能会发生这种情况,其中两个团队在管理层次中相隔很远,或者两个团队的共同主管不关心它们之间的关系。如果真的是这样,这个公司也有点扯皮了。
解决方式:
-
第一种方式:一种是完全放弃对上游的使用。做出这种选择时,应进行切实地评估,绝不要假定上游会满足下游的需求。
-
第二种方式:如果上游设计的质量不是很差,而且风格也能兼容的话,那么最好不要再开发一个独立的模型。这种情况下可以使用CONFORMIST(跟随者)模式。
第二种方式带来解决的成本问题
1)通过严格遵从上游团队的模型,可以消除在BOUNDED CONTEXT之间进行转换的复杂性。尽管这会限制下游设计人员的风格,而且可能不会得到理想的应用程序模型,但选择CONFORMITY模式可以极大地简化集成。
2)这个决策会加深你对上游团队的依赖,同时你的应用也受限于上游模型的功能,充其量也只能做一些简单的增强而已。人们在主观上不愿意这样做,因此有时本应该这样选择时,却没有这样选择。
ANTICORRUPTION LAYER(防腐层)
创建一个隔离层,以便根据客户自己的领域模型来为客户提供相关功能。这个层通过另一个系统现有接口与其进行对话,而只需对那个系统作出很少的修改,甚至无需修改。在内部,这个层在两个模型之间进行必要的双向转换。ANTICORRUPTION LAYER的公共接口通常以一组SERVICE的形式出现,但偶尔也会采用ENTITY的形式。对ANTICORRUPTION LAYER设计进行组织的一种方法是把它实现为FACADE、ADAPTER(这两种模式来自[Gamma et al.1995])和转换器的组合,外加两个系统之间进行对话所需的通信和传输机制。
Separate Way (各行其道)
我们必须严格划定需求的范围。如果两组功能之间的关系并非必不可少,那么二者完全可以彼此独立,集成总是代价高昂,而有时获益却很小声明一个与其他上下文毫无关联的BOUNDED CONTEXT,使开发人员能够在这个小范围内找到简单、专用的解决方案。
Open Host Service (开放主机服务)
设计出一个足够干净的协议,使之能够被多个团队理解和使用,是一件十分困难的事情,因此只有当子系统的资源可以被描述为一组内聚的SERVICE并且必须进行很多集成的时候,才值得这样做。例如:xml协议,dubbo协议,rest协议等等。
总结
本周非常抽象,不仅仅是在说DDD,同时也提到了DDD按照界限上下文进行划分后,不同的团队维护不同的领域边界内的应用,团队之间如何友好协作。文章中提出,可以通过XP(极限编程)模式(有点像互联网公司的开发方式)去开发协作。另外,上下游团队之间要用客户/供应商的意识去合作配合,边界内要梳理出共享内核,不同边界之间的应用,可以通过adapter层去通过一定的协议(xml,dubbo,rest)和开发主机服务去适配不同的应用。最重要的一点是要有自动化测试套件。