5. 引用实体
5m

概述

记住我们的梦想

query GetRecipeAndCookwareInformation {
recipe(id: "rec3j49yFpY2uRNM1") {
name
description
ingredients {
text
}
instructions
cookware {
name
description
cleaningInstructions
}
}
}

这个需要改变

在本课中,我们将

什么是实体?

在我们的诗意图谱supergraph中已经有了一个entity。让我们转到Studio的supergraph页面,导航到Schema页面。

从左侧列表选择对象。看到Cookware类型旁边的绿色E标签了吗?E代表实体

https://studio.apollographql.com/

Schema reference page for Objects in Studio

注意:我们在探索器的文档面板中看到了相同的标签,它出现在返回Cookware类型的任何字段旁边。

因为这个实体定义在kitchenware子图谱中,我们没有克隆或本地运行,所以没有真正看到模式文件背后的情况。但没关系,我们实际上并不需要:Studio帮助我们确定了哪些实体类型可用!

您仍然可以在这里找到模式文件(如果您感兴趣的话)。

使用实体,每个子图谱可以进行以下一个或两个操作:

  • 贡献不同的字段到实体
  • 引用一个实体,这意味着使用它作为另一个由子图谱中定义的字段的返回类型

在我们本课程中,我们将重点学习如何引用一个实体。如果您对深入了解实体和更具体的地域概念感兴趣,您可以查看《Voyage》系列

引用实体

Cookware》类型是一个实体;我们知道我们可以引用实体,这意味着使用它作为另一个字段的返回类型。这正是我们在Recipe.cookware字段中想要做的!我们来试试吧。

首先,请确保两个 rover dev 进程仍在运行,以及 recipes

接下来,让我们打开位于我们的 recipes 中的 schema.graphql 文件。

滚动到底部找到 Recipe 类型,并在末尾添加新的

schema.graphql
type Recipe {
# ... other Recipe fields
"List of cookware used in the recipe"
cookware: [Cookware]
}

保存您的更改!第一个 rover dev 进程正在监听该 schema.graphql 文件的更改;当它检测到更改时,它将重新组合本地的 。我们来查看主要的 rover dev 进程... 哦不!出错了!

✨ change detected in ./schema.graphql...
🔃 updating the schema for the 'recipes' subgraph in the session
🎶 composing supergraph with Federation v2.3.1
💀 composition failed, killing the router
error[E029]: Encountered 1 build error while trying to build a supergraph.
Caused by:
UNKNOWN: [recipes] Unknown type Cookware
The subgraph schemas you provided are incompatible with each other. See https://apollo.graphql.net.cn/docs/federation/errors/ for more information on resolving build errors.

我们遇到了构建错误 "未知类型 Cookware"。

这很合理!我们还没有为 recipes 定义 Cookware 类型,因此它还不明白我们指的什么。让我们先修复它。

在同一个模式文件中,紧接在 Recipe 类型下方,添加 Cookware 类型。具体来说,我们将添加一个 占位符Cookware ,它包含 recipes 需要引用的最小

为了定义占位符,我们将在类型定义后添加 @key 。这个指令用于定义实体的主键,它唯一地标识一个实体实例。(例如,让我们区分代表 “铸铁煎锅” 的对象和代表 “炒锅” 的对象!)

schema.graphql
type Cookware @key {
}

我们可以使用 Studio 找到厨具类型的主键。回到 Schema 页面,选择 Objects 列表,点击 Cookware 类型查看其详细信息。我们将在 Keys 部分看到列出的 name

现在让我们回到 schema.graphql 文件。键 @key 指令需要一个 fields 属性,该属性设置为它的主键(或主键!)。在这种情况下,我们将 fields 属性设置为 name。此外,我们还将添加类型定义中的 name ,它返回一个可非空的 String

schema.graphql
type Cookware @key(fields: "name") {
name: String!
}

最后,由于子图不向实体增加任何 (我们只是在实体上使用 name 进行引用,未向其添加字段!),我们需要再添加一个东西。在 @key 指令中,添加另一个 用于 resolvable 并将其设置为 false

schema.graphql
type Cookware @key(fields: "name", resolvable: false) {
name: String!
}

这表明 recipes 不会定义对 Cookware 的引用 。如果你对实体和更具体的聚合概念有兴趣深入了解,可以 查看 Voyage 系列

让我们保存我们的更改,并再次检查 rover dev 输出。

✨ change detected in ./schema.graphql...
🔃 updating the schema for the 'recipes' subgraph in the session
🎶 composing supergraph with Federation v2.3.1
✅ successfully composed after updating the 'recipes' subgraph

一切正常!我们的模式更改已成功与其他 以及路由器一起综合,路由器正在顺畅运行。

所以...我们都完成了,对吧?🤔 让我们回到Sandbox标签页,该标签页连接到我们本地运行的路由器,https://127.0.0.1:4000,并尝试运行我们的查询。

query GetRecipeAndCookwareInformation {
recipe(id: "rec3j49yFpY2uRNM1") {
name
description
ingredients {
text
}
instructions
cookware {
name
description
cleaningInstructions
}
}
}

我们有了菜谱的详细信息...但是向下滚动到我们的数据中的炊具部分时,我们看到的是null

现在,可能根本就没有关于铸铁炒锅的信息。直接使用炊具字段来查询它。

query GetCookware {
cookware(name: "cast iron skillet") {
name
description
cleaningInstructions
}
}

看起来我们收到了数据!所以...问题在哪里?

解析炊具字段

我们的查询可以解析特定菜谱的数据,但当它到达菜谱类型的炊具字段并尝试解析它时,它并不知道该怎么办!这是因为我们还没有为Recipe.cookware字段编写解析器函数!如果没有解析器函数,当查询特定菜谱的炊具数据时,recipes子图不知道该返回什么数据——甚至不知道在哪里找到它。让我们解决这个问题!

src/resolvers/Recipe.js文件中,为炊具字段添加以下解析器函数。

src/resolvers/Recipe.js
cookware(recipe, _, { dataSources }) {
const cookwareNamesList = dataSources.recipesAPI.getRecipeCookware(recipe.id);
if (!cookwareNamesList) return;
return cookwareNamesList.map((c) => ({
name: c,
}));
},

这里发生了什么事?让我们来分析一下。

首先,解析器的参数。我们正在使用第一个参数parent(重命名为recipe)和第三个参数contextValue(解构以访问dataSources)。

接下来,在函数体的内部,我们正在使用data source方法getRecipeCookware来检索菜谱的炊具列表。这个方法已经被我们实现了。

在这种情况下,如果那个菜谱没有任何炊具列表(它可能根本不用它,或者数据缺失!),我们将提前返回。

否则,我们将遍历列表并对每个项目返回一个对象。这个对象被称为实体表示。这是用来表示实体特定实例的方式。表示始终包含该typename和特定实例的@key字段。

  • __typename字段:的所有类型都自动存在这个字段。它总是返回其包含类型的名称,作为字符串。例如,Cookware.__typename返回“Cookware”。
  • @key字段:定义了实体的主键,它可以唯一地识别实体的一个实例。我们知道Cookware类型的主键是其name字段。

所以,回到Recipe.cookware解算器的最后一行:

src/resolvers/Recipe.js
return cookwareNamesList.map((c) => ({
name: c,
}));

在这种情况下,我们省略了__typename字段,因为会自动处理返回该值。由于cookwareNamesList只有一个String列表,我们不能简单地返回这个列表,而是需要map函数将列表中的每个项目转换成所期望的格式。

使用本地路由器测试

好的,我们已经对模式更改进行了代码修改!让我们跳转到沙箱中去检查我们的本地。我们应该会得到我们梦想查询的全部数据。

query GetRecipeAndCookwareInformation {
recipe(id: "rec3j49yFpY2uRNM1") {
name
description
ingredients {
text
}
instructions
cookware {
name
description
cleaningInstructions
}
}
}

太棒了,我们已经得到了它!

实践

使用下面的不完整模式来回答下一个问题。

type Fruit {
id: ID!
name: String
soldBy: Vendor
}
??? {
vendorId: ID!
}
以下哪个代码片段应该在第7行替代问号?

主要收获

  • 一个实体是一个,它具有,这些字段分布在多个中。子图可以向贡献字段,或者简单地引用它。
  • 引用一个意味着将其用作中定义的一个的返回类型。要引用一个实体,子图必须包含包含实体的@key的实体定义的存根,该定义包括实体的主要和将属性设置为false的属性集。
  • 对模式所做的添加可以成功组合,但请不要忘记添加所有必要的代码来解析新的

接下来

本地一切工作得很好!让我们将这些更改推送到,并更新我们的的生产到最新状态。

上一页

分享你对这节课的问题和评论

本课程目前处于

beta
.您的反馈可以帮助我们改进!如果您遇到困难或困惑,请告诉我们,我们会帮助您。所有评论都是公开的,必须遵循 Apollo 行为准则。请注意,已解决或处理的评论可能会被移除。

您需要 GitHub 账户才能发布以下内容。您还没有吗? 请在我们的大洋论坛上发布。