微服务和数据表外键困境 – Stephen


微服务的旅程集中在将单体应用分解为可组合的、领域驱动的服务。创建较小的可组合服务有许多有价值的方面。较小的服务更容易部署和扩展。使用较小的可组合服务的整体系统弹性更强,因为故障可以被隔离。它还支持整个企业更多的灵活性和可重用性。

然而,微服务架构也有缺点,使得它更像是一种权衡的方案。

其中一个缺点是将数据分割到多个数据存储中。
很多时候,我们所拆分的单体也是由巨大的单体关系数据库支持的。这些巨大的模式可能包含数百个关系和大量的数据库特定的实施细节,如复杂的存储过程、视图或索引工作。
分解单体数据库的工作有时可能是这一过程中最复杂的方面。

外键的乐趣
关系型数据库中的外键是一个非常强大的工具。外键是一个用来唯一地识别另一个表中的行的字段。它被称为 "外键",因为它是一个与另一个表中的主键有关的字段。外键被用来建立和执行数据库中表之间的关系。然而,外键的力量在于它能够确保数据的清洁(完整性)。我们可以在用户界面上用外键表项建立选择字段下拉,以确保他们只能输入我们想要的数据。我们还将外键作为验证数据的最后手段,以确保插入主表的东西不是意外的东西。

在能源交易领域,有许多不同的外键关系的例子。最适合讨论的是交易trade与对手的关系。交易是指现在或未来某个时候购买或出售某种商品(或基于商品价格获得财务结算)的协议。在执行交易时,有一些法律实体与现在的合同义务相关,在特定的时间和地点以特定的价格提供商品或接受该商品的交付。这些法律实体被称为交易方。


上图是交易方与交易对手关系的 ERD 示例

当一个新的交易被执行时,这些交易方已经需要与公司建立联系。一家贸易公司不能只是开始与其他一些公司进行交易。该公司将需要做一个KYC(了解你的客户)程序,并建立管理关系的基础合同和建立信用限额,以及其他事项。这意味着当一个新的交易被创建,并且该交易引用了一个对手方时,企业要求该交易被验证为只与已建立的、与该公司有良好信誉的对手方执行。

在交易系统中,这意味着当有人试图在系统中创建一个新的交易时,交易采集系统会执行这一验证。在一个单一的ETRM中,这个过程可能会做一个对手方查询,检查对手方的状态,然后保存交易--外键约束是检查之一,以确保交易表只参考已经在对手方表中的实体。

开始拆分
在这个例子中,领域非常完善:trade交易 和 counterparty交易方,它们各自有独特的特征。counterparty 交易方是一个法律实体数据结构,可能有命名信息、物理地址、逻辑地址、联系信息和关于公司的更多细节。在交易trade的上下文中,除了知道trade的某一方是与某个特定的对手方合作之外,这些信息几乎没有什么意义。

微服务的一些积极方面在拆分后迅速显现出来。由于新的交易工具和功能,以及修复错误,交易服务service在变化方面将更加不稳定。交易服务的能力范围也会更大,因为它可能需要支持更多的数据验证,而不是那些与交易方counterparty 相关的数据。交易方counterparty 服务现在也可以被其他多个系统使用,以管理与法律实体相关的、非交易的合同事件相关的数据,如物流管理系统。然而,这些好处不是没有代价的。

有什么复杂complicated的问题?
第一个权衡因素将与跨独立系统的数据维护有关。在单体中,整个trade交易可以在一个系统中从上到下进行验证。在一个独立的trade服务中,我们不希望所有这些能力都存在,因为它们不是手头领域的核心部分。我们将希望有一套精简的数据验证,但会有一些需要考虑的选择。

需要考虑的选项
这个贸易系统将有一个API来接受trade交易对象的持久化。作为这个过程的一部分,trade交易将需要被检查结构和成分的有效性。在counterparty 交易方的上下文中,我们要检查交易是否在正确的地方包含对正确的交易方的引用,而且我们要检查他们是否是我们已经在合同上同意交易的交易方。

虽然对结构有效性的检查可以卸载到数据格式的验证管理器中,但检查实际对手方的部分是我们开始必须做出选择的地方,并且会产生一些额外的复杂性。快速的答案可能是让交易服务从交易中获得对手方的识别信息,并检查对手方服务是否存在并具有良好信誉。

四种选择

  • 每次检查服务——在这种情况下,对于提交给交易服务的每笔交易,验证步骤都会获取交易对手标识符,并根据交易对手服务检查它们以进行验证。问题包括如果交易对手服务关闭,验证将失败。此外,如果交易服务的流量很大,那么交易和交易对手服务之间的 IO 可能会成为一个问题。更不用说交易对手服务需要能够管理验证过程产生的任何负载。
  • Local Cache Plus Changes——在这个选项中,当服务启动时,它会联系对方服务,获取它们并将相关信息存储在本地缓存或独立表中。提交交易后,该服务不必对交易对手数据进行远程检查,而是使用本地缓存。对此有一些权衡。首先,贸易服务现在必须创建和维护缓存机制。其次,在启动时加载缓存可能会抑制启动时间,并且根据实施情况,可能会由于启动时间而使服务不再能够“无服务器”。最后但同样重要的是,如果你有缓存,那么你必须处理计算机科学中两个最困难的事情中的第二个——缓存失效!
    • 通过轮询缓存更新——处理缓存陈旧的一种方法是轮询服务并定期更新本地数据。交易对手服务甚至可以提供一项豪华服务,为您提供自给定时间戳以来已更新的所有交易对手的列表。
    • 通过事件缓存更新——另一种方法是,如果交易对手服务正在发出事件,那么交易服务可以监听这些事件并更新缓存。无论如何,贸易服务可能会发出和消耗事件,因此此处的附加代码可能并不重要。
  • Local Cache Plus Changes Plus Read-Through——这是我最喜欢的选项,因为它将成为最强大的功能。在这种情况下,我们仍然在创建一个本地缓存,但是如果存在缓存未命中,验证过程将通过缓存读取到交易对手服务,并在返回时填充缓存。以这种方式,服务没有启动延迟,因为缓存加载是在后台线程中运行的,交易消费不需要加载缓存。更新也可以是最大努力,因为在任何缓存未命中时,通读将处理验证检查。
  • 不要检查——第四种选择会让任何在能源交易领域待过一段时间的人都有些不寒而栗。如果交易对手字段存在且格式正确,那么实际上是在讨论谁可能将交易发送到服务中。
    • 交易所——如果交易来自交易所,那么该数据流中的事件处理机制将*可能*替换正确的交易对手信息。
    • ETRM——如果 ETRM 将其捕获的交易发送到交易服务,那么它发送的交易对手信息*可能*已经正确,因为要将交易输入 ETRM,它需要交易对手。
    • 一次性交易捕获用户界面——许多贸易组织都具有一次性快速交易记录功能,其中一些交易员将 Web 应用程序组合在一起以捕获交易。如果这个系统已经在使用交易对手服务来获取它的数据,那么它又是*可能*有效的。
  • Insert Cache Miss——这种情况下的另一种选择是,如果数据格式正确且结构完整,交易服务是否有可能向交易对手服务提交新的交易对手?这将需要一个相当基本的界面,用于提交准系统的交易对手数据,以及向所有感兴趣的各方提供相当强大的服务,即新交易刚刚与不在我们系统中的交易对手发生。此警报应伴随风险部门的喇叭声。

这是组件及其交互的快速绘图:


结论
分解单体并不是一件容易的事。很多时候需要解决复杂性和权衡问题,尤其是在开始时,因为新组件只会带来额外的麻烦,而且几乎无法缓解单体应用带来的痛苦。
关于什么是微服务,也有一些想法可能需要在范围和其他特征方面进行调整。
在这种情况下,我们最终可能会在两个不同的数据库中得到重复的交易对手数据,这并不理想,但它也允许我们几乎每天都对我们的交易系统进行更改,因此权衡可能非常有价值。
坚持这个过程,不要害怕重复多编写一次性代码来实现目标,只要你的产品经理承诺他们会在下个季度给你时间来清理它。