概述
为了为特定食谱推荐更好的(真实的)播放列表,soundtracks
子图 需要从食谱中获取更具体的数据。
在本课中,我们将
- 了解
@external
和@requires
联合特定的 指令s - 解析 字段 在
soundtracks
子图 中使用来自recipes
的数据
搜索完美的播放列表
The soundtracks
子图 使用 Spotify REST API 作为 数据源。我们有一个方便的端点,我们可以使用它来搜索与食谱相配的完美播放列表: the /search
端点。
The /search
端点接收几个参数,最重要的是:一个 查询 字符串以搜索,以及要返回的结果类型(专辑、艺术家、歌曲、播放列表)。我们已经知道我们想要播放列表,并且我们将把其余的参数保留为默认值。但是查询字符串呢?我们可以提供关于食谱的哪些搜索词或关键字?
The soundtracks
子图 已经可以访问食谱的唯一 id
,但我们不能仅仅根据一个 id
来进行有用的推荐!
食谱的 name
,然而,可能更有用。我们如何使用 that 来自 recipes
子图 的信息来确定我们从 soundtracks
返回什么?目前,该信息仅存在于 recipes
子图 中。
当 一个 子图 中的逻辑依赖于来自 另一个 的数据时,我们可以使用 @requires
和 @external
指令。让我们深入了解它们,看看我们如何将它们一起使用。
使用 @requires
和 @external
指令
The @requires
directive 用于模式中的 字段 上,以表明此特定字段依赖于 其他 字段 的值,这些值由 其他 子图 解决。这 directive 告诉 路由器 它需要首先获取那些外部定义的 字段 的值,即使原始 GraphQL 操作 没有请求它们。这些外部定义的字段需要包含在子图的模式中,并用 @external
directive 标记。
在我们的例子中,Recipe
实体 的 recommendedPlaylists
字段 需要 name
字段。
在 Hot Chocolate 中,我们将使用注释来表示这些 指令: [Requires]
和 [External]
。
让我们看看这些 directives 的实际操作。
打开
Recipe.cs
文件。我们将使用
RecommendedPlaylists
解析器 添加[Requires]
指令。该指令需要一个 参数:它所依赖的 字段。在本例中,该字段为name
字段。请注意,此 字段 需要以小写 n 开头,以匹配我们在 schema 中的显示方式。
Types/Recipe.cs[Requires("name")]public List<Playlist> RecommendedPlaylists()接下来,我们需要添加
Name
解析器。它将是一个简单的getter
属性,返回一个可为空的string
类型,以匹配recipes
子图 的返回值。请注意这里的 大写
N
。我们遵循 C# 约定。在后台,Hot Chocolate 将其转换为小写n
以匹配 GraphQL schema 约定。Types/Recipe.cspublic string? Name { get; }我们将使用
[External]
属性标记Name
解析器。这将使 路由器 知道 另一个 子图 负责提供此 字段 的数据。Types/Recipe.cs[External]public string? Name { get; }
查询计划的更改
重启服务器,让 rover dev
完成其 合成 操作,然后返回 Sandbox,地址为 https://127.0.0.1:4000。我们可以看到在查询计划中使用这两个 指令 的效果。
让我们再次运行该 查询:
query GetRecipeWithPlaylists {randomRecipe {namerecommendedPlaylists {idname}}}
然后,让我们以文本形式查看 查询计划。
QueryPlan {Sequence {Fetch(service: "recipes") {{randomRecipe {__typenameidname}}},Flatten(path: "recipe") {Fetch(service: "soundtracks") {{... on Recipe {__typenameidname}} =>{... on Recipe {recommendedPlaylists {idname}}}},},},}
与我们之前拥有的 查询计划(在我们引入 @requires
和 @external)
之前)相比,这里有一个细微的差别: 实体 表示(突出显示的行)现在包括 name
字段。有趣!
让我们对 查询 进行一个小小的调整,并注释掉食谱名称。
query GetRecipeWithPlaylists {randomRecipe {# namerecommendedPlaylists {idname}}}
运行 查询,再次查看 查询计划……查询计划保持不变!
这是因为解析 recommendedPlaylists
的过程 需要 将 name
字段 的值传递给它,即使原始的 GraphQL 操作 没有请求它。因此,路由器 总是先获取食谱的 name
数据,并确保将其传递给 soundtracks
子图。
访问必需字段
现在让我们对 name
做点什么!
回到我们的 Recipe.cs
文件,让我们找到参考 解析器。
[ReferenceResolver]public static Recipe GetRecipeById(string id){return new Recipe(id);}
我们将更新参数列表以包含 name
,并在构造函数调用中使用它。
public static Recipe GetRecipeById(string id,string? name) {return new Recipe(id, name);}
我们的构造函数也需要考虑 name
值!
public Recipe(string id, string? name){Id = id;if (name != null){Name = name;}}
调用 /search
端点
回到我们的
RecommendedPlaylists
解析器 函数参数,让我们添加SpotifyService
数据源。Types/Recipe.cspublic List<Playlist> RecommendedPlaylists(SpotifyService spotifyService)不要忘记在文件顶部导入
SpotifyService
包。Types/Recipe.csusing SpotifyWeb;我们还将更新函数签名,使其成为异步函数,并返回
Task
类型。Types/Recipe.cspublic async Task<List<Playlist>> RecommendedPlaylists(SpotifyService spotifyService)在函数主体内部,我们将使用
spotifyService.SearchAsync()
方法。提示:将鼠标悬停在该方法上,查看其签名,并点击查看
SpotifyService.cs
文件中的函数定义。Types/Recipe.csvar response = await spotifyService.SearchAsync(this.Name,new List<SearchType> { SearchType.Playlist },3,0,null);第一个参数是搜索词,即食谱名称。我们现在可以通过类访问它(如果你愿意,可以省略
this
)!第二个参数是
SearchType
枚举的列表:我们只在寻找Playlist
类型。然后,应返回的最大播放列表数量(limit
),以及如果我们正在使用分页,则应返回的offset
(0 是第一页)。最后,对于include_external
类型,使用null
,我们不需要担心它。请记住,
RecommendedPlaylists
解析器 函数应该返回List<Playlist>
类型。但SearchAsync
方法的response
返回的是SearchResults
类型,不匹配!我们需要深入了解一下才能获得我们想要的返回类型。看起来播放列表结果包含在
response.Playlists
属性中,该属性又包含一个Items
属性,播放列表 实际 位于该属性中。Types/Recipe.csvar items = response.Playlists.Items;items
现在是一个PlaylistSimplified
类型的集合,我们需要将其转换为Playlist
类型。幸运的是,我们有一个Playlist(PlaylistSimplified obj
) 构造函数可以做到这一点!因此,我们将使用Select
遍历items
集合,并在每个元素上调用new Playlist(item)
。Types/Recipe.csvar playlists = items.Select(item => new Playlist(item));最后,resolver 函数期望的是
List
类型,所以我们将在返回结果之前调用ToList()
方法。Types/Recipe.csreturn playlists.ToList();完美!您可以将这三行代码合并成 一行简洁的代码。
Types/Recipe.csreturn response.Playlists.Items.Select(item => new Playlist(item)).ToList();我们还将删除 硬编码的返回数组。
Types/Recipe.cs- return new List<Playlist>- {- new Playlist("1", "Grooving"),- new Playlist("2", "Graph Explorer Jams"),- new Playlist("3", "Interpretive GraphQL Dance")- };
查询梦寐以求的查询
一切都准备好了!重启服务器,并给 rover dev
一些时间来整理新的 supergraph schema,其中包含我们所有的更改。
在 Sandbox 中,让我们以完整、精彩的细节来体验一下梦寐以求的 query!
query GetRecipeWithPlaylists {randomRecipe {namedescriptioningredients {text}instructionsrecommendedPlaylists {idnamedescriptiontracks {explicitidnameuridurationMs}}}}
👏👏👏 食谱细节、说明、食材……以及完美的烹饪伴侣音乐播放列表。太棒了!
关键要点
-
@requires
directive 用于指示 schema 中的 field 依赖于由 其他 subgraph 解析的 其他 fields 的值。这个 directive 确保即使在原始 GraphQL operation 中没有明确请求,外部定义的 fields 也会优先被获取。在 Hot Chocolate 中,这个 directive 由[Requires]
属性表示。 -
@external
directive 用于标记 field 为外部定义,表示该字段的数据来自另一个 subgraph。在 Hot Chocolate 中,这个 directive 由[External]
属性表示。
下一步
我们对服务器进行了很多更改, rover dev
帮助我们在本地组成的 supergraph 中测试了所有内容。重点是 本地;要让我们的更改真正“上线”(至少在 教程 的意义上),我们需要告诉 GraphOS 关于这些更改!
在下一课中,我们将看看如何 使用 schema 检查 和 launches 安全可靠地将这些更改落地。
分享您对此课程的疑问和评论
此课程目前处于
您需要一个 GitHub 帐户才能在下方发布评论。还没有帐户吗? 请在我们的 Odyssey 论坛上发布评论。