概述
是时候深入了解一下数据源,我们将在本课程中一直使用它。
在本节课中,我们将
- 探索列表 REST API
- 创建一个可以管理我们对不同 REST 端点的请求的类
探索真实数据
我们的数据获取器(或 解析器)检索到的数据可以来自各种地方:数据库、第三方 API、webhook 等。这些被称为 数据源。GraphQL 的优势在于,您可以混合使用任意数量的数据源来创建一个 API,以满足您的客户端应用程序和图消费者需求。
在本课程的其余部分,我们将使用一个提供列表数据的 REST API。我们将在以下 URL 访问它。
https://rt-airlock-services-listing.herokuapp.com/
不幸的是,我们的 数据源 缺少文档,并且包含一些不再积极使用的 字段。我们将使用 GraphQL 来创建一个 API,使其对下游消费者更易访问且更直观。
我们的数据是如何结构化的?
我们需要回答的下一个问题是,我们的数据是如何在我们的 REST API 中 结构化的。这会影响我们如何检索和转换该数据以匹配我们模式中的 字段。
我们的目标是检索特色列表的数据,并且有一个专门用于此的端点GET /featured-listings
。让我们通过打开一个新的浏览器标签页并导航到以下 URL 来访问 /featured-listings
响应。
https://rt-airlock-services-listing.herokuapp.com/featured-listings
页面加载后,我们可以检查返回的响应的形状。
此端点返回一个数组,其中包含三个具有多个不同属性的对象。
注意: 看到一些杂乱的 JSON 吗?点击屏幕顶部的“漂亮打印”复选框!
让我们将这些属性与我们在 GraphQL 模式 中定义的 Listing
类型进行比较:
type Listing {id: ID!title: String!numOfBeds: IntcostPerNight: FloatclosedForBookings: Boolean}
响应数组中的每个对象都包含所有这些 字段,以及一些我们现在不需要的其他属性,例如 hostId
、latitude
和 longitude
等!
{"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
的新包中,它将位于 datafetchers
和 models
旁边。
📂 java┣ 📂 com.example.listings┃ ┣ 📂 datafetchers┃ ┣ 📂 datasources┃ ┃ ┃ ┣ 📄 ListingService┃ ┣ 📂 models
首先,我们将给我们的类添加 @Component
注解,以便 Spring 框架了解如何扫描、识别和实例化我们的服务。
package com.example.listings.datasources;import org.springframework.stereotype.Component;@Componentpublic 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;@Componentpublic 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
。我们不会直接返回此调用的结果,而是会更新此方法以将它的结果捕获在一个名为 response
的 JsonNode
实例中。
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 节点,我们可以使用我们创建的新映射器将它的每个对象映射到我们指定的任何类。
让我们在类文件的顶部导入它,以及我们将在稍后使用的其他一些导入。
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
。
public class ListingService {// ... other propertiesprivate 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;}
这段代码做了几件事
- 我们获取 REST API 的
response
,首先检查它是否存在。 - 如果存在,我们调用我们
mapper
的readValue
。 - 我们将
readValue
传递两个参数:JSON 节点(可以通过调用它的traverse
方法读取)和映射器应该从数据中创建的最终对象。在本例中,我们想要一个List
的ListingModel
实例。 - 然后,我们返回结果。在
if
块之外,如果我们的response
为null
,我们只需返回null
。
调用 mapper.readValue
方法可能会抛出异常,所以让我们更新函数的签名:我们将给它一个 List<ListingModel>
的返回类型,并指示它可以抛出异常。
public List<ListingModel> featuredListingsRequest() throws IOException {// ... method body}
练习
关键要点
- 使用 GraphQL,我们可以访问任意数量的 数据源 来创建满足多个客户端需求的强大 API。
- 将新的 数据源 融入我们的 GraphQL API,首先要评估其响应的形状,并确定如何将其最佳地映射到我们架构的 字段。
下一步
我们准备将我们的 数据源 连接到我们的数据获取器方法,并 查询 一些 实际 数据!
分享您关于本课的疑问和评论
本课程目前处于
您需要一个 GitHub 帐户才能在下方发布。没有帐户? 请在我们的 Odyssey 论坛上发布。