于10月8日至10日加入我们,在纽约市了解关于GraphQL Federation和API平台工程的最新技巧、趋势和新闻。参加在纽约市的GraphOS 2024会议
文档
免费开始

Apollo联盟中的值类型

在多个子图中共享类型和字段


在一个联盟图中中,想要在多个 GraphQL 类型之间共享是很常见的子图

例如,假设您想在不同的子图中定义和重用通用Position类型:

type Position {
x: Int!
y: Int!
}

此类类型被称为值类型。本文描述了如何在联盟图中共享值类型及其,使多个子图能够定义并解析它们。

共享对象类型

默认情况下,在Federation 2 子图中,单个对象字段不能被超过一个定义或解析。

考虑以下Position示例:

子图 A
type Position {
x: Int!
y: Int!
}
子图 B
type Position {
x: Int!
y: Int!
}

尝试将这些两个组合在一起将会破坏组合。破坏组合。路由器不知道哪个负责解析Position.xPosition.y。要使多个子图解析这些字段,您必须首先将此字段标记为@shareable

注意

作为替代方案,如果您希望A和B解析Position的不同字段,您可以指定Position类型为实体

使用 @shareable

@shareable指令允许多个子图解析特定对象字段(或一组对象字段)。

要在子图模式中使用@shareable,您首先需要将以下片段添加到该模式中,以选择加入 Federation 2

extend schema
@link(url: "https://specs.apollo.dev/federation/v2.3",
import: ["@key", "@shareable"])

然后,您可以将其应用到一个,或者应用于该类型的单个字段:

子图 A
type Position @shareable {
x: Int!
y: Int!
}
子图 B
type Position {
x: Int! @shareable
y: Int! @shareable
}

注意

将类型标记为@shareable等效于将所有字段标记为@shareable,因此上面的两个子图定义是等效的。

现在,子图A和B都可以解析xy字段,我们的子图模式将成功组合成一个

⚠️ 对于 @shareable 的重要注意事项

  • 如果一个类型或字段被标记为 @shareable 在任何子图中,它必须在定义它的每个子图中被标记为 @shareable@external。否则, 将失败。
  • 如果有多个子图可以解析一个字段,请确保每个子图对该字段的 行为相同。否则,查询的结果可能会因哪个子图解析了该字段而不同。

使用 @shareableextend

如果您将 @shareable 应用于对象类型声明,它仅适用于该声明中的 字段

子图 A
type Position @shareable {
x: Int! # shareable
y: Int! # shareable
}
extend type Position {
z: Int! # ⚠️ NOT shareable!
}

使用 extend 关键字,上述模式包括对 Position 的两个不同声明。因为只有第一个声明被标记为 @shareable,所以 Position.z 不被视为可分享的。

要使 Position.z 可分享,您可以执行以下操作之一:

  • 将单个字段 z 标记为 @shareable

    extend type Position {
    z: Int! @shareable
    }
  • 将整个 extend 声明标记为 @shareable

    • 这种策略需要将您的子图模式中的 规范版本 targeting v2.2 或更高。早期版本不支持将 @shareable 应用到同一对象类型多次。

      extend schema
      @link(url: "https://specs.apollo.dev/federation/v2.3", import: ["@shareable"])
      extend type Position @shareable {
      z: Int!
      }

不同的共享字段

共享字段只能以特定方式在不同 返回类型参数 中有所不同。如果您要在子图之间共享的字段之间有比允许的更大的差异,请使用 实体 而不是可分享的值类型。

返回类型

假设两个子图都定义了具有一个 timestamp 字段的 Event 对象类型:

子图 A
type Event @shareable {
timestamp: Int!
}
子图 B
type Event @shareable {
timestamp: String!
}

A的时间戳 返回一个整数,而子图B返回一个字符串。这是无效的。当组合 尝试为超级图模式生成事件 类型时,由于两个时间戳 字段定义之间的不可解决的冲突 ,它失败。

接下来,看看对位置 对象类型的这些不同定义:

子图 A
type Position @shareable {
x: Int!
y: Int!
}
子图 B
type Position @shareable {
x: Int
y: Int
}

子图A中xy 字段是非空的,但在子图B中是可为空的。这是有效的。组合 识别出它可以使用下面的定义在超级图模式中为位置

超级图模式
type Position {
x: Int
y: Int
}

这个定义适用于对子图A的查询,因为子图A的定义比这个更严格(对于可为空字段,非空值始终有效)。在这种情况下,组合 将子图A的位置 字段转换为满足子图B降低限制性的定义。

注意

子图A的实际子图模式没有修改。在子图A中,xy 仍然是非空的。

参数

共享字段参数 在不同子图之间可能以某种方式不同:

  • 如果一个参数 至少在一个子图中是必需的,那么它可以在其他子图中是可选的。它不能被省略。
  • 如果一个参数 在定义它的每个子图表中都是可选的,那么在它定义的其他子图中省略它是技术上有效的。然而:
    • ⚠️ 如果任何子图省略了字段参数,那么该参数将完全省略在超级图模式中。这意味着客户端不能为该字段提供参数。

子图 A
type Building @shareable {
# Argument is required
height(units: String!): Int!
}
子图 B
type Building @shareable {
# Argument can be optional
height(units: String): Int!
}

子图 A
type Building @shareable {
# Argument is required
height(units: String!): Int!
}
子图 B
type Building @shareable {
# ⚠️ Argument can't be omitted! ⚠️
height: Int!
}

⚠️

子图 A
type Building @shareable {
# Argument is optional
height(units: String): Int!
}
子图 B
type Building @shareable {
# Argument can be omitted, BUT
# it doesn't appear in the
# supergraph schema!
height: Int!
}

有关更多信息,请参阅输入类型和字段参数

省略字段

看看这两个位置 对象类型的定义:

⚠️

子图 A
type Position @shareable {
x: Int!
y: Int!
}
子图 B
type Position @shareable {
x: Int!
y: Int!
z: Int!
}

子图B定义了一个z 字段,但子图A没有。在这种情况下,当组合 为超级图模式生成位置 类型时,它包含了所有三个字段

超级图模式
type Position {
x: Int!
y: Int!
z: Int!
}

这个定义适用于子图B,但它对子图A提出了问题。假设子图A定义了以下查询 类型:

子图 A
type Query {
currentPosition: Position!
}

根据假定的 supergraph 架构 ,以下 对 supergraph 是有效的:

query GetCurrentPosition {
currentPosition {
x
y
z # ⚠️ Unresolvable! ⚠️
}
}

问题是这样的:如果子图 B 没有定义 Query.currentPosition,那么必须对子图 A 执行该 查证。但子图 A 缺少 Position.z 字段,因此无法解决该字段!

认识潜在问题,并以错误失败。那么我们如何解决这个问题?请查看 不可解决字段的解决方案

添加新共享字段

将新字段添加到值类型可能会导致 组合 问题,因为很难同时将该字段添加到所有定义 子图

假设我们正在将 z 字段添加到我们的 Position 值类型,并且我们从子图 A 开始:

⚠️

子图 A
type Position @shareable {
x: Int!
y: Int!
z: Int!
}
子图 B
type Position @shareable {
x: Int!
y: Int!
}

可能当我们尝试组合这些两个架构时, 组合 将失败,因为子图 B 无法解决 Position.z.

要逐步将字段添加到所有子图而不会破坏 组合,我们可以在其中使用 @inaccessible 指令

使用 @inaccessible

如果将 @inaccessible 指令应用于一个字段, 组合 将会省略该字段从您的 API 架构 这有助于您逐步将一个字段添加到多个子图而不会破坏组合。

要在子图中使用 @inaccessible,首先请确保将其包含在 Federation 2 的 import 声明中的

子图 A
extend schema
@link(url: "https://specs.apollo.dev/federation/v2.3",
import: ["@key", "@shareable", "@inaccessible"])

每次将新字段添加到值类型时,如果该字段不是在每个定义值类型的子图中都存在,则将 @inaccessible 应用到该字段:

子图 A
type Position @shareable {
x: Int!
y: Int!
z: Int! @inaccessible
}
子图 B
type Position @shareable {
x: Int!
y: Int!
}

即使 Position.z 在多个子图中已定义,您也只需在其中一个子图中应用 @inaccessible 来省略它。事实上,您可能只想在其中一个子图中应用它,以便以后更容易删除它。

使用上述语法, 组合 将省略生成的 API架构中的 Position.z,并且生成的 Position 类型将仅包括 xy 字段。

注意

请注意,Position.z在supergraph架构中确实存在,但API架构强制规定客户端可以包含哪些字段中。了解更多关于联邦架构的信息。

随时您都可以现在将Position.z添加到子图B:

子图 A
type Position @shareable {
x: Int!
y: Int!
z: Int! @inaccessible
}
子图 B
type Position @shareable {
x: Int!
y: Int!
z: Int!
}

此时,Position.z仍然是@inaccessible,因此组合仍然忽略它。

最后,当您已将Position.z添加到定义Position的每个子图时,您可以从子图A中删除@inaccessible

子图 A
type Position @shareable {
x: Int!
y: Int!
z: Int!
}
子图 B
type Position @shareable {
x: Int!
y: Int!
z: Int!
}

组合现在已成功在supergraph架构中包含Position.z

联合和接口

Federation 2中,默认情况下,unioninterface类型定义可以在子图之间共享,并且那些定义可以不同:

子图 A
union Media = Book | Movie
interface User {
name: String!
}
子图 B
union Media = Book | Podcast
interface User {
name: String!
age: Int!
}

组合逻辑在您的supergraph架构中合并这些定义:

超级图模式
union Media = Book | Movie | Podcast
# The object types that implement this interface are
# responsible for resolving these fields.
interface User {
name: String!
age: Int!
}

当不同的子图负责一组相关类型或值的子集时,这可能非常有用。

注意

您还可以在多个子图之间使用enum类型。有关详细信息,请参阅合并来自多个子图的类型

共享接口的挑战

在子图之间共享接口类型会在接口变化时引入维护挑战。考虑以下子图:

子图 A
interface Media {
id: ID!
title: String!
}
type Book implements Media {
id: ID!
title: String!
}
子图 B
interface Media {
id: ID!
title: String!
}
type Podcast implements Media {
id: ID!
title: String!
}

现在,假设子图B向Media接口添加一个creator 字段:

子图 A
interface Media {
id: ID!
title: String!
}
type Book implements Media {
id: ID!
title: String!
# ❌ Doesn't define creator!
}
子图 B
interface Media {
id: ID!
title: String!
creator: String!
}
type Podcast implements Media {
id: ID!
title: String!
creator: String!
}

这打破了组合,因为Book也实现了Media但未定义新的creator 字段。

为了避免此错误,所有子图中的实现类型都需要更新,以包含所有Media字段。随着您的子图和团队数量增长,这变得越来越具有挑战性。幸运的是,有一种解决方案

解决方案:实体接口

apol delenation 2.3引入了一种强大的抽象机制,使您能够在不需要更新每个实现类型的情况下,向子图添加接口字段。

了解更多关于实体接口的信息。

输入类型

子图可以共享 输入类型定义,但组合使用交集策略合并它们的字段。当输入类型跨多个子图组合时,只有互相的字段在超级图模式中保留:

子图 A
input UserInput {
name: String!
age: Int # Not in Subgraph B
}
子图 B
input UserInput {
name: String!
email: String # Not in Subgraph A
}

只合并所有输入类型都有的字段。了解更多信息,请参阅合并输入类型和字段参数

超级图模式
input UserInput {
name: String!
}

要了解更多关于组合如何在底层合并不同模式类型的信息,请参阅在组合期间合并类型

上一页
迁移字段
下一页
联邦指令
评价文章评价Edit on GitHubEditForumsDiscord

©2024Apollo Graph Inc.,其中文名为 Apollo GraphQL。

隐私政策

公司