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

实体接口

以多态方式添加实体字段


接口提供强大的,特别适用于与您的一起使用entities

  1. 应用@key interface定义中,使其成为实体接口
  2. 在其他中,使用@interfaceObject指令自动向实现您的实体接口的每个添加字段。

借助这些扩展,您的子图可以快速向多个实体贡献一组字段,而无需复制任何现有(或未来的)实体定义。

概述视频

示例模式

让我们看看定义一个Media实体接口的超级图,以及实现它的Book实体:

子图 A
interface Media @key(fields: "id") {
id: ID!
title: String!
}
type Book implements Media @key(fields: "id"){
id: ID!
title: String!
}
子图 B
type Media @key(fields: "id") @interfaceObject {
id: ID!
reviews: [Review!]!
}
type Review {
score: Int!
}
type Query {
topRatedMedia: [Media!]!
}

这个例子虽然简短,但内容丰富。让我们来分解一下

  • 子图 A 定义了 Media 接口,以及实现该接口的 Book 实体。
    • Media 接口使用 @key 指令,这使得它成为一个 实体接口。
    • 这种用法要求所有实现 Media 的对象都是实体,并且这些实体都使用指定的 @key
    • 如上图所示, Book 是一个实体,并且确实使用了单个指定的 @key
  • 子图 B 想要为所有实现 Media 的实体添加一个 reviews 字段。
    • 为了实现这一点, B 也定义了 Media,但作为 了解更多关于为什么这是必要的
    • Subgraph B 将 @interfaceObject 指令应用于 Media,这表示该对象对应于另一个 的实体接口。
    • Subgraph B 对 Media 使用了与 Subgraph A 相同的确切 @key,并且还定义了所有 @key 字段(在这种情况下,只是 id)。
    • Subgraph B 定义了新的 Media.reviews 字段。
    • 子图B还将负责解析 reviews 字段。要了解如何操作,请参阅解析@interfaceObject

组合针对上述运行时,它会识别子图B的@interfaceObject。它将新的reviews 字段添加到Media接口中,并将该字段添加到实现中Book 实体(以及其他实体)中:

Supergraph架构(简化版)
interface Media @key(fields: "id") {
id: ID!
title: String!
reviews: [Review!]!
}
type Book implements Media @key(fields: "id"){
id: ID!
title: String!
reviews: [Review!]!
}
type Review {
score: Int!
}

子图B可能会通过直接向实体贡献字段来添加Book.reviews。然而,如果我们想将该reviews字段添加到一百多个实体实现中Media怎么办?

通过通过@interfaceObject)添加实体字段,我们可以避免在子图B中重新定义一百个实体(更不用说每当创建一个新的实现实体时都需要添加更多定义了)。了解更多信息。

需求

要使用实体接口和@interfaceObject,您的supergraph必须满足以下所有要求。如果不满足,组合将失败。

启用支持

  • 如果当前没有,所有的子图架构都必须使用@link指令来启用Federation 2特性。

  • 任何使用@interfaceObject指令或对一个接口应用@key都必须针对Apollo Federation规范v2.3或更高版本:

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

    此外,使用@interfaceObject的架构必须将其包含在上面的@link指令的import数组中。

用法规则

interface定义

假设子图A将MyInterface类型定义为一个实体接口,以便其他子图可以为其添加字段:

子图 A
interface MyInterface @key(fields: "id") {
id: ID!
originalField: String!
}
type MyObject implements MyInterface @key(fields: "id") {
id: ID!
originalField: String!
}

在这种情况下

  • 子图A必须在其MyInterface定义中包含至少一个@key指令。
    • 它可能包含多个@key指令。
  • 子图A必须定义超图中实现MyInterface的每个实体类型。
    • 某些其他子图也可以定义这些实体,但子图A必须定义所有这些实体。
    • 您可以想象一个定义实体接口的子图,也可以拥有实现该接口的每个实体。
  • 子图A必须能够唯一识别任何实现MyInterface的实体实例,仅使用由MyInterface定义的@key字段。
    • 换句话说,如果EntityAEntityB都实现MyInterface,则EntityA的任何实例都不能与EntityB的任何实例具有完全相同的@key字段值。
    • 这一唯一性要求始终适用于单个实体的实例。通过实体接口,这一要求扩展到所有实现实体的实例。
    • 此要求是为了支持在子图A中确定性解析接口。
  • 每个实现MyInterface的实体必须包含MyInterface定义中的所有属性。
    • 这些实体可以根据需要定义附加的属性。

@interfaceObject定义

假设子图B将@interfaceObject应用于名为MyInterface的对象类型:

子图 B
type MyInterface @key(fields: "id") @interfaceObject {
id: ID!
addedField: Int!
}

在这种情况下

  • 必须至少有一个其他子图定义一个名为MyInterface的接口类型,并应用@key指令(例如,上面的子图A):

    子图 A
    interface MyInterface @key(fields: "id") {
    id: ID!
    originalField: String!
    }
  • 每个定义MyInterface为对象类型的子图必须:

    • 将其定义应用于@interfaceObject
    • 包含与接口类型定义完全相同的@key
  • 子图B不得将MyInterface也定义为接口类型。

  • 子图B不得定义任何实现MyInterface的实体。

    • 如果一个 子图 通过 @interfaceObject 提交 实体 字段,它将放弃 向实现该接口的任何单个 实体 提交 字段的 能力。

必需的解析器

界面 引用解析器

上面的示例模式中子图 A 定义 Media 为一个 实体 接口,并对其应用了 @key 指令:

子图 A
interface Media @key(fields: "id") {
id: ID!
title: String!
}

与任何 标准实体 类似,@key 指出 "此 子图 可解析提供其 @key 字段 此类型任何实例。" 这意味着子图 A 需要为 Media 定义一个引用 ,就像为任何其他 实体 一样。

注意

定义引用 解析器的 子图库取决于您使用的库。某些 子图 库可能使用不同的术语来表示此功能。有关详细信息,请参阅您的库文档。

以下是在使用 并使用 @apollo/subgraph 库时为 Media 的示例引用

Media: {
__resolveReference(representation) {
return allMedia.find((obj) => obj.id === representation.id);
},
},
// ....other resolvers ...

在这个例子中,假设的 allMedia 包含所有 Media 数据,包括每个对象的 id

@interfaceObject 解析器

字段解析器

上面的示例模式中,子图 B 定义 Media 为一个 对象类型,并将其应用于它。它还定义了 Query.topRatedMedia 字段:

子图 B
type Media @key(fields: "id") @interfaceObject {
id: ID!
reviews: [Review!]!
}
type Review {
score: Int!
}
type Query {
topRatedMedia: [Media!]!
}

子图 B 需要为新的 topRatedMedia 字段定义一个解析器,以及任何其他返回 Media 类型的字段。

记住:从Subgraph B的角度来看,Media 是一个对象。因此,你必须使用和其他对象相同的逻辑来为其创建Subgraph B只需要能够解析它所了解的Media字段(idreviews)。

引用解析器

请注意,在Subgraph B中,Media是一个应用了@keyobject types,因此它是一个标准实体。与任何实体定义一样,它也需要相应的引用解析器:

Media: {
__resolveReference(representation) {
return allMedia.find((obj) => obj.id === representation.id);
},
},
// ....other resolvers ...

为什么@interfaceObject是必要的?

没有@interfaceObject指令及其相关组合逻辑,将接口类型的定义在子图之间分发,可能会对您的子图团队造成持续的维护需求。

让我们看看一个没有使用@interfaceObject的例子。在这里,Subgraph A定义了Media接口,以及两个实现实体:

子图 A
interface Media {
id: ID!
title: String!
}
type Book implements Media @key(fields: "id") {
id: ID!
title: String!
author: String!
}
type Movie implements Media @key(fields: "id") {
id: ID!
title: String!
director: String!
}

现在,如果Subgraph B想要在Media接口中添加一个reviews字段,它不能仅仅定义该字段:

子图 B
interface Media {
reviews: [Review!]!
}
type Review {
score: Int!
}
type Query {
topRatedMedia: [Media!]!
}

这个添加打破了组合。在supergraph模式中,现在Media接口定义了reviews字段,但Book nor Movie 没有定义!

为了使其有效,Subgraph B还必须将reviews字段添加到每个实现Mediaentities

⚠️

interface Media {
reviews: [Review!]!
}
type Review {
score: Int!
}
type Book implements Media @key(fields: "id") {
id: ID!
reviews: [Review!]!
}
type Movie implements Media @key(fields: "id") {
id: ID!
reviews: [Review!]!
}

这将解决我们当前的组合错误,但组合将再次在Subgraph A定义实现Media的新的实体时打破:

子图 A
type Podcast implements Media @key(fields: "id") {
id: ID!
title: String!
}

为了防止这些组合错误,维护Subgraph A和Subgraph B的团队需要在创建每个Media实现时协调其模式更改。想象一下,如果Media的定义分布在十个子图中!

总的来说,Subgraph B不应该需要知道您的supergraph中存在的所有可能的Media类型。相反,它应该普遍知道如何检索任何类型的Media的评论。这正是实体接口和@interfaceObject提供的关系,如上例所示。

使用@interfaceObject有其他替代方案吗?

使用@interfaceObject的主要替代方法是使用前文提到的不可取策略。这需要在每个贡献该接口字段的子图中复制给定接口的所有实现。

请注意,此替代方法还要求每个子图能够解析实现该接口的任何对象的类型。在许多情况下,特定的子图无法完成此任务,这意味着此替代方法不可行。

上一节
使用上下文来共享数据
下一节
迁移字段
评价文章评价在GitHub上编辑编辑论坛Discord

©2024Apollo GraphInc.,以Apollo GraphQL营利。

隐私政策

公司