在过去的几周里,我们研究了领域驱动设计领域中的一些重要主题。
首先,我们看看什么是领域模型,以及它们为什么对领域驱动设计如此重要。领域模型是围绕业务的特定问题的重点知识。
接下来,我们研究了有界的上下文,以及它们如何适应整个组织的上下文映射。有界上下文是特定域模型周围的边界,而上下文映射是每个有界上下文如何适应全局的全局视图。
绝大多数领域驱动的设计应用程序都有多个有界的上下文。这可能意味着与第三方服务集成,但通常情况下,您还需要与现有的遗留应用程序或同一应用程序的其他模型集成。
有许多技术方法可以集成有界上下文、第三方服务和遗留应用程序。
然而,选择正确的集成模式非常重要,因为它将对应用程序的设计和整个项目的未来产生重大影响。
在今天的文章中,我们将讨论在域驱动设计应用程序中集成有界上下文的策略,每种策略的优缺点以及如何决定为项目选择哪种上下文。
重述域模型和有界上下文
在开始讨论集成策略之前,我们先快速回顾一下域模型和边界上下文。
领域模型是围绕特定问题的重点知识。这包括实体、值对象和特定于特定问题的重要事物之间的关系。
有界上下文是关于域模型的边界。在有限的上下文中,对象的语言、名称和思想应该形成手边问题的统一模型。有界的上下文将内部模型与外部世界的复杂性隔离开来。每个有界的上下文应该有一个内部模型,团队的所有成员都能清楚地理解这个模型。这很重要,因为在大多数组织中,某些术语在不同的部门或业务领域有不同的含义。
最后,上下文映射是每个有界上下文如何在应用程序或组织中相互配合的全局视图。这个更大的问题视图确保应用程序的目标不会丢失在每个有界上下文的重点细节中。对于每个有界的上下文,最好有一个真正的内部模型和一层转换,而不是使用单一的对象来试图填补不同的、常常相互冲突的工作。
软件项目的现实
在理想的情况下,每个软件项目都可以从头开始,有一个干净的git存储库,没有遗留问题。
然而,在现实世界中,这种类型的项目非常少见。
绝大多数重要的应用程序开发项目都需要多个有界上下文。
在与现有组织合作时,通常需要与遗留应用程序或第三方服务集成。
因此,您的工作就是将这些遗留应用程序和第三方系统与您正在进行的新项目集成起来。
这些类型的集成的问题是,您可以发现自己处于无限多的情况中。例如,您可能会发现自己受制于第三方服务,或者您可能负责为现有遗留系统提供接口。
我将在本文中讨论许多不同的集成策略。每种策略都有其优点和缺点。
为特定的情况选择正确的集成策略非常重要,因为它将对应用程序的设计和未来的开发路径产生重大影响。
一个简单的翻译例子
在我们开始讨论集成策略之前,首先我将解释一个简单的示例,以便我们都能理解为什么这很重要。
想象一下,我们以顾问的身份进入一家现有的线下零售商,为该公司创建一个新的在线电子商务网站。
这家公司已经在商业大街上经营了很多年,所以它已经有了现有的股票管理、分销和金融系统。
我们的任务是创建一个电子商务网站,可以接口与公司现有的系统,提供一个无缝的销售新渠道。
我们的新开发项目显然是一个新的有界的环境,在组织作为一个整体的环境地图的范围内。我们不会扩展当前的任何系统,但是我们必须使用并返回来自每个现有系统的数据,以便使新通道无缝地集成到当前的体系结构中。
我们将需要能够要求从库存管理系统的数据,以便知道什么应该提供网上购买。
我们还需要将数据发送到配送和财务系统,以便正确处理订单和处理公司的会计责任。
如果这是一个真实的情况,我们可能不得不与大量其他现有系统和第三方服务进行交互。但是,希望您能够通过一层转换看到与现有系统集成的需求。
而在一个理论的想法世界,如果电子商务销售、库存管理、分销和财务都是一个统一的模型的一部分,那就太好了。然而,这样一个系统的复杂性将很快失去控制。
相反,我们需要找到通过翻译层进行集成的方法。以下是7种方法。
共享内核
第一个集成策略是使用一个共享内核,其中域模型的一部分在处理相同应用程序的不同团队之间共享。
共享内核集成策略是有益的,因为它减少了重复代码的数量和翻译层的开销。
但是,只有当所有的开发团队都致力于共享和交流共享代码的需求时,共享的内核才能工作。这意味着共享内核的设计不能像应用程序的其他方面那样快速发展,任何更改都必须得到每个开发团队成员的同意。
每个开发团队还必须承担维护单元测试的同等责任,以确保共享内核的功能保持一致。
当应用程序的某个方面存在一个共享需求,并且通信水平较高,政治动荡程度较低时,共享内核集成策略比我们将在本文中看到的许多其他集成策略更容易实现。
在我们的电子商务类比中使用共享内核的一个例子可能是,客户模型在交易团队和在线营销团队之间共享。
交易团队需要客户模型来关联交易和请求支付,而在线营销团队需要客户模型来发送营销信息来刺激新购买,并让客户了解新产品和新报价。
与两个团队编写自己的客户模型并来回转换不同,他们可以使用一个共同的客户模型来满足他们的需求。这意味着团队需要就客户模型的规范达成一致,并且任何未来的变更都必须由两个团队签署。
客户/供应商
两个软件应用程序之间的常见关系是,下游应用程序需要来自上游应用程序的数据,但上游应用程序不依赖于下游应用程序。
这种关系可以通过许多不同的方式表现出来。
首先,如果上游团队不考虑下游团队的需求而进行更改,那么下游团队就会受到上游团队的支配。
另外,如果下游团队能够控制他们的应用程序的发展,那么上游团队就会感到他们的应用程序的设计和实现受到了限制。
下游团队依赖于上游团队,但上游团队不负责下游团队的交付。为了使这种情况起作用,两个团队需要有一个正式的关系,其中考虑了下游团队的需求,就像在任何客户/供应商关系中一样。
这意味着未来的发展优先事项、任务和要求必须得到两个工作队成员的同意。
两个团队还应该就一系列验收测试达成一致,以确保边界的接口保持一致。这意味着只要接口保持一致,上游团队就可以发展他们的应用程序,而下游团队可以确信,他们不会因为上游团队的更改而某天醒来发现他们的应用程序被破坏。
当两个团队的目标是一致的,或者他们都向相同的管理层报告时,客户/供应商关系工作得最好。当两个团队的目标不一致时,关系往往会破裂。
电子商务类比中的客户/供应商关系的一个例子是,一个单独的分析应用程序必须从电子商务应用程序获取数据,以生成客户建议并预测新的趋势。
分析应用程序位于不同的有界上下文中,因为它可能使用不同的编程语言编写,使用与电子商务应用程序非常不同的工具和持久存储。
分析应用程序依赖于电子商务应用程序来发送事务、客户概要和跟踪事件的数据,以便运行分析。
两队应就数据的类型、格式和方法以及如何向下游传送数据达成协议。如果两个团队在提高公司整体的成功和盈利能力上保持一致,这种关系就更有可能奏效。
墨守成规
当客户和供应商之间的关系不一致时,下游团队可能最终处于一种受上游团队支配的情况。
这可能发生在同一家公司,其中每个团队向具有不同目标的非常不同的管理层报告,或者供应商有许多小客户,因此每个单独的客户对供应商来说不是特别重要。
这意味着上游团队没有动力为下游团队提供任何类型的优先级甚至一致性。下游团队必须接受这样一个事实,即他们不能依赖上游团队和关系边界上一致的接口。
如果上游应用程序的价值对下游应用程序至关重要,那么继续下去的唯一方法就是坚持上游团队的想法。
这将意味着上游团队将完全控制这两个应用程序的集成,因此下游团队只能让它工作。
这将导致下游团队对上游团队有很深的依赖性,因此任何未来的开发都将受到形势的限制。
在我们的电子商务类比中,因循守旧关系的一个例子可能是,我们依赖第三方送货服务向客户发送包裹。为了更新客户的进度位置,我们需要从第三方交付服务请求更新。
然而,我们只是数以千计的快递服务客户中的一员,所以他们没有动力提供我们需要的数据。交付服务也可以在任何时候自由地更改它们的API,这可能会破坏我们的集成。
在这种情况下,我们完全处于交付服务的支配之下,因此我们必须负责接受他们的数据和他们提供的请求数据的接口。如果接口发生了变化,我们有责任遵循这些变化并演进我们的集成以满足那些需求。
反腐败层
在使用现有的遗留应用程序或第三方服务时,集成需求通常很复杂。虽然一开始似乎很容易避免集成,但是如果其他系统非常重要,那么集成中可能有更多的价值。
集成其他系统是困难的,因为其他模型可能通过集成而泄漏,并开始影响新系统自己的模型。过多地适应现有的系统,我们最终会得到一个新的系统,它的模型不一致或者不适合解决它自己的问题。
为了防止模型从外部系统内部泄漏到新系统,我们需要一种方法来在两个模型之间转换数据。这通常意味着确保在新模型中解释数据的上下文是一致的,但也要防止在集成过程中出现不必要的数据泄漏。
为了实现这一点,我们需要创建一个隔离层,它可以与遗留系统和第三方系统的现有接口进行通信,然后在内部模型之间来回转换请求。
在我们的电子商务类比中使用反腐败层的一个例子是,将现有的线下零售系统的客户忠诚度计划与新的在线零售系统的客户忠诚度计划联系起来。
如果现有的线下客户与零售商之间存在忠诚度平衡,并且她开始在网上购物,那么这两个系统应该相互了解,这样客户就不会失去优惠或折扣。
然而,这两个系统可能会有不同的模型和不同的支持数据,而这些模型和数据在这两个系统之间是不相关的。
为了保持两个系统的一致性,我们可以设置Web挂钩(什么是Web挂钩?),每当客户忠诚度平衡在关系的一端更新时,它就会发送请求。
当请求被发送或接收时,它将通过反腐败层被转换为接收应用程序的适当形式。这意味着数据可以在两个系统之间流动,而不必更改现有的脱机系统,也不必使新系统符合现有系统的模型。
开放主机
当您的应用程序需要与另一个系统集成时,您通常会提供一个转换层来简化集成。
然而,当您的应用程序需要与许多其他现有系统集成时,拥有所有这些翻译层可能会变得难以处理。
不是为每个集成提供一个独立的翻译层,而是提供一组可由任何其他有界上下文使用的服务。
通过将应用程序作为一系列服务提供,可以减少维护多层翻译所需的开销。
随着新功能在内部创建或从外部使用者请求,这些服务可能会发展。然而,对于一次性集成需求,单层转换要比牺牲通用服务接口更好。
在我们的电子商务应用程序中使用开放主机策略的一个例子可能是将客户和交易的数据作为一系列服务提供。
公司内部的许多现有应用程序可能需要从我们的应用程序请求数据来完成订单或补充产品库存,因此,我们可以提供一组服务来减少这种开销,而不是为每个系统提供翻译层。
发布的语言
将来自外部系统的模型集成到新系统中是困难的,因为您不希望您的新系统受到外部系统设计的影响。
外部系统的模型可能与您的内部模型不兼容,因此接受它们的模型作为数据交换语言意味着您的模型依赖于外部系统并受到外部系统的影响。
我们不应该依赖模型作为数据交换语言,而应该使用通用的发布语言,如JSON或XML,这些语言允许使用通用格式在不同的系统之间转换数据。
在我们的电子商务类比中,使用已发布语言的一个例子可能是每当发生新事务时更新消息传递应用程序。消息传递应用程序将向客户发送电子邮件,通知他们产品更新、折扣和相关产品。
我们可以通过将请求的详细信息发送为JSON或XML来确保数据与消息传递应用程序兼容。通过将数据作为公共格式发送,我们并没有强迫消息传递系统遵从我们的内部模型,或者在如何设计内部应用程序方面做出妥协。
分道扬镳
在许多情况下,集成的成本可能比它的价值要高。这可能是由于墨守成规的情况,或者可能是其他团队太难以合作。
如果两个系统之间的功能或数据没有内在的联系,仅仅因为它是相关的,并不意味着它应该被集成。
另一个有吸引力的选择是允许两个系统以各自的方式运行,而不是试图强制进行集成。
在我们的电子商务类比中,另一种方法的例子可能是向零售商现有的财务部门报告财务统计数据。财务部门使用一个古老的系统,需要很长时间才能与我们新的电子商务应用程序集成。
公司的财务负债需要每周、每月、每季、每年报告一次。对我们的需求来说,花费时间在两个系统之间构建实时报告的集成是多余的。
相反,我们可以简单地确保财务报告能够以所需的格式导出。这意味着两个系统根本不需要集成,因此我们失去了使集成工作正常进行的开销。
结论
在任何重要的应用程序中,几乎不可避免地需要与现有的应用程序、第三方服务或多个有界上下文集成。
世界上许多不同类型的公司都可以通过在组织内集成新的和现有的系统来获得巨大的生产力收益。
当您被要求集成两个非常不同的系统时,理解围绕集成的通用模式将是一项巨大的资产。
在开发全新的应用程序时,集成也是需要记住的重要内容。通过将您的应用程序作为一系列的服务来提供,您将使将来与您的应用程序的集成需求变得更加容易!
当分布式系统可以作为一个整体进行集成和利用时,软件的力量就会被放大。了解如何在不同的环境下集成应用程序是非常有价值的知识。