1

我们使用 Apollo Federation 作为我们的主要 api 已经有 1.5 年了。联邦网关后面是 6 个子 graphql 服务,它们都在网关处组合。当您拥有跨越不同服务的数据结果集时,此配置确实非常有效。例如,引用购买的用户和与之关联的事件的门票列表等。

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

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

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

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

问题

这真的是个坏主意吗?
这是一个合理的想法吗?
有没有人尝试过这样的事情?


是的,我们必须确保解析器没有循环依赖。在我们的例子中,我看到用于收集突变中的初始数据的“数据源访问网关”。


联合查询示例。在此查询中,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
          }
        }
      }
    }
  }
}
4

1 回答 1

2

仅供参考,在查看您的编辑之前,我不太明白这个问题,其中有一些示例。

这真的是个坏主意吗?

以我的经验,是的。不是一个想法,因为您与其他非常聪明的人相处得很好。

这是一个合理的想法吗?

这绝对是合理的,但我不推荐它。

有没有人尝试过这样的事情?

是的,但我希望你不要。


你的问题

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

我不推荐这个。我亲眼目睹了这种情况的发生,我亲自帮助公司摆脱了这把你带入的混乱局面。循环依赖将会发生。当你有越来越多的跃点、TLS 握手等时,延迟只会飙升。改为进行编排。引入非 GraphQL 感觉很奇怪,但 IMO 最终比“只与网关对话”更简单、更快速、更易于维护。


然后怎样呢?

当您处理一些需要来自多个数据源的数据才能处理单个事情的突变时(例如向某人发送交易电子邮件),您有一些选择。帮助我弄清楚这一点的问题是“在 GraphQL 之前我将如何做到这一点?”

  1. 编排:你有一个单一的“编排服务”,它接受突变并调用(最好是非 GraphQL,所以是 REST、gRPC、Lambda?)到所有者服务以收集数据。编排层不拥有数据,但它可以与其他服务对话。这就像联邦,但用于将数据发送到请求中,而不是发送到响应中。

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

  3. CQRS(预测):数据库数据的副本,用于报告等。CQRS 基本上是“读取数据的方式不必与写入数据的方式相同”,它允许诸如事件源数据之类的事情。如果您的所有数据源实际上共享同一个数据库,那么您甚至不需要“投影”,因为您只需要一个只读副本。如果您的规模不足以进行复制,只需跳过它并保证永远不会写入您当前域不拥有的数据。


我所做的

在我工作的地方,我让我们:

查询

  1. 查询总是以“一个数据库调用”开始。
  2. 如果“一个数据库调用”进入一个数据域(通常是正确的),则该查询进入一个服务,并且联合填充树的叶子。如果你真的遵循 CQRS,这可能与 #3 相同,但我们没有。
  3. 如果您的“一个数据库调用”需要来自跨域的数据(例如,获取所有包含产品 X 的订单,但按客户的名字排序),您需要一个数据库投影。最好这可以由“报告服务”处理:它不拥有任何数据,但它读取所有数据。

突变

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