使用Apollo Federation配置访问其他服务的儿童服务数据



我们已经使用Apollo Federation作为我们的主要api大约1.5年了。在联邦网关后面有6个子graphql服务,它们都在网关上组合在一起。当您有一个跨不同服务的结果数据集时,这种配置确实非常有效。例如,引用购买用户及其关联事件的票证列表,等等。

我们经历过这种故障的一个地方是,当需要预先设置的数据时,该数据已经在另一个子服务(或跨其他子服务)中定义(解析器/路径)。没有办法(我们已经发现)从子服务查询联合,以获得一组联合数据,供解析器使用以处理该数据。

例如,假设我们定义了一个graphql查询,它查询一个事件的所有票证,并通过联合返回购买者的数据、事件的数据和产品的数据。如果我需要来自解析器的数据集,我需要自己再次进行所有这些查询,复制dataSource逻辑,并必须匹配代码中的数据。

出现了一个疯狂的想法,那就是设置apollo-datasource-restdataSource来对我们的网关端点进行查询,作为我们解析器的dataSource。通过这种方式,我们可以请求我们需要的数据,并让Apollo Federation按照设计将所有数据缝合在一起。因此,我们将从已经定义了该查询的graphql网关请求数据,而不是由解析器查询数据库中的所有不同数据,然后进行匹配。

我们试图通过这样做来避免在子服务中重复一组查询,以获取其他服务中(或跨其他服务)已经可用的详细信息。

问题

这真的是个坏主意吗
这是一个合理的想法吗
以前有人试过这样的东西吗?


是的,我们必须确保解析器上没有循环依赖关系。在我们的例子中;dataSource访问网关";用于收集突变的初始数据。


联合查询的示例。在该查询中,eventallocatedTopurchasedByproduct都是其他服务中的类型。event是事件类型,allocatedTopurchasedBy是配置文件类型,而product是产品类型。这个查询为我提供了所有的数据,比如说,向结果集中的人发送电子邮件通知。尽管要从突变中的解析器中获取这些数据来排队处理这些电子邮件,意味着我需要自己进行多次查询,并通过代码对齐所有数据,而不是使用网关/联合会,它已经与已经建立的查询一起完成了这项工作。使用apollo-datasource-rest查询我们自己的网关的想法是以这种形式获取这些数据。而不是通过单独的查询和代码来对齐id等。

query getRegisteredUsers($eventId: ID!) {
communications {
event(eventId: $eventId) {
registered {
event {
name
}
isAllocated,
hasCheckedIn,
lastUpdatedAt,
allocatedTo {
firstName
lastName
email
}
purchasedBy {
id
firstName
lastName
}
product {
__typename
...on Ticket {
id
name
}
}
}
}
}
}

仅供参考,直到我看了你的编辑,其中有一些例子,我才完全理解这个问题。

这真的是个坏主意吗?

根据我的经验,是的。这不是一个主意,因为你和其他做过这件事的非常聪明的人在一起。

这是一个合理的想法吗?

这当然是合理的,但我不推荐。

以前有人尝试过这样的东西吗?

是的,但我希望你不要。


您的问题

让解析器向网关发出请求:

我不建议这样做。我亲眼目睹了这种情况的发生,我亲自努力帮助公司摆脱这种困境。循环依赖关系即将发生。随着跳数、TLS握手等次数的增加,延迟会急剧增加。改为编排。引入非GraphQL感觉很奇怪,但IMO最终它比where";只需与网关通话";带你去。


然后呢

当你处理一些突变时,需要来自多个数据源的数据才能处理一件事(比如向某人发送交易电子邮件),你有一些选择。帮助我解决这个问题的是";在GraphQL之前我该怎么做">

  1. 编排:您有一个单一的"编排服务";,它接受突变并对所有者服务进行调用(最好是非GraphQL,所以REST、gRPC、Lambda?)以收集数据。编排层不拥有数据,但它可以与其他服务通信。这类似于Federation,但用于将数据发送到请求中,而不是发送到响应中。

  2. 编排:您可以通过事件流触发大致相同的事情。(不适用于GraphQL的请求/响应模型)

  3. CQRS(投影):数据库数据的副本,用于报告之类的事情。CQRS基本上是";读取数据的方式不必与写入数据的方式相同";,并且它允许诸如事件源数据之类的东西。如果您的所有数据源实际上共享同一个数据库,那么您甚至不需要"投影";就像您只想要一个读取复制副本一样。如果您没有足够的规模来进行复制,只需跳过它,并承诺永远不会写入当前域不拥有的数据。


我的工作

在我工作的地方,我把我们带到了:

查询

  1. 查询总是以"开头;一个数据库调用">
  2. 如果";一个数据库调用";进入一个数据域(通常为true),该查询进入一个服务,Federation填充树的叶子。如果你真的遵循CQRS,这可能和#3一样,但我们没有
  3. 如果您的";一个数据库调用";需要跨域的数据(例如,获取包含产品X的所有订单,但按客户的名字排序),您需要数据库投影。优选地,这可以通过";报告服务":它不拥有任何数据,但读取所有数据

突变

  1. 如果您的顶级突变修改仅在一个域内起作用,则该突变进入服务,它可以使用数据库事务,并且Federation会填充叶子
  2. 如果您的突变需要跨多个域写入,并且需要立即保持一致性(通过库存、付款等下单),我们选择了跨多个服务写入的编排(并在必要时回滚,因为我们没有数据库事务来为我们做这件事)
  3. 如果您的突变需要来自多个地方的数据才能进一步发送到请求中(比如发送电子邮件),我们选择了从多个服务中提取并向下推送数据的编排。这感觉很像联邦,但恰恰相反

最新更新