DDD阅读笔记(三)模型一致性

435 阅读9分钟

最近阅读了info 的《DDD(领域驱动设计)-精简版》,整理了一些笔记用于后期回顾(不定期补充)。


一、完整性与一致性

在实施DDD时,和业务人员沟通时使用的专有名词会被梳理为领域模型,场景一致、高相关性的领域模型会形成聚合。而为了保证业务的完整性,也需要统一聚合内不同领域模型的生命周期,可以通过仓库实现聚合对象的一致性

因此,完整性是业务的要求,一致性是实现完整性的方式。

二、界定的上下文

每个模型都应该有一个清晰的边界,模型之间的关系也应该被精确地定义,这个边界被称之为上下文。在我们处理一个独立的模型时,上下文是固定的。

意识到边界

在协同开发同一个模型的时候,必须意识到模型的边界,意识到对模型的调整就是对现有功能的破坏。而使用多个模型的时候,只调整自己负责的模型,而不调整其它模型。开发人员需要确保模型的纯洁、一致和完整。每个模型应能使重构尽可能容易,而不会影响到其他的模型。

模型不交互

有多个模型时总是会付出些代价。我们需要定义不同模型间的边界和关系。我们不能在不同模型间传递任何对象,也不能在没有边界的情况下自由地操作不同模型。开发时明确模型的独立性,只需要保证它们之间的接口正常运作即可。

三、持续集成

模型不是一开始就被完全定义,总会有新的概念被提取出来,然后被集成到统一的模型中,上下文也会被调整。因此,在开发过程中会持续的改进和调整,逐渐形成一个可以表示业务的模型。

这种开发方式,对于团队每个人员的要求都很高,在落地时如果不进行合理的分包,那么最终只会导致整个系统的腐烂。

开发时,需要这样一个集成的过程,以确保所有新增的部分和模型原有的部分配合得很好,在代码中也被正确地实现。我们需要有个过程来合并代码。合并得越早越好。对小的独立团队,推荐每日合并。我们还需要适当地采用构建流程。合并的代码需要自动地被构建,以被测试。另外一个必须的邀请是执行自动测试。如果团队有测试工具,并创建了测试集,那么测试就可以运行在每个构建上,任何错误都可以被检查出来。而这时也可以较容易地修改代码以修正报告的错误,因为它们被发现的很早,合并、构建、和测试流程才刚开始。

持续集成是基于模型中概念的集成,然后再通过测试实现。任何不完整的模型在实现过程中都会被检测出来。持续集成应用于界定的上下文,不会被用来处理相邻上下文之间的关系。

四、上下文映射

上下文映射(Context Map)是指抽象出不同界定上下文和它们之间关系的文档,它可以是一个示意图(Diagram),也可以是其他任何类型的文档。这个文档的作用是让系统的开发者能够模型的上下文。如果上下文之间的关系没有被抽象出来,在系统被集成的时候它们就有可能不能工作。

每个人也应该知道每个上下文的界限以及在上下文和代码之间的映射等。一个通常的做法是先定义上下文,然后为每个上下文创建模型,再用一个约定的名称指明每个模型所属的上下文。

创建上下文映射的模式(整合子系统):

  • 共享内核(Shared Kernel)
  • 客户-供应商(Customer-Supplier)
  • 防崩溃层(Anticorruption Layer)
  • 隔离通道(Separate Way):让上下文高度独立和分开运行
  • 开放主机服务(Open Host Service)

共享内核

协同开发时,需要指派两个团队同意共享的领域模型子集。当然除了模型的子集部分,还要包括代码自己或者和模型相关联的数据库设计子集。这个明确被共享的东西有特别的状态,没有团队之间的沟通不能做修改。

共享内核的目的是减少重复,但是仍保持两个独立的上下文。对于共享内核的开发需要多加小心。两个开发团队都有可能修改内核代码,还要必须整合所做的修改。如果团队用的是内核代码的副本,那么要尽可能早地融合(Merge)代码,至少每周一次。还应该使用测试工具,这样每一个针对内核的修改都能快速地被测试。内核的任何改变都应该通知另一个团队,团队之间密切沟通,使大家都能了解最新的功能。

客户-供应商

当一个子系统严重依赖另一个时,子系统的输出作为另外一个子系统的输入,不应该有共享的内核。例如,报表系统与商店系统,报表系统一般是桌面软件,商店系统可能是web软件,不应该共享内核。而报表系统需要分析商店系统的数据,因此报表系统应该作为商店系统的供应商,商店系统需要提供报表所需的接口,为下游负责

两个子系统之间的接口需要预先明确定义。另外还要创建一个统一的测试集,在任何接口需求被提出的时候用于测试。供应商团队可以在他们的设计上大胆地工作,因为接口测试集的保护网会在任何有问题的时候报警。

联合开发可以验证期望(Expected)接口的自动化验收测试。将这些测试增加到供应商团队的测试集里,作为它的持续集成的一部分运行。这个测试能使供应商团队放心地做修改,而不用担心影响客户团队的应用。

当两个开发团队有客户-供应商关系,而且供应商团队没有动力为客户团队的需要提供帮助时,客户团队是无助的。客户团队没有多少选择。最常见的现象是将它从供应商那儿分割模型

客户端需要实现连接两边上下文的转换层。也有可能供应商团队的模型没有被很好地理解,效用发挥不出来。虽然客户上下文仍然可以使用它,但它应该通过使用一个我们后面要讨论的“防崩溃层”来保护自己。

用开发者的语言描述,那就是应该配置一个适配器,让客户去适应供应商

防崩溃层

开发时,经常会遇到所创建的新应用需要和遗留软件或者其他独立应用相交互的情况。对于DDD架构而言,这又是一个挑战。在领域模型和遗留模型之间就须要有一个集成层,这也是使用旧应用的需求之一。与外部系统的交互方式如下:

  • 通过网络连接
  • 通过共享数据库(需要充分理解旧模型的数据语义)

在领域模型与外部模型建立防崩溃层,避免污染领域模型。通过防崩溃层,抽象其它系统,此类服务作为其它系统的外观(外观模式),配置适配器可以将外部系统的接口转换成客户端能理解的语言。

独立方法

在协同开发时,有时候需要建立转换器或者为协作修改自己的领域模型,而采取哪种方式要在严格评估后决定。如果整合难度过大,需要考虑独立方法。独立方法模式适合一个企业应用可由几个较小的应用组成,而且从建模的角度来看彼此之间有很少或者没有相同之处的情况。这些应用服务在使用时作为一个应用使用,但架构上却是分散的,因为模型独立且关联不大。在开发时,可以创建独立的界定上下文(Bounded Context),并独立建模。

这种架构也可以称之为微服务架构,这样做的好处是有选择实现技术的自由。

开放主机服务

如果外部子系统不是被一个客户端子系统使用,而是被多个服务端子系统的话,我们就需要为所有的服务端子系统创建转换器。当一个子系统要和其他许多子系统集成时,为每一个子系统定制一个转换器会使整个团队陷入困难。

解决这一个问题的方法是,将外部子系统看作服务提供者。可以定义一个能以服务的形式访问你子系统的协议。开放它,使得所有需要和你集成的人都能获取到。然后优化和扩展这个协议,使其可以处理新的集成需求,但某团队有特殊需求时除外。对于特殊的需求,使用一个一次性的转换器增加协议,从而使得共享的协议保持简洁和精干。

精炼

在提炼和创建很多抽象之后,一个大的领域还是会有一个大的模型。就是在重构多次之后,也依然会很大。对于这样的情况,就需要精炼了。思路是定义一个代表领域本质的核心域(Core Domain)。精炼过程的副产品将是组合领域中其他部分的普通子域(Generic Subdomain)。

如何理解系统意味着核心域如何构成,一个系统的核心域有可能会变成另一个系统的普通子域。正确标识核心,以及它和其他模型之间的关系是非常重要的 找到核心域,发现一个能轻松地从支持模型和代码中区分核心域的方法。强调最有价值和特殊的概念,使核心变小,而让最好的开发人员去承担构建核心域的重要任务。

五、参考文档

DDD-精简版