请于10月8日至10日加入我们在纽约市的相聚,了解GraphQL联邦和API平台工程的最新技巧、趋势和新闻。加入2024年纽约市GraphQL峰会
文档
免费开始

跨子图聚合数据

如果产品需求不符合单个领域,可能表明需要一个新的领域

联邦

A 帮助您在多个领域之间协调数据检索,但它并不能自动解决分布式架构中常见的一些问题:

  • 搜索存储在多个 中的多种类型
  • 将来自多个来源的多个类型的列表组合成一个列表,尤其是当分页时
  • 根据定义在多个 数据源 中的属性过滤列表
  • 从多个 数据源 的聚合中推导数据

虽然有时可以使用 来支持这些用例使用 @requires ,但几乎总是最好在新系统中提供此功能,例如搜索索引。

给定一个在书籍和电影之间搜索的操作,我们希望返回一个符合搜索条件的多态的书和电影列表。

query SearchEverything($query: String!) {
search(query: $query) {
... on Book {
title
authors {
name
}
}
... on Movie {
title
directors {
name
}
}
}
}

如果不同的 定义了 Book 类型和 Movie 类型,那么问题是:哪个 提供了 Query.search

对于给定的 操作会解析单个 子图 中的每个 字段。这种情形即使在多个子图定义了特定的字段时也是成立的。

书籍子图
type Query {
search(query: String!): [Product] @shareable
}
interface Product {
title: String
}
type Book implements Product {
title: String
authors: [Person]
}
电影子图
type Query {
search(query: String!): [Product] @shareable
}
interface Product {
title: String
}
type Movie implements Product {
title: String
directors: [Person]
}

查询规划器会确定性地选择一个 子图 来解析 Query.search 字段。(它会计算操作的所有有效 查询计划,并选择“最便宜的”一个。)

如果查询规划器选择在 书籍 子图中使用 Query.search,那么该子图只能提供书籍,不能提供电影。

为了解决这个问题,我们可以扩展书籍 子图 模式以包括 Movie 定义并添加 @key 指令以创建到电影 子图 的连接,如下所示:

type Movie implements Product @key(fields: "id") {
id: ID!
title: String @external
}

现在,书籍 子图 可以返回 电影 实例,这意味着它需要访问电影数据的数据源。这打破了我们在创建域和子图之间的界限时所依赖的职责分离。

解决方案:创建新的子图

当产品需求不能很好地适应单个域时,通常表明我们需要一个新的域。让我们设计一个包含新搜索域的系统。这包括搜索索引和提供 Query.search字段的 Search 子图

Search domain
reads
writes
writes
Search Index
Search subgraph
Client
Router
Books
Movies
Books DB
Movies DB

此模式适用于上述所有用例

  • 搜索:搜索索引(如Elasticsearch)是搜索各种数据类型并返回最相关结果的最有效方法。
  • 合并列表:合并索引是列出和翻页各种数据类型的最有效方法。动态获取多个列表并将它们合并通常意味着过度检索数据页,并在它们不包含在结果中时丢弃数据。
  • 过滤:数据存储可以包含各种数据类型的各种属性上的索引,并有效地根据这些属性过滤结果。
  • 导出的聚合:许多数据存储可以有效地计算导出值,例如AVG(products.rating),或者我们可以将预先计算的导出值写入数据存储。

我们可以从书籍和电影子图的对象中删除Query.search字段,并将其添加到我们新的搜索子图中:

搜索子图
type Query {
search(query: String!): [Product]
}
interface Product {
id: ID!
title: String
}
type Book implements Product @key(fields: "id") {
id: ID!
title: String @shareable
}
type Movie implements Product @key(fields: "id") {
id: ID!
title: String @shareable
}

查询计划首先使用搜索子图来返回一组多态的书籍电影及其id标题字段。然后在并行的基础上,它与书籍子图结合以获取Book.authors,并与电影子图结合以获取Movie.directors

请注意,搜索子图提供了一组最小字段。我们不需要在搜索域中复制整个BookMovie类型,只需复制我们想要搜索的字段。

权衡

正如你所预期的那样,向你的超级图添加全新的领域有其权衡:

所有权问题

在搜索子图的例子中,我们引入了多个新的服务,需要持续的开发、维护和支持。让我们的现有书籍和电影团队承担这项额外负担是没有意义的。通常,我们想要成立一个全新的团队来运营新的子图和数据存储的告警和部署密钥。

最终一致性

从书籍和电影数据库将数据复制到搜索索引不可避免地涉及复制延迟,从而导致子图之间的结果最终一致。

我们如何处理这种延迟取决于我们的业务需求。如果 Query.search 的结果必须“内部”一致,我们可以使用 @shareable@provides 来对数据进行反规范化。我们通过提供搜索子图及其支持索引的 title 字段来演示了这一点。

如果结果必须与我们权威数据源准确匹配,我们可以确保查询规划器从相应的子图中检索这些字段。例如,Book.authorsMovie.directors 字段就体现了这种模式。

接下来
主页
评定文章评定在GitHub上编辑编辑论坛Discord

©2024Apollo Graph Inc.,又称为Apollo GraphQL。

隐私政策

公司