跨子图聚合数据
如果产品需求不符合单个领域,可能表明需要一个新的领域
Asupergraph 帮助您在多个领域之间协调数据检索,但它并不能自动解决分布式架构中常见的一些问题:
- 搜索存储在多个 数据源 中的多种类型
- 将来自多个来源的多个类型的列表组合成一个列表,尤其是当分页时
- 根据定义在多个 数据源 中的属性过滤列表
- 从多个 数据源 的聚合中推导数据
虽然有时可以使用 查询计划 来支持这些用例使用 @requires
指令,但几乎总是最好在新系统中提供此功能,例如搜索索引。
示例:搜索
给定一个在书籍和电影之间搜索的操作,我们希望返回一个符合搜索条件的多态的书和电影列表。
query SearchEverything($query: String!) {search(query: $query) {... on Book {titleauthors {name}}... on Movie {titledirectors {name}}}}
如果不同的 子图定义了 Book
类型和 Movie
类型,那么问题是:哪个 子图 提供了 Query.search
根 字段?
对于给定的 操作, 查询规划器会解析单个 子图 中的每个 字段。这种情形即使在多个子图定义了特定的字段时也是成立的。
type Query {search(query: String!): [Product] @shareable}interface Product {title: String}type Book implements Product {title: Stringauthors: [Person]}
type Query {search(query: String!): [Product] @shareable}interface Product {title: String}type Movie implements Product {title: Stringdirectors: [Person]}
查询规划器会确定性地选择一个 子图 来解析 Query.search
字段。(它会计算操作的所有有效 查询计划,并选择“最便宜的”一个。)
如果查询规划器选择在 书籍 子图中使用 Query.search
,那么该子图只能提供书籍,不能提供电影。
为了解决这个问题,我们可以扩展书籍 子图 模式以包括 Movie
定义并添加 @key
指令以创建到电影 子图 的连接,如下所示:
type Movie implements Product @key(fields: "id") {id: ID!title: String @external}
现在,书籍 子图 可以返回 电影 实例,这意味着它需要访问电影数据的数据源。这打破了我们在创建域和子图之间的界限时所依赖的职责分离。
解决方案:创建新的子图
当产品需求不能很好地适应单个域时,通常表明我们需要一个新的域。让我们设计一个包含新搜索域的系统。这包括搜索索引和提供 Query.search
根 字段的 Search 子图。
此模式适用于上述所有用例
- 搜索:搜索索引(如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。
请注意,搜索子图提供了一组最小字段。我们不需要在搜索域中复制整个Book
和Movie
类型,只需复制我们想要搜索的字段。
权衡
正如你所预期的那样,向你的超级图添加全新的领域有其权衡:
所有权问题
在搜索子图的例子中,我们引入了多个新的服务,需要持续的开发、维护和支持。让我们的现有书籍和电影团队承担这项额外负担是没有意义的。通常,我们想要成立一个全新的团队来运营新的子图和数据存储的告警和部署密钥。
最终一致性
从书籍和电影数据库将数据复制到搜索索引不可避免地涉及复制延迟,从而导致子图之间的结果最终一致。
我们如何处理这种延迟取决于我们的业务需求。如果 Query.search
的结果必须“内部”一致,我们可以使用 @shareable
和 @provides
来对数据进行反规范化。我们通过提供搜索子图及其支持索引的 title
字段来演示了这一点。
如果结果必须与我们权威数据源准确匹配,我们可以确保查询规划器从相应的子图中检索这些字段。例如,Book.authors
和 Movie.directors
字段就体现了这种模式。