4. 注册数据加载器
10m

概述

是时候向我们的应用程序中引入数据加载器了。

在本课程中,我们将

  • 实现并注册我们的数据加载器

添加数据加载器

我们在ListingService 类上引入了一种新方法,负责为 多个 房源信息抓取便利设施数据。但是,仅将我们的 Listing.amenities 数据提取器更新为使用这种方法还远远不够。

假设我们执行了类似以下操作,将 amenitiesRequest 的调用替换为 multipleAmenitiesRequest

datafetchers/ListingDataFetcher
@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);
+ return listingService.multipleAmenitiesRequest(id);
}

虽然此更改将利用我们的新端点,但它不足以实际提高我们的 性能。 Listing.amenities 数据提取器(为我们请求便利设施的每个房源信息调用)仍然会触发 针对 每个房源信息的单个网络请求。换句话说,我们仍然缺少一个可以在数据提取器执行 之间 为我们需要提取数据的键批量设置的逻辑。

REQUEST FOR LISTING-1 AMENITIES:
GET /amenities/listings?ids=listing-1
REQUEST FOR LISTING-2 AMENITIES:
GET /amenities/listings?ids=listing-2
...etc.

实际上,我们所做的将与之前相同 — 为每个列表调用一个 REST 端点。这不会提升性能!

我们的数据加载器类就在这里派上用场了。

循序渐进的加载器

下面是所有内容协同运行的方式。

  1. 我们创建一个数据加载器类,用 DGS @DgsDataLoader注释来表示它。
  2. 我们更新我们的类,以便它实现 BatchLoader接口。
  3. 我们给我们的类一个 load方法。此方法负责收集需要获取数据的所有必要键(那些将是 中每个列表的 ID)。
  4. 我们更新 Listing.amenities 的数据提取方法。它不会直接调用我们的 ListingService 方法,而是将责任委派给数据加载器,以收集数据的便利设施并为我们 中的 全部 列表提供便利设施数据。而且是一次全部提供!

我们将在本课中完成前两个步骤,然后在下一课中完成所有内容。让我们开始吧!

步骤 1 - 创建数据加载器类

我们通过进入我们的代码并创建一个新目录来开始 dataloaders,与 datafetchersdatasourcesmodels并列存在。

📂 com.example.listings
┣ 📂 datafetchers
┣ 📂 dataloaders
┣ 📂 datasources
┣ 📂 models
┣ 📄 ListingApplication
┗ 📄 WebConfiguration

dataloaders目录中,创建一个名为 AmenityDataLoader的类。

dataloaders/AmenityDataLoader.java
package com.example.listings.dataloaders;
public class AmenityDataLoader {
// TODO
}

添加 @DgsDataLoader 注释

为了在我们的应用程序中注册为数据加载器,我们将引入一些新导入。

dataloaders/AmenityDataLoader
import com.example.listings.datasources.ListingService;
import com.example.listings.generated.types.Amenity;
import com.netflix.graphql.dgs.DgsDataLoader;
import org.dataloader.BatchLoader;
import org.springframework.beans.factory.annotation.Autowired;
import java.io.IOException;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;

接下来,我们将使用 @DgsDataLoader 注释将我们的类标记为正式的数据加载器。我们将为它提供 name 属性,我们可以用该属性在其他地方标识此数据加载器。

dataloaders/AmenityDataLoader.java
@DgsDataLoader(name = "amenities")
public class AmenityDataLoader {
// TODO
}

BatchLoader 接口

为了充当真正的 data loader ,我们的类需要实现一个处理批量加载的接口。在此课程中,我们将使用 dataloader 库中的 BatchLoaderBatchLoader 是一个接受键列表并加载相应值实用工具。

BatchLoader 接口采用两个参数:

  1. 批处理加载器将收集的每个标识符的数据类型
  2. 批处理加载器预期为每个标识符返回的数据类型

我们继续更新我们的类以实现此接口。

对于此特定数据加载器, BatchLoader 会收集某些编号的 String 类型清单 ID;并返回 ListAmenity 类型(请记住,每个清单都可以有多种便利设施,这就是为什么我们为每个请求的清单获得返回 List<Amenity> 类型的原因!)

dataloaders/AmenityDataLoader.java
public class AmenityDataLoader implements BatchLoader<String, List<Amenity>> {
// TODO
}

步骤 2 - 添加 load 方法

接下来,我们将提供该类的 load 方法。此方法具有非常具体的签名。

load 方法签名
public CompletionStage<List<SomeJavaClass>> load(List<DataTypeOfIdentifiers> listOfIdentifiers) {
// logic to fetch data by identifiers
}

它需要接受 List 的键,并返回 CompletionStage 类型,该类型接受 List 的批处理加载程序解析为任何类。

让我们逐个应用这些方法,看看它们如何组合在一起。

首先,我们将编写我们的 load 方法并给它一个 List<String> 参数,称为 listingIds

dataloaders/AmenityDataLoader.java
public void load(List<String> listingIds) {
// TODO
}

接下来,让我们更新返回类型。为了符合 BatchLoader 接口,我们的方法需要返回 CompletionStage 类型,它接受一个类型

public CompletionStage<> load(List<String> listingIds) {
// TODO
}

注意:我们在处理异步操作时使用 CompletionStage 接口。我们稍后将使用 CompletableFuture,这是一个实现 CompletionStage 接口的类。

对于类型 ,我们将传递我们期望的 ListingServicemultipleAmenitiesRequest 方法返回的类型,即 ListList<Amenity> 类型(每个清单一个便利设施列表)!因此,我们可以更新我们的 load 方法的返回类型,以便 CompletionStage 接受 List<List<Amenity>> 类型

下面是它的样子。

public CompletionStage<List<List<Amenity>>> load(List<String> listingIds) {
// TODO
}

最后,我们需要调用我们的ListingService类方法multipleAmenitiesRequest,使用listingIds参数,以实际获取我们的便利设施数据。

但我们不能只在此处调用该方法,并返回结果。我们的load方法的签名指示它返回CompletionStage类型。

为了满足这一点,我们将使用CompletableFuture,这是一个实现CompletionStage接口的类,并调用它的一个方法: supplyAsync。此方法接受一个函数,我们可以在其中实际调用multipleAmenitiesRequest方法,传递listingIds

public CompletionStage<List<List<Amenity>>> load(List<String> listingIds) {
return CompletableFuture.supplyAsync(() -> listingService.multipleAmenitiesRequest(listingIds));
}

此调用可能会导致抛出异常,因此我们会在try/catch中包装我们的代码。

public CompletionStage<List<List<Amenity>>> load(List<String> listingIds) {
return CompletableFuture.supplyAsync(() -> {
try {
return listingService.multipleAmenitiesRequest(listingIds);
} catch (IOException e) {
throw new RuntimeException(e);
}
});
}

注意:确保在listingService.multipleAmenitiesRequest的调用中返回结果,且在try块中!

我们的AmenityDataLoader类已接近完成。为了让我们的load方法有效,我们需要应用@Override注释(这将覆盖BatchLoader界面的实现)。我们还需要为我们的数据加载器提供一个ListingService实例,以便它能正常工作。请注意,我们使用 Spring 的@Autowired注释以利用依赖项注入。这意味着在创建AmenityDataLoader类的实例时ListingService将自动注入。

@DgsDataLoader(name = "amenities")
public class AmenityDataLoader implements BatchLoader<String, Amenity> {
@Autowired
ListingService listingService;
@Override
public CompletionStage<List<List<Amenity>>> load(List<String> listingIds) {
return CompletableFuture.supplyAsync(() -> {
try {
return listingService.multipleAmenitiesRequest(listingIds);
} catch (IOException e) {
throw new RuntimeException(e);
}
});
}
}

这就是数据加载器完成其工作所需的一切!

练习

下列哪些内容是注册新的 DGS 数据加载器的必要条件?

要点

  • 要在我们的应用中注册数据加载器,我们需要完成三个步骤
    • 类需要应用@DgsDataLoader注释,并提供名称
    • 类需要实现支持批量加载的接口。在此课程中,我们使用了BatchLoader接口。
    • 类需要一个load函数,该函数遵循非常具体的签名

接下来

再完成最后一步,我们的数据加载器将为我们完成剩余的繁重工作。在下节课中,我们最终将在数据获取器中应用我们的数据加载器。

上一个

分享您关于本课程的问题和评论

此课程当前处于

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

您需要一个 GitHub 帐户才能在下面发帖。没有帐户吗? 改为在我们的 Odyssey 论坛中发帖。