7. 列表 REST API
10m

概述

是时候深入了解一下,我们将在本课程中一直使用它。

在本节课中,我们将

  • 探索列表 REST API
  • 创建一个可以管理我们对不同 REST 端点的请求的类

探索真实数据

我们的数据获取器(或 )检索到的数据可以来自各种地方:数据库、第三方 API、webhook 等。这些被称为 的优势在于,您可以混合使用任意数量的数据源来创建一个 API,以满足您的客户端应用程序和图消费者需求。

在本课程的其余部分,我们将使用一个提供列表数据的 REST API。我们将在以下 URL 访问它。

我们的 REST API 数据源
https://rt-airlock-services-listing.herokuapp.com/

不幸的是,我们的 缺少文档,并且包含一些不再积极使用的 。我们将使用 来创建一个 API,使其对下游消费者更易访问且更直观。

我们的数据是如何结构化的?

我们需要回答的下一个问题是,我们的数据是如何在我们的 REST API 中 结构化的。这会影响我们如何检索和转换该数据以匹配我们模式中的

我们的目标是检索特色列表的数据,并且有一个专门用于此的端点GET /featured-listings。让我们通过打开一个新的浏览器标签页并导航到以下 URL 来访问 /featured-listings 响应。

GET /featured-listings 端点
https://rt-airlock-services-listing.herokuapp.com/featured-listings

页面加载后,我们可以检查返回的响应的形状。

此端点返回一个数组,其中包含三个具有多个不同属性的对象。

https://rt-airlock-services-listing.herokuapp.com/featured-listings

A screenshot of the featured listings endpoint response

注意: 看到一些杂乱的 JSON 吗?点击屏幕顶部的“漂亮打印”复选框!

让我们将这些属性与我们在 中定义的 Listing 类型进行比较:

type Listing {
id: ID!
title: String!
numOfBeds: Int
costPerNight: Float
closedForBookings: Boolean
}

响应数组中的每个对象都包含所有这些 ,以及一些我们现在不需要的其他属性,例如 hostIdlatitudelongitude 等!

响应中的第一个对象,突出显示与我们的模式匹配的字段
{
"id": "listing-1",
"title": "Cave campsite in snowy MoundiiX",
"description": "Enjoy this amazing cave campsite in snow MoundiiX, where you'll be one with the nature and wildlife in this wintery planet. All space survival amenities are available. We have complementary dehydrated wine upon your arrival. Check in between 34:00 and 72:00. The nearest village is 3AU away, so please plan accordingly. Recommended for extreme outdoor adventurers.",
"costPerNight": 120,
"hostId": "user-1",
"locationType": "CAMPSITE",
"numOfBeds": 2,
"photoThumbnail": "https://res.cloudinary.com/apollographql/image/upload/v1644350721/odyssey/federation-course2/illustrations/listings-01.png",
"isFeatured": true,
"latitude": 1023.4,
"longitude": -203.4,
"closedForBookings": false,
"amenities": [{"id": "am-2"} /* additional objects */]
},

响应包含我们不需要的 是可以的。我们的数据获取器以及生成的类将负责挑选出与 所要求的匹配数据属性。

设置我们的数据源

我们知道我们的数据在哪里,并且了解了它的结构。太棒了。现在,我们需要一种方法来请求它提供的所有内容!

我们应该从创建一个可以容纳所有特定于此列表服务的逻辑的文件开始,我们将称之为 ListingService,并将它存储在一个名为 datasources 的新包中,它将位于 datafetchersmodels 旁边。

📂 java
┣ 📂 com.example.listings
┃ ┣ 📂 datafetchers
┃ ┣ 📂 datasources
┃ ┃ ┃ ┣ 📄 ListingService
┃ ┣ 📂 models

首先,我们将给我们的类添加 @Component 注解,以便 Spring 框架了解如何扫描、识别和实例化我们的服务。

datasources/ListingService
package com.example.listings.datasources;
import org.springframework.stereotype.Component;
@Component
public class ListingService {
}

接下来,我们将给我们的类添加一个 LISTING_API_URL 属性来保存我们的 API 端点的 String 值:https://rt-airlock-services-listing.herokuapp.com

private static final String LISTING_API_URL = "https://rt-airlock-services-listing.herokuapp.com";

我们将使用 Spring 的 RestClient 来向此端点发出请求,并且我们可以提前定义一些配置,因为这些配置在请求之间不会更改。

在文件顶部导入 RestClient,并给该类添加一个 client 属性。在这里,我们将构建我们的 RestClient 实例,并为它提供所有请求将使用的 baseUrl

package com.example.listings.datasources;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestClient;
@Component
public class ListingService {
private static final String LISTING_API_URL = "https://rt-airlock-services-listing.herokuapp.com";
private final RestClient client = RestClient.builder().baseUrl(LISTING_API_URL).build();
}

注意: client 使我们能够直接编写和发送到 REST API 的不同请求,而无需在每个请求中重复此代码。

/featured-listings 检索数据

接下来,我们可以给我们的 ListingService 添加一个专门用于检索特色列表数据的方法。以下是初始语法:

public void featuredListingsRequest() {
return client
.get()
.uri("/featured-listings")
.retrieve()
}

注意: 此方法的更简洁名称是 featuredListings。我们在这里选择较长的名称是为了帮助区分 方法与我们在 ListingDataFetcher 中定义的方法!虽然它们都将发挥作用,但它们是独立且不同的方法。

到目前为止,我们的 client 构建了一个 get 请求到 /featured-listings 端点。接下来,它链接了 retrieve 方法来实际获取数据。

但是我们还没有完全完成,让我们提醒自己此端点返回的数据的形状。

响应对象
[
{
"id": "listing-1",
"title": "Cave campsite in snowy MoundiiX",
"description": "Enjoy this amazing cave campsite in snow MoundiiX..."
// ...additional properties
}
// ...additional objects
]

为了将此 JSON 对象转换为我们可以实际使用的 Java 类,我们可以链接 .body() 方法。此方法接收我们希望 JSON 数据转换为的类的名称作为参数。

public void featuredListingsRequest() {
return client
.get()
.uri("/featured-listings")
.retrieve()
.body();
}

我们不能在这里使用 ListingModel 作为类,因为我们正在使用 JSON 数组 列表。我们需要能够访问 JSON 响应中的每个对象,使用其数据创建 ListingModel,并返回所有列表的 Java List

为了处理这个大型 JSON 对象,我们将使用 JsonNode 类,该类来自 Jackson 库。这将给我们一个初始容器来保存我们的数据,然后我们对其中的每个对象进行一些额外的操作。在文件的顶部导入 JsonNode

import com.fasterxml.jackson.databind.JsonNode;

然后,我们可以更新我们的方法以使用此类,并将 JsonNode.class 作为参数传递给 body。我们不会直接返回此调用的结果,而是会更新此方法以将它的结果捕获在一个名为 responseJsonNode 实例中。

JsonNode response = client
.get()
.uri("/featured-listings")
.retrieve()
.body(JsonNode.class);

太棒了,现在我们需要遍历 response,以便它的每个对象都被实例化为 ListingModel

从 JSON 对象到 Java 类

要将 JSON 数据转换为 Java 类,我们将使用 Jackson 的 ObjectMapper。该 ObjectMapper 包含专门用于将 JSON 反序列化为 Java 对象(或将对象序列化回 JSON!)的方法。由于 response 是一个 JSON 节点,我们可以使用我们创建的新映射器将它的每个对象映射到我们指定的任何类。

让我们在类文件的顶部导入它,以及我们将在稍后使用的其他一些导入。

datasources/ListingService
import com.fasterxml.jackson.databind.ObjectMapper;
import com.example.listings.models.ListingModel;
import com.fasterxml.jackson.core.type.TypeReference;
import java.util.List;
import java.io.IOException;

接下来,我们将实例化 ObjectMapper 作为我们 ListingService 类中的私有 ,名为 mapper

datasources/ListingService
public class ListingService {
// ... other properties
private final ObjectMapper mapper = new ObjectMapper();
}

回到我们的 featuredListingsRequest 方法中,让我们用以下代码更新它。

public List<ListingModel> featuredListingsRequest() {
JsonNode response = client
.get()
.uri("featured-listings")
.retrieve()
.body(JsonNode.class);
if (response != null) {
return mapper.readValue(response.traverse(), new TypeReference<List<ListingModel>>() {});
}
return null;
}

这段代码做了几件事

  1. 我们获取 REST API 的 response,首先检查它是否存在。
  2. 如果存在,我们调用我们 mapperreadValue
  3. 我们将 readValue 传递两个参数:JSON 节点(可以通过调用它的 traverse 方法读取)和映射器应该从数据中创建的最终对象。在本例中,我们想要一个 ListListingModel 实例。
  4. 然后,我们返回结果。在 if 块之外,如果我们的 responsenull,我们只需返回 null

调用 mapper.readValue 方法可能会抛出异常,所以让我们更新函数的签名:我们将给它一个 List<ListingModel> 的返回类型,并指示它可以抛出异常。

public List<ListingModel> featuredListingsRequest() throws IOException {
// ... method body
}

练习

以下关于数据源的说法哪些是正确的?

关键要点

  • 使用 ,我们可以访问任意数量的 来创建满足多个客户端需求的强大 API。
  • 将新的 融入我们的 API,首先要评估其响应的形状,并确定如何将其最佳地映射到我们架构的

下一步

我们准备将我们的 连接到我们的数据获取器方法,并 一些 实际 数据!

上一页

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

本课程目前处于

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

您需要一个 GitHub 帐户才能在下方发布。没有帐户? 请在我们的 Odyssey 论坛上发布。