概述
是时候为我们的开发人员工具带添加一个新工具了:实体!
在本课中,我们将
- 了解什么是实体,它的用途以及如何定义它
- 了解 路由器 如何使用 实体 表示和 查询计划 从多个 子图 连接数据
- 了解 实体 表示和引用 解析器 如何协同工作
食谱和配乐
还记得我们的梦想 查询 吗?
query GetRecipeAndRecommendedSoundtracks {randomRecipe {idnamedescriptioningredients {text}instructionsrecommendedPlaylists {idnamedescriptiontracks {idnameexplicitdurationMs}}}}
我们设想生成一个随机食谱,并立即获得与之搭配的完美配乐推荐。我们称之为 字段:recommendedPlaylists
.
这 recommendedPlaylists
字段 需要位于 soundtracks
子图 中,因为它与音乐数据相关,但它属于 Recipe
类型。
最大的问题是什么? soundtracks
子图 完全不知道 Recipe
类型是什么!让我们用实体来解决这个问题。
什么是实体?
一个 实体 是一个 对象类型,其 字段 分布在多个 子图 中。它是联邦图架构的基本构建块,用于在子图之间连接数据,同时仍然遵守关注点分离原则。
一个 子图 定义一个 实体 可以执行以下一项或两项操作:
- 贡献 不同的 字段 到 实体
- 引用 一个 实体,这意味着将其用作 子图 中定义的另一个 字段 的返回类型
贡献与引用
为了区分 子图 贡献 到一个 实体,以及那些 引用 一个 实体,可以这样想:一个 子图 贡献 字段 实际上是将 它自己的域 中的新数据功能添加到 实体 类型。
这与仅仅 引用 实体 的 子图 相反;它本质上只是“提及”实体的存在,并将其作为 另一个字段 的返回类型。
在联邦中,我们使用实体来创建一致的类型,这些类型不会局限于一个 子图 或另一个;相反,它们可以跨越我们整个 API!
我们已经有了 实体 在我们的 超级图 中。让我们转到 Studio,到我们的超级图页面,然后导航到 模式 页面。
从左侧列表中选择 对象。看到 Recipe
类型旁边的绿色 E 标签吗?E 代表 实体!
注意: 我们在 Explorer 的文档面板中看到了相同的标签,它位于返回 Recipe
类型的任何 字段 旁边。
如何创建实体
要创建一个 实体,一个 子图 需要提供两项内容:一个 主键 和一个 引用解析器。
定义主键
一个 实体 的 主键 是 字段 (或字段),它可以唯一地标识 子图 中该 实体 的一个实例。 路由器 使用主键从多个子图收集数据,并将其与单个实体实例相关联。这就是我们知道每个子图都在讨论 - 以及为其提供数据 - 的 相同 对象的方式!
例如,一个食谱实体的主键是它的id
字段。这意味着,路由器可以使用特定食谱的id
从多个子图收集其数据。
我们使用@key
指令,以及一个名为fields
的属性来设置我们想要用作实体主键的字段。
该@key
指令需要一个名为fields
的属性,我们将其设置为我们想要用作实体主键的字段。
type EntityType @key(fields: "id") {}
在 Hot Chocolate 中,这由[Key]
属性表示。
定义引用解析函数
每个对实体贡献字段的子图也需要为该实体定义一个特殊的解析器函数,称为引用解析器。引用解析器负责返回实体的特定实例。
为了在 Hot Chocolate 中定义引用解析器函数,我们使用属性[ReferenceResolver]
。
为了帮助返回实体的实例,引用解析器将能够访问所谓的实体表示。
什么是实体表示?
一个实体表示是路由器用来识别实体的特定实例的对象。表示始终包含该类型名对于该实体以及特定实例的@key
字段。
__typename
字段:此字段自动存在于所有GraphQL类型上。它始终以字符串形式返回其包含类型的名称。例如,Recipe.__typename
返回“Recipe”。@key
字段:键值对,子图可以使用它来识别实体的实例。例如,如果我们使用“id”字段定义Recipe
实体作为主键,那么我们的实体表示将包含一个“id”属性,其值为“rec3j49yFpY2uRNM1”。
食谱的实体表示可能如下所示:
{"__typename": "Recipe","id": "rec3j49yFpY2uRNM1"}
您可以将实体表示视为路由器需要将来自多个子图的数据关联起来,并确保每个子图都在讨论同一个对象的最小基本信息。
路由器如何使用实体和查询计划解析数据
让我们以我们梦寐以求的简化版本为例查询。
query GetRecipeWithPlaylists {randomRecipe {namedescriptionrecommendedPlaylists {name}}}
客户端将此操作发送到路由器。
步骤 1:构建查询计划
该路由器首先构建一个查询计划,指示要向哪些子图发送哪些请求。
该路由器从传入查询的顶级字段开始,即randomRecipe
。在超级图模式的帮助下,该路由器看到randomRecipe
是在recipes
子图中定义的。
因此,该路由器使用对recipes
子图的请求开始查询计划。
该路由器继续执行此操作,针对超级图模式检查查询中的每个字段,并将它们添加到查询计划中。该description
字段也属于recipes
子图。
但是当该路由器到达特定Recipe
的recommendedPlaylists
字段时,它从超级图模式中看到Recipe.recommendedPlaylists
只能由soundtracks
子图解析(因为`Recipe.recommendedPlaylists 字段是在那里定义的)。
这意味着该路由器将不得不连接来自不同子图的数据。
为此,该路由器需要从recipes
子图中获取更多信息:该实体对Recipe
对象的表示。
请记住,实体 表示是 路由器 用于在 子图 之间跟踪特定对象的。要为 Recipe
对象创建实体表示,路由器 需要食谱的类型名称及其主键(在本例中为 id
字段)。
该 路由器 可以从 recipes
子图 中获取这两个 字段。
从那里,该 路由器 在其 查询计划 中添加另一个 操作,以请求每个播放列表的 name
从 soundtracks
子图 获取。
有了这个,查询 中的所有 字段 都已在 查询计划 中被考虑了。现在是时候进入下一步:执行该计划。
步骤 2:查询 recipes
子图
该 路由器 首先从 recipes
子图 请求数据。
该 recipes
子图 按正常方式解析所有请求的 字段,包括所有请求的 Recipe
对象的 实体 表示。
这个 子图 并不知道 路由器 计划对食谱的 id 或类型名称进行任何特殊操作。它只是像被要求的那样将数据发回给路由器。
有了这个,该 路由器 就完成了 查询计划 的第一步!下一步是检索 Playlist.name
字段 从 soundtracks
子图 获取。
步骤 3:查询 soundtracks
子图
请记住我们在启用联合时出现在我们的 _entities
字段 中的 子图?这就是它重新回到故事中的地方!
该 路由器 使用 _entities
字段 构建请求。
该 字段 接受一个名为 representations
的 参数,它接受一个 实体 表示列表!这就是 路由器 从 recipes
子图 收到的实体表示将要放置的地方。
在同一个请求中,该 路由器 添加了 查询计划 中剩下的其他 字段(在本例中,每个播放列表的 name
)。
该 路由器 将此请求发送到 soundtracks
子图。
为了解析 _entities
字段,该 soundtracks
子图 使用其 引用解析器。请记住,这是一个特殊的 解析器 函数,用于返回该 子图 贡献的所有 实体 字段。
该 soundtracks
子图 查看每个引用对象的 __typename
值,以确定 哪个 实体 的引用 解析器 要使用。在本例中,因为类型名称是“食谱”,所以该 soundtracks
子图 知道要使用 Recipe
实体 的引用 解析器。
该 Recipe
引用 解析器 对 查询 中的每个 实体 表示运行一次。每次它使用实体表示的主键来返回相应的 Recipe
对象。
在该 soundtracks
子图 完成解析请求后,它将数据发回给 路由器。
这就是执行阶段的全部内容!
步骤 4:将最终响应发送给客户端
现在,该 路由器 将从 recipes
和 soundtracks
子图 收到的所有数据组合成一个 JSON 对象。最后,该 路由器 将最终对象发回给客户端。
练习
关键要点
- 一个 实体 是一个可以跨多个 子图 解析其 字段 的类型。
- 要创建一个 实体,我们可以使用
@key
指令 来指定哪些 字段 可以唯一标识该类型对象。 - 我们可以用两种方式使用实体
- 作为 字段 的返回类型(引用 实体)。
- 定义 字段 用于来自多个 子图 的 实体 (为实体做出贡献)。
- 任何 子图 为 实体 贡献 字段 都需要为该实体定义一个引用 解析器 函数。这个
__resolveReference
解析器 会在 路由器 需要从另一个 子图 中访问 实体 的 字段 时被调用。 - 一个 实体 表示是一个对象,路由器 使用它来表示实体的特定实例。它包括实体的类型和它的键 字段。
接下来
让我们把这个理论付诸实践!我们将在 soundtracks
子图 中为 Recipe
实体 贡献 字段。
分享你关于本课的问题和意见
本课程目前处于
您需要一个 GitHub 帐户才能在下面发帖。没有帐户吗? 在我们的 Odyssey 论坛发帖。