微服务关系/依赖策略

3

我希望您能提供一些关于如何处理微服务间数据依赖和关系的不同解决方案的反馈。

考虑以下这些服务: enter image description here

简要说明:有一个银行服务和一个账户服务。账户服务保存账户并始终使用bankId与银行连接。

困境在于如何处理和验证此关系和bankId,以及每个决策所带来的利弊。

选项1:

完全忽略验证。对账户进行POST / PATCH操作将永远不会验证给定的BankId是否存在。

优点

  • 服务互相独立,它们之间没有硬性依赖关系,如果一个服务停止工作,另一个服务不会停止工作。(这是一个重大的优点)

缺点

  • 如果BankId不正确,则无法访问账户。
  • 报告服务和/或任何读取器必须考虑缺失或不正确的银行,并呈现其具有的任何数据而不崩溃。

反思

服务完全解耦,这将有利于性能、正常运行时间和复杂度。所有读取器和应用程序都需要“响应式”,并能够处理交叉服务关系“断裂”的情况。

选项2:

始终使用同步REST调用进行验证。如果BankId不存在或BankService无法响应或已损坏,则对账户进行POST / PATCH操作将失败。

优点

  • 100%数据完整性。
  • 读取器不需要处理和期望破碎的关系。

缺点

  • 服务之间的依赖性过于紧密,您可以认为它们不再是真正的微服务,而是单一服务。
  • 性能受到负面影响。
  • 如果BankService关闭,AccountService的POST / PATCH将无法工作,但GET仍将工作。

反思

服务之间的依赖性过于紧密,这非常糟糕,这更像“旧方法”,通常我觉得这是错误的做法。在这种情况下合并服务甚至更糟,如果您开始通过合并解决问题,您可能会继续这样做,并很快以大型服务结束,从而未能实现整个微服务原则。当然,读取仍将工作,但这是一个牵强的借口。

选项3:

在AccountService中保留BankEntity的只读副本。 AccountService通过事件总线更新此副本。对POST / PATCH进行验证。

优点

  • 100%数据完整性。
  • 读取器不需要处理和期望破碎的关系。
  • 没有可测量的负面影响。

缺点

  • 复杂性增加了
  • 由于事件的异步性质,我们不能假设银行只读副本已经100%更新。在创建BankEntity后快速连续POST/PATCH Account,可能会失败。
  • 尽管是一个松散的依赖关系,但AccountService对其他服务的了解更多了

反思

这是最复杂的方式,读者不需要处理破裂的关系和性能/可用性问题得到解决,但是,相反你必须处理只读的Banks副本可能尚未更新并稍后再试的事实。与Option 1相比,这意味着你仍然必须以某种方式处理它,而且由于这将在各方面更加复杂,我认为这不是最有利的方法。

最终想法

总的目标是使服务之间不同步地交互,并尽可能保持数据完整性。

然而,在微服务架构中,我认为关系完整性可能只是你接受失去的这些事情之一。

我们的决定倾向于Option 1,实际上就是忽略它,任何时候需要使用它时,都必须预期并处理它可能不正确的情况。这似乎是最“微服务”解决方案,服务彼此并不真正了解,只有需要执行跨服务操作的应用程序和报告服务知道彼此。

所有服务都需要完全负责,以确保它们在任何时候都具备完全运行所需的所有数据。例如,假设AccountEntity出于某种原因需要Location来成为可用和完整的域实体,则不能指望依赖BankId,而必须将Location存储在AccountEntity上,如果更改,您可以收到事件并更新它。

简短概括 你对此有什么经验、观点和想法?你会怎样做?哪种策略会选择?


只要您的银行ID一旦建立就稳定,我会选择选项4,其中账户包括银行ID。从账户服务的角度来看,这只是上下文,账户仍然不知道银行是什么,但是消费者可以利用上下文进行任何操作,并向银行服务咨询。您会发现许多类似的情况,例如使用用户ID时可能需要将其作为上下文包含在内,而不涉及具体细节。 - Oswin Noetzelmann
1
感谢您的意见,Oswin。这个想法是BankId将始终存在于账户上,并由AccountService的使用者提供它,只是AccountService本身不关心也不知道它是什么,但应用程序可能会,然后如果他们需要,他们可以选择查找它。 - Einarsson
答案取决于领域。例如,我不确定选项1(“大多数情况下只是希望一切顺利”)是否适用于银行账户建模。 - Oliver Charlesworth
2个回答

3
首先,您选择的选项将取决于您的业务需求。
1. 完全忽略:在银行和账户的情况下,我不建议使用此方法,因为这可能会导致丢失账户。您可以在业务流程能够确保最终解决问题的情况下采取此类方法。
2. 始终验证:我不会完全使用此方法,因为这会使服务依赖性过强。我会使用此方法的变体:
- 首先,允许创建未经验证的账户,账户的初始状态为“已创建”。 - 然后,在AccountService中创建一个ProcessManager,监听AccountCreated事件,并异步地使用Bank Service进行验证,以检查银行ID是否有效。如果有效,则将账户状态更新为“已验证”,如果无效,则将状态更新为“无效的银行ID”,并采取适当的措施。
3. 保留只读副本:我也不会这样做,因为首先需要复制大量数据,其次副本可能已过期。例如:银行已删除但未知于此服务,或者银行刚刚创建但未知于此服务,在这两种情况下都需要进行额外的检查和异步验证。
实际上,所有方法都是有效的,具体取决于您的关键业务需求。有时即使它会产生依赖关系,实时验证也是必需的。

基本上和我的想法一致。我们也考虑过通过内部任务/事件验证外部ID,但这会增加复杂性,我们正在考虑这一点。然而,银行/账户场景只是一个例子,在我们的情况下,丢失的账户实际上并不会有太大影响,因为该数据被用作创建其他数据时的建议,如果这有意义的话。无论如何,感谢您的意见。内部流程的添加是一种有效的方法,我将把它标记为已回答,因为对于许多读者来说,这可能是最好的选择。 - Einarsson
感谢 @Einarsson,如有需要,欢迎进行其他讨论。 - techagrammer

0

这个问题实际上是基于观点的,没有对或错的答案(实际上不是这样,有很多错误的答案,但也有很多正确的答案)。这真的取决于您的用例。个人而言,我倾向于松散耦合,直到我需要更强的耦合。

我最喜欢的模式是使用银行ID的URL而不是数字。这为您提供了指向银行的指针,而无需了解另一个服务的内部结构的具体信息。

如果以后您决定需要ID,则始终可以解析所有URL并收集ID,或者可以在运行时执行此操作。整个重点是,在您有消费此数据的用例之前,您不必知道这一点,不要将其耦合。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接