5. 使用数据加载器
10m

概述

我们的数据加载器已准备好批量处理我们的便利设施 ID,并处理对 REST 端点的请求。它已正式注册到我们的应用程序,但尚未在任何地方使用。

本课程将

  • 更新我们的数据提取器,以将责任委托给数据加载器

使用我们的数据加载器

我们已完成将数据加载器引入应用程序计划的前两个步骤。

  1. 我们创建了一个数据加载器类。
  2. 我们为我们的类提供了一个load方法。

现在,我们要更新Listing.amenities 的数据提取器方法。它不会直接调用我们的ListingService方法,而是将责任委托给数据加载器,以便收集我们的中所有房源的便利设施数据并提供这些数据。

步骤 3 - 更新我们的数据提取器

回到datafetchers/ListingDataFetcher,我们要进行一些更改。

让我们从我们将需要的一些额外导入开始。

datafetchers/ListingDataFetcher
import org.dataloader.DataLoader;
import java.util.concurrent.CompletableFuture;

在我们的方法中,我们删除了调用 ListingServiceamenitiesRequest这一行。

@DgsData(parentType = "Listing")
public List<Amenity> amenities(DgsDataFetchingEnvironment dfe) throws IOException {
ListingModel listing = dfe.getSource();
String id = listing.getId();
Map<String, Boolean> localContext = dfe.getLocalContext();
if (localContext.get("hasAmenityData")) {
return listing.getAmenities();
}
- return listingService.amenitiesRequest(id);
}

现在,我们将更新 our datafetcher 以改为调用 data loader。我们可以在我们的应用程序中访问任何已注册的数据加载器(任何应用了 @DgsDataLoader 注解的类)通过调用 dfe.getDataLoader() 方法,并提供一个名称。

if (localContext.get("hasAmenityData")) {
return listing.getAmenities();
}
dfe.getDataLoader("amenities");

这样会返回 DataLoader 类型有两个类型 :第一个描述传递到 data loader 中的标识符的数据类型。第二个描述为每个标识符返回的数据类型。我们正在传入 String 类型的我们的 listing ID,对于每个标识符,我们希望获得 ListAmenity 类型。

DataLoader<String, List<Amenity>> amenityDataLoader = dfe.getDataLoader("amenities");

我们现在我们可以调用我们 amenityDataLoaderload 方法,传入我们要解析便利设施数据的 listing 的 id

DataLoader<String, List<Amenity>> amenityDataLoader = dfe.getDataLoader("amenities");
return amenityDataLoader.load(id);

我们很快就会在已添加的新行上看到一条红色的波浪线。我们的 IDE 抱怨,因为我们方法的返回类型是 List<Amenity>,但我们的 data loader 的 load 方法返回的类型是 CompletableFuture<List<Amenity>>

我们不能简单地将该方法的返回类型更新为 CompletableFuture<List<Amenity>>,因为如果该数据已可用,我们的方法有可能返回 List<Amenity> 类型。为了缓解这种不匹配,我们改为将我们的方法返回值类型更新为 Object

@DgsData(parentType = "Listing") {1}
public Object amenities(DgsDataFetchingEnvironment dfe) throws IOException {
ListingModel listing = dfe.getSource();
String id = listing.getId();
Map<String, Boolean> localContext = dfe.getLocalContext();
if (localContext.get("hasAmenityData")) {
return listing.getAmenities();
}
DataLoader<String, List<Amenity>> amenityDataLoader = dfe.getDataLoader("amenities");
return amenityDataLoader.load(id);
}

退一步,我们可以看到这个 Listing.amenities datafetcher 的新流程。我们仍然有两条路径:

  • 如果我们已经可以访问设施数据 (localContext.get("hasAmenityData")),我们可以简单地返回我们的 Listing 实例中的 amenities 属性。
  • 否则,我们需要提出后续请求获取设施数据,我们的 datafetcher 将信息列表 id 传递给数据加载器,以将其批量分组在一个大型请求中,用于查询中的 所有 信息列表的设施。

以前,我们的 datafetcher 在收到每个 listingId 后,在新的请求中调用我们的 。现在,它将每个信息的 id 传递到我们的数据加载器类,该类负责将收到的 所有 id 分组在一个请求中。

这可以让数据取用器继续实现其常规责任,即被调用来处理 Listing.amenities 的每个实例,它能够帮助解决此问题,而无需用多项网络调用来损害性能。当需要在网络上发起调用时,它会将每个 ID 传递到数据加载器,而 DGS 会负责处理剩余的事务!

运行查询

我们已经做了很多修改,因此让我们停止运行的服务器并重新它。

按下播放按钮,或运行以下命令。

./gradlew bootRun

让我们跳回 沙盒并确保我们的运行服务器已连接。

https://studio.apollographql.com/sandbox/explorer

A screenshot of the Apollo Sandbox Explorer, highlighting the connection input with the locally running server's address

我们将运行与之前相同的查询,将目光放在应用程序终端上,以便能够通过已记录的消息来监视已发出的请求数量。

针对特色房源及其设施的查询
query GetFeaturedListingsAmenities {
featuredListings {
id
title
amenities {
id
name
category
}
}
}

当我们运行 时,应当会看到返回的房源及其设施列表。

并且我们仅在终端中看到了输出的一行内容:

终端输出
Calling the /amenities/listings endpoint with listings [listing-1, listing-2, listing-3]

我们的房源 ID 已成功在单次请求中得到批量处理!👏👏👏

练习

下列哪项陈述描述了数据加载器如何配合数据取用器工作?

要点

  • 实际上,要使用数据加载器,我们需要在数据取用器方法中调用其 load 方法。
  • 每一个数据取用器方法均可利用 DgsDataFetchingEnvironment 参数,该参数包括 getDataLoader 方法。
  • DGS 内置数据加载器具有缓存优势,并会对它们请求的键进行重复数据删除。

恭喜!

完成这些之后,你就大功告成了!你已解决 n+1 问题,拥有工具集中的数据加载器,你就有资源可以提高数据获取器的效率。我们在应用程序中采用了基本的数据获取策略,并将其变为可以随着我们的查询和功能扩展的内容。通过使用数据加载器,我们了解到如何通过网络进行更少、更高效的请求,从而以前所未有的速度交付数据。

接下来:学习如何在 API。我们将介绍构建联邦 的最佳实践,以及 工具,你将使用这些工具让通往稳健企业 API 的道路变得平稳、可观察和高性能!

感谢你参加本课程,我们迫不及待地在下一堂课中见到你。

上一步

分享你对这一课的疑问和评论

本课程当前为

beta
.你的反馈有助于我们改进!如果你遇到了困难或感到困惑,请告知我们,我们会为你提供帮助。所有评论都是公开的,并且必须遵循 Apollo 行为准则。请注意,已解决或已处理的评论可能会被删除。

你需要一个 GitHub 帐户才能进行以下操作。没有该帐户? 改为在我们的 Odyssey 论坛中发帖。