领域驱动设计-读书笔记-第十四章-保持模型的完整性

通过一个案例引入

案例:客户发票系统共用了一个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 过程。

持续继承过程具备的特征:

  1. 分步集成,采用可重现的合并/构建技术。

  2. 自动测试套件;

  3. 有一些规则,用来为那些尚未集成的改动设臵一个相当小的生命期上限。

  4. 在讨论模型和应用程序时要坚持使用UBIQUITOUS LANGUAGE。

  5. 多数敏捷项目至少每天会把每位开发人员所做的修改合并进来。

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)和开发主机服务去适配不同的应用。最重要的一点是要有自动化测试套件。