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

Apollo Federation子图规范

服务器库开发者参考子图规范


此内容为添加联邦支持到GraphQL服务器库的开发者提供支持,以及任何对联邦内部工作原理感兴趣的人。如果您正在构建现有的,则无需阅读此内容。兼容子图库,如

与该规范部分或完全兼容的服务器记录在Apollo的子图兼容性存储库

要使GraphQL服务作为Apollo Federation 2子图运行,必须执行以下所有操作:

  • 自动扩展其模式,在子图模式添加中列出所有定义
  • 正确解析Query._service 增强的自省字段
  • 为子图开发者提供一个机制通过 来解决问题,这个机制通过 Query._entities 字段

以下各部分将详细描述这些要求。

子图模式新增

子图必须自动将以下所有定义添加到其 中。每个定义的目的在 方案新增词汇表 中有所描述。

注意

如果你的 GraphQL 服务器库是代码优先而非模式优先(即,它是通过编程方法添加方案定义而不是通过静态 ),则在启动时使用适合你库的任何 API 生成这些定义。

# ⚠️ This definition must be created dynamically. The union
# must include every object type in the schema that uses
# the @key directive (i.e., all federated entities).
union _Entity
scalar _Any
scalar FieldSet
scalar link__Import
scalar federation__ContextFieldValue
scalar federation__Scope
scalar federation__Policy
enum link__Purpose {
"""
`SECURITY` features provide metadata necessary to securely resolve fields.
"""
SECURITY
"""
`EXECUTION` features provide metadata necessary for operation execution.
"""
EXECUTION
}
type _Service {
sdl: String!
}
extend type Query {
_entities(representations: [_Any!]!): [_Entity]!
_service: _Service!
}
directive @external on FIELD_DEFINITION | OBJECT
directive @requires(fields: FieldSet!) on FIELD_DEFINITION
directive @provides(fields: FieldSet!) on FIELD_DEFINITION
directive @key(fields: FieldSet!, resolvable: Boolean = true) repeatable on OBJECT | INTERFACE
directive @link(url: String!, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA
directive @shareable repeatable on OBJECT | FIELD_DEFINITION
directive @inaccessible on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION
directive @tag(name: String!) repeatable on FIELD_DEFINITION | INTERFACE | OBJECT | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION
directive @override(from: String!) on FIELD_DEFINITION
directive @composeDirective(name: String!) repeatable on SCHEMA
directive @interfaceObject on OBJECT
directive @authenticated on FIELD_DEFINITION | OBJECT | INTERFACE | SCALAR | ENUM
directive @requiresScopes(scopes: [[federation__Scope!]!]!) on FIELD_DEFINITION | OBJECT | INTERFACE | SCALAR | ENUM
directive @policy(policies: [[federation__Policy!]!]!) on FIELD_DEFINITION | OBJECT | INTERFACE | SCALAR | ENUM
directive @context(name: String!) repeatable on INTERFACE | OBJECT | UNION
directive @fromContext(field: ContextFieldValue) on ARGUMENT_DEFINITION
# This definition is required only for libraries that don't support
# GraphQL's built-in `extend` keyword
directive @extends on OBJECT | INTERFACE

使用 Query._service 提高内省功能

一些联邦 路由器可以动态地运行时组合其 。为此,图路由器首先对其每个 执行以下增强 以获取所有

query {
_service {
sdl
}
}

⚠️ 警告

Apollo强烈建议不要在图路由器中使用动态组合。如果组合在路由器启动时失败,动态组合可能会导致意外的停机时间。尽管如此,支持这一用例仍然是子图库的需求。

与内置内省的区别

上述“增强”的内省查询与以下GraphQL规范的内置内省查询有以下几点不同:

  • 返回的架构表示是一个字符串,而不是一个__Schema对象。
  • 返回的架构字符串包括了所有使用@key等专门针对联盟的指令。
    • 内置内省查询的响应不包括任何指令的使用。
    • 图路由器需要这些专门针对联盟的指令才能成功执行组合。
  • 如果子图服务器“禁用了内省”,增强的内省查询仍然可用。

注意

_service字段不包括在组合的超图架构中。出于安全原因,它专为图路由器使用。

内省所需解析器

为了支持增强的内省查询,子图服务必须定义以下字段的解析器:

extend type Query {
_service: _Service!
}
type _Service {
sdl: String!
}

Query._service返回一个_Service对象,该对象有一个单一的字段,即sdl(简称为架构定义语言)。该sdl字段返回子图架构的字符串表示。

返回的sdl字符串有以下要求:

  • 它必须包含所有使用所有专门针对联盟的指令,例如@key
  • 如果支持Federation 1,则sdl必须省略子图架构新增中自动添加的所有定义,例如Query._service_Service.sdl
    • 如果你的库仅支持 Federation 2,sdl 可以包含这些定义。

例如,考虑这个 Federation 2

schema.graphql
extend schema
@link(url: "https://specs.apollo.dev/federation/v2.3",
import: ["@key"])
type Query {
me: User
}
type User @key(fields: "id") {
id: ID!
}

对于 sdl 字段返回的值应包含所有这些信息,包括 directives(多余的空白字符可以删除)。

使用 Query._entities 解决实体字段

在一个联合超级图中,一个 实体 是一个 ,它可以在多个子图中定义不同的 字段。您可以通过其实体在模式中使用 @key 指令来识别实体。

在以下示例中,Product 实体在 Products 和 Reviews 子图中定义了其 字段:

Products 子图
type Product @key(fields: "upc") {
upc: String!
name: String!
}
Reviews 子图
type Product @key(fields: "upc") {
upc: String!
avgRating: Int!
}

如果子图向实体贡献任何 字段,它还必须向图路由器直接提供那些字段的值。为此,子图库必须执行以下操作:

  • 定义 _Entity 联合类型,它必须包含子图贡献字段的每个实体类型
  • 提供一个机制,允许子图开发人员根据其实体的 @key 字段识别和返回唯一的实体实例
  • 定义 Query._entities 字段,并使用提供给子图开发人员的机制来解决它

这些要求在下文部分中进一步描述。

定义 _Entity 联合

_Entity 联合类型是子图必须基于其提供的模式动态生成的唯一模式定义。所有其他定义都是静态的,可以按照所示添加。

_Entity 联合必须包含在子图模式中定义的所有实体类型,但具有设置 resolvable: false 的 @key 的实体除外。

注意

如果一个子图定义了零可应用的实体类型,则不应定义 _Entity 联合。

示例

考虑以下子图模式:

Reviews 子图
type Review @key(fields: "id") {
id: ID!
body: String
author: User
product: Product
}
type Product @key(fields: "upc") {
upc: String!
reviews: [Review!]!
}
type User @key(fields: "email", resolvable: false) {
email: String!
}

本图模式中的三种类型都是实体(注意它们的@key 指示器)。但是,User 实体的 @key 设置为 resolvable: false。因此,子图库应该在模式中添加以下 _Entity 联合定义:

# Omits `User` because its @key sets resolvable: false
union _Entity = Review | Product

_Entity 联合由 Query._entities 字段使用,接下来会讲解。

理解 Query._entities

如果一个子图至少向一个实体贡献了字段,它必须自动定义并正确解析 Query._entities 字段:

type Query {
_entities(representations: [_Any!]!): [_Entity]!
}

注意

如果一个子图没有定义任何实体类型,则不应定义 Query._entities 字段。

图表路由器使用此入口直接检索实体对象的字段。它将其他子图返回的同一实体的其他字段与这些字段结合。

Query._entities 字段接受一个必需的 representations 参数,它是一个实体表示的列表。表示是一个包含实体 @key 中之一的所有字段的对象,以及该实体的 __typename 字段。这些都是子图需要用于唯一标识特定实体实例的字段。

representations 列表中的每个项都是一个 _Any 占位符。这是在 子图模式添加 中定义的局部分量。此标量被序列化为通用JSON对象,这允许图表路由器在同一查询中将不同实体的表示包括在内,它们的形状可以不同。

以下是一个 _Any 表示的示例,用于 Product 实体:

{
"__typename": "Product",
"upc": "abc123"
}

查询的Query._entities 字段必须返回一个与提供的表示形式对应的实体对象列表,且顺序必须完全相同。如果提供的表示形式不存在实体,则列表中的条目可以为空。

示例

假设一个超级图包含两个子图,如下所示:

产品
type Product @key(fields: "upc") {
upc: String!
name: String!
}
type Query {
topProducts: [Product!]!
}
评论
type Product @key(fields: "upc") {
upc: String!
reviews: [Review!]!
}
type Review {
score: Int!
description: String!
}

有了这些子图模式,客户端可以针对路由执行以下查询:

query GetTopProductReviews {
topProducts {
reviews {
description
}
}
}

为了解决这个查询,路由首先将以下查询发送到产品子图,因为顶层Query.topProducts 字段是在这里定义的:

query {
topProducts {
__typename
upc
}
}

注意,这个查询包括了Product.__typenameProduct.upc,尽管这些字段没有包含在原始客户端查询中。图路由知道这两个字段用于Product类型表示,它将使用此信息从评论子图获取剩余的字段。

在从产品子图获得此结果后,路由器可以发送以下后续查询到评论子图:

query ($_representations: [_Any!]!) {
_entities(representations: $_representations) {
... on Product {
reviews {
description
}
}
}
}

注意,这个查询使用了内联匹配(... on Product),因为Query._entities的返回类型是_Entity联合类型。

每个路由器将其包含在$_representations列表中的条目具有以下形状:

{
"__typename": "Product",
"upc": "B00005N5PF"
}

这些是从路由器上述产品查询中获取的表示字段。

处理 Query._entities

提醒一下,以下是每个Query._entities字段的定义,每个子图都必须自动定义(除非子图向零个实体贡献字段):

type Query {
_entities(representations: [_Any!]!): [_Entity]!
}

每个子图还必须自动定义此字段。此解析器的逻辑如下:

  1. 创建一个空数组,该数组将包含要返回的实体对象。
  2. 对于列表中包含的每个实体's表示:
    1. 从表示中获取实体的__typename
    2. 将完整的表示对象传递给库提供的任何机制,以便为相应的__typename获取实体。
    3. 将获取的实体对象添加到实体对象数组中。确保对象的列表顺序与相应的表示相同。
  3. 返回实体对象数组。

在上面的步骤 2.2 中,请注意子图开发者负责定义根据其实体的表示获取特定实体逻辑。子图库负责提供开发者指定此逻辑的机制,以及自动在Query._entities的解析器中挂钩此机制。

有关提供此机制的更多详细信息,请参阅下一节。

提供获取实体的机制

当使用您的子图库时,开发者必须能够根据相应实体的表示指定获取唯一实体实例的逻辑。然后,自动定义的Query._entities解析器必须挂钩到此逻辑。

例如,让我们看看Apollo Server(使用@apollo/subgraph库)如何通过引用解析器实现这一点。

在Apollo Server中,开发人员可以为他们的解析器映射中定义的每个实体类型添加一个名为__resolveReference的特殊函数:

resolvers.js
// Products subgraph
const resolvers = {
Product: {
__resolveReference(productRepresentation) {
return fetchProductByUPC(productRepresentation.upc);
}
},
// ...other resolvers...
}

Query._entities解析器按顺序遍历传递给它的representations执行对应的__resolveReference函数。此函数将表示对象作为第一个参数传递给函数。

传递给上方引用解析器的表示对象可能具有以下结构:

{
"__typename": "Product",
"upc": "B00005N5PF"
}

对于此引用解析器,开发者调用了fetchProductByUPC函数,并将表示中的upc传递给它。此函数可能会查询数据库或REST API来检索此subgraph所知的Product实体的字段。

你的subgraph库不需要使用这种引用解析器模式。它只需要提供并一种定义实体获取逻辑的模式。

模式添加词汇表

本节描述了一个有效的subgraph服务必须自动添加到其架构中的类型和字段定义。这些定义在上面的Subgraph架构添加中列出。

有关添加的指令的说明,请参阅联盟特定的GraphQL指令

Query字段

Query._service

Query类型字段必须返回一个非空白的_Service类型

详细信息请参阅增强型的Query._service内省

Query._entities

图路由器使用此根级Query字段直接获取由subgraph定义的实体的字段。

字段必须接受一个representations参数,参数类型为[_Any!]!(一个非空的可选列表,列表元素为非空_Any标量)。它的返回类型必须是[_Entity]!(一个非空的可选列表,列表元素属于_Entity联合类型的对象)。

代表列表中的每个条目都必须使用以下规则进行验证:

  • 代表必须包含一个__typename字符串字段。
  • 代表必须包含与@key指令应用到的相应的实体定义关联的字段集中的所有字段。

有关详细信息,请参阅使用 Query._entities解析实体字段

类型

type _Service

此对象类型必须有一个sdl: String!字段,该字段返回子图schema的SDL作为字符串。

  • 返回的schema字符串必须包括一切对federation-specific指令的引用(@key, @requires等)。
  • 如果支持Federation 1,则schema不能包含来自子图schema增加的定义。

详细信息请参阅增强型的Query._service内省

联合 _Entity

注意

此联合类型根据输入子图schema动态生成。

此联合的可能的类型必须包括子图定义的所有实体。这是Query._entities字段的返回类型,该字段由图路由器使用,以直接访问子图的实体字段。

有关详细信息,请参阅定义_Entity联合

标量 _Any

此标量是图路由器传递给Query._entities字段的实体表示的类型。_Any标量通过与其在子图schema中定义的实体匹配其__typename@key字段进行验证。

_Any序列化为JSON对象,如下所示:

{
"__typename": "Product",
"upc": "abc123"
}

标量 FieldSet

此字符串序列化的标量表示传递给federated指令(如@key, @requires, 或@provides)的字段集。

从语法上讲,一个 FieldSet 是一个 选择集,但不包括最外层的花括号。它可以表示单个 字段"upc")、多个 字段"id countryCode"),甚至嵌套的 "id organization { id }")。

标量作用域

这个字符串序列化的 标量代表一个 JWT 作用域。

标量策略

这个字符串序列化的 标量代表一个授权策略。

指令

请参阅 Federation 特定的 GraphQL 指令

上一个
OpenTelemetry
下一个
从 Federation 1 中的变更
ť评分文章评分在GitHub上编辑编辑论坛Discord

©2024Apollo Graph Inc.,简称Apollo GraphQL。

隐私政策

公司