3. 连接数据源
10m

从多个位置获取数据

既然我们已经构建好了架构,我们现在需要 数据源是可以用来填充模式字段的任何数据库、服务或 API。您的 API 可与任意组合的 交互。

Apollo 提供了一个DataSource我们可以扩展此类来处理特定类型 的交互逻辑.在此部分中,我们将扩展 DataSource以将 REST API 和 SQL 数据库连接至 .不用担心,你无需了解这两项技术就能理解这些示例。

连接 REST API

让我们将SpaceX v2 REST API连接到我们的服务器.为此,我们将使用 apollo-datasource-rest包中的 RESTDataSource类.此类是 DataSource的扩展,它处理从 REST API 提取数据.要使用此类,你 扩展它,并向其提供将与之进行通信的 REST API 的基本网址。

Space-X API 的基本网址是 https://api.spacexdata.com/v2/. 让我们通过将以下代码添加到 server/src/datasources/launch.js创建一个名为 LaunchAPI :

server/src/datasources/launch.js
const { RESTDataSource } = require("apollo-datasource-rest");
class LaunchAPI extends RESTDataSource {
constructor() {
super();
this.baseURL = "https://api.spacexdata.com/v2/";
}
}
module.exports = LaunchAPI;

RESTDataSource类会自动缓存 REST 资源的响应,且无需其他设置. 我们将此特性称之为 部分查询缓存. 它使你能够利用 REST API 已经公开的缓存逻辑。

要详细了解 在 Apollo 中与 一起使用的信息,请查看此 博客文章

编写数据提取方法

我们 LaunchAPI 需要可以使用户因查询传入来获取数据的各种方法。

getAllLaunches 方法

根据我们的架构,我们需要一个方法来获取所有 SpaceX 。让我们在我们的 LaunchAPI 类中添加一个 getAllLaunches 方法

server/src/datasources/launch.js
// class LaunchAPI... {
async getAllLaunches() {
const response = await this.get('launches');
return Array.isArray(response)
? response.map(launch => this.launchReducer(launch))
: [];
}

RESTDataSource 类提供了对应于 HTTP 动词(如 GETPOST)的助手方法。在上面的代码中:

  1. this.get('launches') 的调用会将 GET 请求发送到 https://api.spacexdata.com/v2/launches,并将返回的发射信息存储在 response 中。
  2. 我们使用 this.launchReducer(我们会在接下来撰写)将每个返回的发射信息 转换为我们的架构所期望的格式。如果不存在任何发射信息,则返回一个空数组。

现在我们需要撰写 launchReducer 方法,它将返回的发射信息 数据转换为我们的架构所期望的形状。这种方法将架构的结构与填充其 的各种 的结构分隔开。

首先让我们回想一下一个 Launch 在我们的架构中是什么样子的:

type Launch {
id: ID!
site: String
mission: Mission
rocket: Rocket
isBooked: Boolean!
}

现在,让我们编写一个 launchReducer 方法,将 REST API 中的 数据转换为此模式定义的形状。将以下代码复制到您的 LaunchAPI 类中

server/src/datasources/launch.js
// class LaunchAPI... {
launchReducer(launch) {
return {
id: launch.flight_number || 0,
cursor: `${launch.launch_date_unix}`,
site: launch.launch_site && launch.launch_site.site_name,
mission: {
name: launch.mission_name,
missionPatchSmall: launch.links.mission_patch_small,
missionPatchLarge: launch.links.mission_patch,
},
rocket: {
id: launch.rocket.rocket_id,
name: launch.rocket.rocket_name,
type: launch.rocket.rocket_type,
},
};
}

请注意, launchReducer 不会为模式中的 isBooked 设置值。这是因为 Space-X API 不知道用户预订了哪次行程!该字段将由我们 另一个 填充,该数据源连接到一个 SQLite 数据库。

使用这样的一个缩减器使 getAllLaunches 方法可以保持简洁,因为我们对 Launch 的定义可能随着时间的推移而不断变化和增长。这还有助于测试 LaunchAPI 类,我们将在后面介绍。

getLaunchById 方法

我们的模式还支持按 ID 获取 。为支持这一功能,让我们在 LaunchAPI 类中添加 两个 方法: getLaunchByIdgetLaunchesByIds

server/src/datasources/launch.js
// class LaunchAPI... {
async getLaunchById({ launchId }) {
const response = await this.get('launches', { flight_number: launchId });
return this.launchReducer(response[0]);
}
getLaunchesByIds({ launchIds }) {
return Promise.all(
launchIds.map(launchId => this.getLaunchById({ launchId })),
);
}

getLaunchById 方法采用 的飞行编号并返回关联的 launch 数据。 getLaunchesByIds 方法返回对 getLaunchById 多次调用的结果。

任务!

我们的 LaunchAPI 类已经完成!接下来,让我们将数据库连接到我们的服务器。

连接数据库

SpaceX API 是一个只读 ,用于获取 数据。我们还需要一个 可写 ,允许我们存储应用程序数据,例如用户身份和座位预订。为实现此目的,我们将连接到一个 SQLite 数据库,然后使用 Sequelize 作为我们的 ORM。我们的 package.json 文件包括这些依赖项,因此它们已随我们的 npm install 调用一起安装在 构建架构中。

由于本节包含不必要了解 Apollo 的 SQL 特定代码,因此 UserAPI 已包含在 src/datasources/user.js中。导航到该文件,以便我们涵盖高级概念。

构建一个自定义数据源

目前,Apollo 没有为 SQL 数据库提供规范的 DataSource 子类。因此,我们通过扩展泛型 DataSource 类,为我们的 SQLite 数据库创建了一个自定义

以下 DataSource 子类的核心概念已在 src/datasources/user.js中演示:

  • initialize 方法:如果您想向子类传递任何配置选项,可实现此方法。 UserAPI 类使用 initialize 访问我们的 API 的 context
  • this.context形 API 的上下文是一个对象,此对象在 解析器中的每个 请求中共享。我们将在下一部分详细介绍 。现在,你只需知道上下文有助于储存和共享用户信息即可。
  • 缓存:虽然 RESTDataSource类提供内置缓存,但通用 DataSource不提供。如果你想为你的 添加缓存功能,可以 配置外部后端构建你自己的后端

我们来回顾一下 src/datasources/user.js中的一些方法,我们用这些方法来获取和更新数据库中的数据。你可以在下一部分中参考这些方法:

  • findOrCreateUser({ email }):在数据库中查找或创建具有给定 email的用户。
  • bookTrips({ launchIds }):获取包含 launchIds数组的对象,并使用此对象为已登录用户预订旅行。
  • cancelTrip({ launchId }):获取包含 launchId的对象,并取消已登录用户的
  • getLaunchIdsByUser():返回已登录用户的所有预订旅行。
  • isBookedOnLaunch({ launchId }):确定已登录用户是否已在特定 中预订旅行。

向 Apollo Server 中添加数据源

现在我们构建了两个 ,我们需要将其添加到

传递 dataSources 选项至 ApolloServer 构造函数。该选项是一个返回带有已实例化 的对象的函数。

导航至 server/src/index.js 并添加下面高亮的代码(或使用整个代码块替换整个文件):

server/src/index.js
const { ApolloServer } = require("apollo-server");
const typeDefs = require("./schema");
const { createStore } = require("./utils");
const LaunchAPI = require("./datasources/launch");
const UserAPI = require("./datasources/user");
const store = createStore();
const server = new ApolloServer({
typeDefs,
dataSources: () => ({
launchAPI: new LaunchAPI(),
userAPI: new UserAPI({ store }),
}),
});
server.listen().then(() => {
console.log(`
Server is running!
Listening on port 4000
Explore at https://studio.apollographql.com/sandbox
`);
});

首先,我们导入并调用 createStore 函数以设置 SQLite 数据库。然后,我们添加 dataSources 函数至 ApolloServer 构造函数,以连接 LaunchAPIUserAPI 的实例至我们的 。我们还要确保将数据库传递至 UserAPI 构造函数。

如果在数据源中使用 this.context,务必在 dataSources 函数中创建此数据源的 实例,而不是共享单个实例。否则, initialize 可能会在特定用户的异步代码执行期间被调用,从而使用 另一个 用户的上下文替换 this.context

任务!

现在我们已将 链接至 ,是时候进入下一部分,了解如何从 中与我们的数据源进行交互。

上一步