Apollo Federation子图规范
服务器库开发者参考子图规范
此内容为添加联邦支持到GraphQL服务器库的开发者提供子图支持,以及任何对联邦内部工作原理感兴趣的人。如果您正在构建现有的supergraph,则无需阅读此内容。兼容子图库,如Apollo Server。
与该规范部分或完全兼容的服务器记录在Apollo的子图兼容性存储库。
要使GraphQL服务作为Apollo Federation 2子图运行,必须执行以下所有操作:
- 自动扩展其模式,在子图模式添加中列出所有定义
- 正确解析
Query._service
增强的自省字段 - 为子图开发者提供一个机制通过 实体 字段 来解决问题,这个机制通过
Query._entities
字段
以下各部分将详细描述这些要求。
子图模式新增
子图必须自动将以下所有定义添加到其 GraphQL 模式 中。每个定义的目的在 方案新增词汇表 中有所描述。
ⓘ 注意
如果你的 GraphQL 服务器库是代码优先而非模式优先(即,它是通过编程方法添加方案定义而不是通过静态 SDL),则在启动时使用适合你库的任何 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 _Entityscalar _Anyscalar FieldSetscalar link__Importscalar federation__ContextFieldValuescalar federation__Scopescalar federation__Policyenum 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 | OBJECTdirective @requires(fields: FieldSet!) on FIELD_DEFINITIONdirective @provides(fields: FieldSet!) on FIELD_DEFINITIONdirective @key(fields: FieldSet!, resolvable: Boolean = true) repeatable on OBJECT | INTERFACEdirective @link(url: String!, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMAdirective @shareable repeatable on OBJECT | FIELD_DEFINITIONdirective @inaccessible on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITIONdirective @tag(name: String!) repeatable on FIELD_DEFINITION | INTERFACE | OBJECT | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITIONdirective @override(from: String!) on FIELD_DEFINITIONdirective @composeDirective(name: String!) repeatable on SCHEMAdirective @interfaceObject on OBJECTdirective @authenticated on FIELD_DEFINITION | OBJECT | INTERFACE | SCALAR | ENUMdirective @requiresScopes(scopes: [[federation__Scope!]!]!) on FIELD_DEFINITION | OBJECT | INTERFACE | SCALAR | ENUMdirective @policy(policies: [[federation__Policy!]!]!) on FIELD_DEFINITION | OBJECT | INTERFACE | SCALAR | ENUMdirective @context(name: String!) repeatable on INTERFACE | OBJECT | UNIONdirective @fromContext(field: ContextFieldValue) on ARGUMENT_DEFINITION# This definition is required only for libraries that don't support# GraphQL's built-in `extend` keyworddirective @extends on OBJECT | INTERFACE
使用 Query._service
提高内省功能
一些联邦 图 路由器可以动态地运行时组合其 supergraph 模式。为此,图路由器首先对其每个 子图 执行以下增强 内省 查询 以获取所有 子图模式:
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,
例如,考虑这个 Federation 2 subgraph模式:
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 子图中定义了其 字段:
type Product @key(fields: "upc") {upc: String!name: String!}
type Product @key(fields: "upc") {upc: String!avgRating: Int!}
如果子图向实体贡献任何 字段,它还必须向图路由器直接提供那些字段的值。为此,子图库必须执行以下操作:
- 定义
_Entity
联合类型,它必须包含子图贡献字段的每个实体类型 - 提供一个机制,允许子图开发人员根据其实体的
@key
字段识别和返回唯一的实体实例 - 定义
Query._entities
字段,并使用提供给子图开发人员的机制来解决它
这些要求在下文部分中进一步描述。
定义 _Entity
联合
_Entity 联合类型是子图必须基于其提供的模式动态生成的唯一模式定义。所有其他定义都是静态的,可以按照所示添加。
_Entity 联合必须包含在子图模式中定义的所有实体类型,但具有设置 resolvable: false
的 @key 的实体除外。
ⓘ 注意
如果一个子图定义了零可应用的实体类型,则不应定义 _Entity 联合。
示例
考虑以下子图模式:
type Review @key(fields: "id") {id: ID!body: Stringauthor: Userproduct: 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: falseunion _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 {__typenameupc}}
注意,这个查询包括了Product.__typename
和 Product.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]!}
每个子图还必须自动定义此字段解析器。此解析器的逻辑如下:
- 创建一个空数组,该数组将包含要返回的实体对象。
- 对于列表中包含的每个实体's表示:
- 从表示中获取实体的
__typename
。 - 将完整的表示对象传递给库提供的任何机制,以便为相应的
__typename
获取实体。 - 将获取的实体对象添加到实体对象数组中。确保对象的列表顺序与相应的表示相同。
- 从表示中获取实体的
- 返回实体对象数组。
在上面的步骤 2.2 中,请注意子图开发者负责定义根据其实体的表示获取特定实体逻辑。子图库负责提供开发者指定此逻辑的机制,以及自动在Query._entities
的解析器中挂钩此机制。
有关提供此机制的更多详细信息,请参阅下一节。
提供获取实体的机制
当使用您的子图库时,开发者必须能够根据相应实体的表示指定获取唯一实体实例的逻辑。然后,自动定义的Query._entities
解析器必须挂钩到此逻辑。
例如,让我们看看Apollo Server(使用@apollo/subgraph
库)如何通过引用解析器实现这一点。
在Apollo Server中,开发人员可以为他们的解析器映射中定义的每个实体类型添加一个名为__resolveReference
的特殊函数:
// Products subgraphconst 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
_Any
此标量是图路由器传递给Query._entities
字段的实体表示的类型。_Any标量通过与其在子图schema中定义的实体匹配其__typename
和@key
字段进行验证。
_Any序列化为JSON对象,如下所示:
{"__typename": "Product","upc": "abc123"}
标量 FieldSet
FieldSet
此字符串序列化的标量表示传递给federated指令(如@key
, @requires
, 或@provides
)的字段集。
从语法上讲,一个 FieldSet
是一个 选择集,但不包括最外层的花括号。它可以表示单个 字段("upc"
)、多个 字段("id countryCode"
),甚至嵌套的 选择集("id organization { id }"
)。
标量作用域
这个字符串序列化的 标量代表一个 JWT 作用域。
标量策略
这个字符串序列化的 标量代表一个授权策略。