播种微服务数据库

eit*_*hed 11 event-sourcing microservices

给定控制模型(产品,让我们假设它具有的唯一字段是 id、标题、价格)的服务 A(CMS)以及必须显示给定模型的服务 B(运输)和 C(电子邮件)应该采用什么方法在事件溯源方法中跨这些服务同步给定的模型信息?假设产品目录很少更改(但确实会更改),并且有管理员可以非常频繁地访问货件和电子邮件的数据(示例功能是: B:display titles of products the order contained和 C: display content of email about shipping that is going to be sent)。每个服务都有自己的数据库。

解决方案1

在事件中发送有关产品的所有必需信息 - 这意味着以下结构order_placed

{
    order_id: [guid],
    product: {
        id: [guid],
        title: 'Foo',
        price: 1000
    }
}
Run Code Online (Sandbox Code Playgroud)

在服务 B 和 C 上,产品信息存储在表的productJSON 属性orders

因此,为了显示必要的信息,仅使用从事件中检索到的数据

问题:根据需要在 B 和 C 中呈现的其他信息,事件中的数据量可能会增加。B 和 C 可能不需要关于产品的相同信息,但事件必须包含两者(除非我们将事件分成两个)。如果给定的事件中不存在给定的数据,则代码无法使用它 - 如果我们为给定的产品添加颜色选项,对于 B 和 C 中的现有订单,除非我们更新事件然后重新运行它们,否则给定的产品将是无色的.

解决方案2

在事件中仅发送产品的 guid - 这意味着以下结构order_placed

{
    order_id: [guid],
    product_id: [guid]
}
Run Code Online (Sandbox Code Playgroud)

在服务 B 和 C 上,产品信息存储在表的product_id属性orders

服务 B 和 C 在需要时通过对A/product/[guid]端点执行 API 调用来检索产品信息

问题:这使得 B 和 C 依赖于 A(在任何时候)。如果 A 上的 Product 模式发生变化,则必须对依赖于它们的所有服务进行更改(突然)

解决方案3

在事件中仅发送产品的 guid - 这意味着 order_placed 的以下结构:

{
    order_id: [guid],
    product_id: [guid]
}
Run Code Online (Sandbox Code Playgroud)

在服务 B 和 C 上,产品信息存储在products表中;仍然product_idorders桌子上,但products在 A、B 和 C 之间有数据复制;B 和 C 可能包含与 A 不同的产品信息

在创建服务 B 和 C 时植入产品信息,并在通过调用A/product端点(显示所有产品所需的信息)或通过执行对 A 的直接数据库访问并复制给定所需的产品信息而更改有关产品的信息时进行更新服务。

问题:这使得 B 和 C 依赖于 A(播种时)。如果 A 上的 Product 架构发生变化,则必须对依赖于它们的所有服务进行更改(播种时)


根据我的理解,正确的方法是使用解决方案 1,或者根据特定逻辑更新事件历史记录(如果产品目录没有改变并且我们想要添加要显示的颜色,我们可以安全地更新历史记录以获取当前状态的产品并在事件中填充缺失的数据)或满足给定数据的不存在(如果产品目录已更改并且我们想要添加要显示的颜色,我们无法确定在过去给定产品的那个时间点是否有颜色 - 我们可以假设之前目录中的所有产品都是黑色的,并通过更新事件或代码来满足)

Voi*_*son 5

解决方案#3 非常接近正确的想法。

思考这个问题的一种方式是:B 和 C 各自缓存它们所需数据的“本地”副本。在 B(以及 C 处同样)处理的消息使用本地缓存的信息。同样,报告是使用本地缓存的信息生成的。

数据通过稳定的 API 从源复制到缓存。B 和 C 甚至不需要使用相同的 API - 他们使用适合其需求的任何获取协议。实际上,我们定义了一个契约——协议和消息模式——它约束提供者和消费者。然后该合同的任何消费者都可以连接到任何供应商。向后不兼容的更改需要新的合同。

服务根据其需求选择适当的缓存失效策略。这可能意味着定期从源中提取更改,或者响应事物可能已更改的通知,甚至“按需”——充当读取缓存,在以下情况下回退到存储的数据副本:来源不可用。

这为您提供了“自主权”,即当 A 暂时不可用时,B 和 C 可以继续提供业务价值。

推荐阅读:《外部数据》、《内部数据》,Pat Helland 2005。


SKl*_*ous 2

一般来说,我强烈建议不要选择选项 2,因为这两个服务之间存在时间耦合(除非这些服务之间的通信非常稳定,并且不是很频繁)。时间耦合就是您所描述的this makes B and C dependant upon A (at all times),意味着如果 A 宕机或无法从 B 或 C 访问,B 和 C 就无法履行其功能。

我个人认为选项1和选项3都存在有效选项的情况。

如果 A 和 B & C 之间的通信量如此之大,或者进入事件所需的数据量足够大以至于需要关注,那么选项 3 是最好的选择,因为网络的负担要低得多,并且操作的延迟将随着消息大小的减小而减小。这里需要考虑的其他问题是:

  1. 契约的稳定性:如果离开A的消息的契约经常改变,那么在消息中放入大量的属性会导致消费者发生大量的变化。然而,在这种情况下,我认为这不是一个大问题,因为:
    1. 您提到系统 A 是 CMS。这意味着您正在稳定的域上工作,因此我不相信您会看到频繁的更改
    2. 由于 B 和 C 正在运送和发送电子邮件,并且您正在从 A 接收数据,因此我相信您将经历附加更改而不是破坏性更改,只要您发现它们,就可以安全地添加它们,而无需返工。
  2. 耦合:这里几乎没有耦合。首先,由于通信是通过消息进行的,因此除了数据播种期间的短暂服务之外,服务之间不存在耦合,并且该操作的契约也存在(这不是您可以或应该尝试避免的耦合)

不过,我不会拒绝选项 1。存在相同数量的耦合,但从开发角度来说,它应该很容易做到(不需要特殊操作),并且域的稳定性应该意味着这些不会经常改变(正如我已经提到的)。

我建议的另一个选项是与 3 稍有不同,即不在启动期间运行该流程,而是在产品目录发生更改时观察 B 和 C 上的“ProductAdded”和“ProductDetailsChanged”事件这将使您的部署更快(如果发现任何问题/错误,更容易修复)。


编辑2020-03-03

在确定集成方法时,我有一个特定的优先顺序:

  1. 一致性的成本是多少?我们可以接受 A 中更改的内容与 B 和 C 中反映的内容之间存在几毫秒的不一致吗?
  2. 您是否需要时间点查询(也称为时间查询)?
  3. 数据有真实来源吗?拥有它们并被视为上游的服务?
  4. 如果有一个所有者/单一事实来源,那么稳定吗?或者我们是否期望看到频繁的重大变化?

如果不一致的成本很高(基本上A中的产品数据需要尽快与B和C中缓存的产品保持一致),那么youb不可避免地需要接受不可用,并发出同步请求(就像Web /rest请求)从B和C到A获取数据。意识到!这仍然并不意味着事务一致,而只是最小化不一致的窗口。如果您绝对必须立即保持一致,则需要重新调整您的服务边界。然而,我坚信这不应该是一个问题。根据经验,公司不能接受几秒钟的不一致的情况实际上是极其罕见的,因此您甚至不需要发出同步请求。

如果您确实需要时间点查询(我在您的问题中没有注意到,因此没有包含在上面,可能是错误的),那么在下游服务上维护此查询的成本非常高(您需要复制所有下游服务中的内部事件投影逻辑),使决策变得清晰:您应该将所有权留给 A,并通过 Web 请求(或类似)临时查询 A,并且 A 应该使用事件源来检索您知道的所有事件当时投影到状态,然后返回。我想这可能是选项 2(如果我理解正确的话?),但成本是这样的,虽然时间耦合优于重复事件和投影逻辑的维护成本。

如果您不需要时间点,并且没有明确的单一数据所有者(在我最初的回答中,我确实根据您的问题假设了这一点),那么一个非常合理的模式是保存表示每项服务中的产品分别。当您更新产品数据时,您可以通过向 A、B 和 C 中的每一个发出并行 Web 请求来并行更新 A、B 和 C,或者您有一个命令 API,可以向 A、B 和 C 中的每一个发送多个命令。B 和 C 使用它们的数据的本地版本来完成它们的工作,这些数据可能是陈旧的,也可能不是陈旧的。这不是上述任何选项(尽管可以使其接近选项 3),因为 A、B 和 C 中的数据可能不同,并且产品的“整体”可能是所有三个数据的组合来源。

了解事实来源是否具有稳定的契约非常有用,因为您可以使用它来使用域/内部事件(或作为 A 中的存储模式存储在事件源中的事件)来跨 A 和服务 B 和 C 进行集成。如果合约稳定,您可以通过领域事件进行集成。但是,如果更改频繁,或者消息契约足够大,导致传输成为问题,那么您还有一个额外的担忧。

如果您有明确的所有者,并且合同预计稳定,那么最好的选择是选项 1;订单将包含所有必要的信息,然后 B 和 C 将使用事件中的数据来执行其功能。

如果合同很容易发生变化或经常中断,那么按照选项 3,回退到 Web 请求来获取产品数据实际上是一个更好的选择,因为维护多个版本要容易得多。因此 B 会向产品 v3 发出请求。