概述
虽然我们已经定义了Listing
实体 在我们的 reviews
子图 中,并贡献了 字段 到它,但我们还没有告诉它如何解析这些字段。
在本课中,我们将
- 创建参考 解析器
- 填充
reviews
和overallRating
字段 中的数据 - 了解
@DgsEntityFetcher
注释
参考解析器
当我们 查询 特定的列表及其评论数据时,我们需要一种方法来告诉 reviews
子图 哪个 列表是我们要谈论的。然后 reviews
子图 就会理解要提供哪些数据!
为了做到这一点, 路由器 会传递我们在上一课中讨论的 实体 表示: reviews
子图 将其用于了解要获取哪些列表数据的最小信息。
{"__typename": "Listing","id": "listing-3"}
但是,在 reviews
子图 中,这个 实体 表示实际上 去 哪里了呢?
这正是我们缺少的部分:参考 解析器。参考解析器是一个特殊的方法,它可以接收 实体 表示,从表示数据中创建一个新的 Listing
实例,并返回它。
让我们看看它在代码中是什么样子的,并逐步进行这个过程。
添加 resolveListingReference
让我们在 reviews
子图 中,在 com.example.reviews/datafetchers/ReviewsDataFetcher
下打开我们的数据获取器类。
首先,让我们在文件开头添加一些导入。我们很快就会用到它们。
import java.util.Map;import com.example.reviews.generated.types.Listing;
注意: 在你的 generated
文件夹中没有看到 Listing
类型吗?尝试重启服务器!
在类的主体中为我们的新方法留出一些空间。为了更清楚地说明它的作用,我们将它命名为 resolveListingReference
。(但你可以根据自己的需要给这个方法命名!)
public void resolveListingReference() {// TODO}
这个方法将接收 实体 表示,它看起来类似于以下代码片段:只有 __typename
和我们设置为 实体 主键的 字段, id
。
{__typename=Listing, id=listing-1}
让我们给我们的方法一个参数来保存这个表示。它被认为是 Map<String, Object>
类型,我们将它称为 entityRepresentation
。让我们打印出 entityRepresentation.toString()
这样我们就能看到这个方法接收了什么。
public void resolveListingReference(Map<String, Object> entityRepresentation) {System.out.println(entityRepresentation.toString());}
@DgsEntityFetcher
注释
为了让我们的 DGS 服务器知道 这个 方法是应该接收 实体 的方法,我们需要用一个特定的注释标记它: @DgsEntityFetcher
。
这个注释有一个名为 name
的属性,我们将用它来定义它解析的 实体 的名称: Listing
!
让我们将这个注释应用到我们的方法。
@DgsEntityFetcher(name = "Listing")public void resolveListingReference(Map<String, Object> entityRepresentation) {System.out.println(entityRepresentation.toString());}
现在很清楚,当 路由器 将一个 Listing
实体 表示传递到我们的 reviews
子图 中时, 这个 方法是我们将用来解析 哪个 列表需要提供数据的!
返回你的终端,重启 reviews
服务器。我们的 rover dev
进程应该还在端口 4000
上运行,所以让我们回到那里,再次尝试我们的 查询。
query GetListingAndReviews {listing(id: "listing-1") {titledescriptionnumOfBedsamenities {namecategory}overallRatingreviews {idtext}}}
当我们运行 查询 时,我们仍然不会得到任何与评论相关的数据;但是当我们检查 reviews
子图 的终端时,我们会看到我们的 实体 表示已经到达并被打印出来了!
{__typename=Listing, id=listing-1}
我们的 reviews
子图 正从 路由器 成功接收列表表示。现在,我们只需要确保我们的方法返回一个新的 Listing
实例,以便其他数据获取器方法可以访问它并附加数据。
删除打印语句,并用实例化一个新的 Listing
实例替换它。我们还可以将方法的返回值类型更新为 Listing
。
@DgsEntityFetcher(name = "Listing")public Listing resolveListingReference(Map<String, Object> entityRepresentation) {Listing listing = new Listing();}
接下来,让我们从我们的 "id"
值中提取 entityRepresentation
。我们可以使用以下行将其转换为 String
类型。
String id = (String) entityRepresentation.get("id");
最后,我们将它设置为 id
在我们的 listing
上,并返回它。
listing.setId(id);return listing;
很好!我们可以认为我们对列表的引用已经 "解析" 了。我们准备提供那些与评论相关的 字段 - reviews
和 overallRating
- 使用它们自己的数据获取器方法。
添加 Listing.reviews
让我们先处理 reviews
方法。
在 ReviewsDataFetcher
类中,我们定义一个名为 reviews
的新方法。这是一个常规的数据提取器,所以我们将使用 @DgsData
注解,并将我们的 parentType
设置为 Listing
。
@DgsData(parentType="Listing")public void reviews() {}
在我们的 GraphQL 模式 中,Listing.reviews
字段 返回 [Review!]!
类型。因此,我们将使用相应的 Java 返回类型 Flux<ReviewDto>
更新我们的方法。
public Flux<ReviewDto> reviews() {}
此方法应使用 id
从 实体 表示中查找并返回数据库中所有相关的评论。因为此数据提取器解析了实体类型上的 字段,所以我们知道链中的上一个数据提取器是我们刚刚定义的 resolveListingReference
方法。这意味着我们可以使用 DgsDataFetchingEnvironment
参数访问其返回值(Listing
实例)。
让我们将其作为名为 dfe
的参数添加到我们的方法中。
public Flux<ReviewDto> reviews(DgsDataFetchingEnvironment dfe) {}
我们可以在 dfe
上调用 getSource
方法来访问 Listing
实例;然后调用列表的 getId
方法来访问其 id
值。
public Flux<ReviewDto> reviews(DgsDataFetchingEnvironment dfe) {Listing listing = dfe.getSource();String id = listing.getId();}
有了列表 id
,我们就可以在数据库中查找与该列表关联的所有评论。我们使用的是已经实例化在类中的 ReviewController
,以访问我们内存中的 数据源。它为 reviewsForListing
提供映射,因此让我们在此处调用该方法并将我们的 id
传递给它。
return this.reviewController.reviewsForListing(id);
The overallRating
method
现在进入 overallRating
数据提取器方法!让我们使用 @DgsData
注解设置初始结构。
@DgsData(parentType="Listing")public void overallRating() {// TODO}
自己尝试一下,并查看 ReviewController
类以查找可用于检索此数据的有用方法。(提示:查看其返回类型,以获取有关我们的方法应返回内容的有用提示!)
准备就绪后,将您的代码与我们完成后的方法进行比较。
@DgsData(parentType="Listing")public Mono<Float> overallRating(DgsDataFetchingEnvironment dfe) {Listing listing = dfe.getSource();String id = listing.getId();return this.reviewController.averageRatingForListing(id);}
注意: 因为我们在响应式风格中编码,所以我们到目前为止一直使用 Flux
类型来描述我们返回的数据流。但是,由于我们只返回一个值——列表评级的平均值(以 Float
返回),我们可以改用 Mono
类型,并将类型 变量 传递给它 Float
。
运行我们的理想查询
是时候重新编译了!
在 rover dev
进程仍在运行的情况下,让我们在 https://127.0.0.1:4000
上尝试我们的 查询。
query GetListingAndReviews {listing(id: "listing-1") {titledescriptionnumOfBedsamenities {namecategory}overallRatingreviews {idtext}}}
提交查询,然后……我们有了数据!🎉 我们已将评论与列表关联起来,并使我们的理想查询成为现实!
查看查询计划
让我们看看 查询计划 来了解这些数据是如何组合在一起的。我们会看到,首先,路由器 将从 listings
中获取数据,然后使用这些数据构建它对 reviews
的请求。最后一步是将来自两个 子图 的响应扁平化为单个 Listing
类型,用于我们查询的 listing
字段!
仍然看到 reviews: null
?尝试重启你的 DGS 服务器!运行我们 路由器 的 rover dev
进程将在端口 4000
上自动刷新。
练习
关键要点
- 任何为 实体 贡献 字段 的 子图 都需要为该实体定义一个参考 解析器 方法。每当 路由器 需要在其他子图中访问实体的字段时,就会调用此方法。
- 在 DGS 中,我们使用
@DgsEntityFetcher
注释将方法标记为参考 解析器,并传入它所解析的类型的name
。此方法从 路由器 中接收 实体 表示。 - 一个 实体 表示是 路由器 用来表示实体的特定实例的对象。它包括实体的类型及其关键 字段。
接下来
太棒了!我们的 listings
和 reviews
服务现在正在合作同一个 Listing
实体。每个 子图 都贡献自己的 字段,而 路由器 启动 的本地 rover dev
进程为我们打包了响应。重点是“本地”这个词;要使我们的更改真正“生效”(至少在 教程 的意义上),我们需要告诉 GraphOS 关于它们!
在下一课中,我们将探讨如何使用模式检查和发布安全可靠地实施这些更改。
分享您关于本课的疑问和评论
本课程目前处于
您需要一个 GitHub 帐户才能在下方发帖。没有帐户吗? 请改为在 Odyssey 论坛中发帖。