
接上篇 —— Apollo 入门引导(一):构建 schema —— 继续翻译 Apollo 的官网入门引导。
从多个数据源获取数据。
Apollo 入门引导 - 目录:
- 介绍
- 构建 schema
- 连接数据源
- 编写查询解析器
- 编写变更解析器
- 连接 Apollo Studio
- 创建 Apollo 客户端
- 通过查询获取数据
- 通过变更修改数据
- 管理本地状态
完成时间:10 分钟
现在已经构建了 schema,接下来需要将数据源连接到 Apollo 服务。数据源是存储用于填充 schema 字段数据的任何数据库、服务或 API。 GraphQL API 可以与几种数据源的任何组合进行交互。
Apollo 提供了一个 DataSource
类,可以扩展该类以处理特定类型数据源的交互逻辑。在本节中,将扩展 DataSource
以将 REST API 和 SQL 数据库都连接到 Apollo 服务。不用担心,无需熟悉这些任一技术中就可以跟随示例一起学习。
连接 REST API
先将SpaceX v2 REST API连接到服务。为此,将使用 apollo-datasource-rest
包中的 RESTDataSource
类。此类是 DataSource
的扩展,用于从 REST API 获取数据。要使用该类,可以对其进行“继承(extend
)”,并为其提供与之通信的 REST API 的基础 URL。
Space-X API 的基础 URL 为 https://api.spacexdata.com/v2/
。通过将以下代码添加到 src/datasources/launch.js
中来创建一个名为 LaunchAPI
的数据源:
const { RESTDataSource } = require('apollo-datasource-rest');
class LaunchAPI extends RESTDataSource {
constructor() {
super();
this.baseURL = 'https://api.spacexdata.com/v2/';
}
}
module.exports = LaunchAPI;
RESTDataSource
类无需其他设置即可自动缓存来自 REST 资源的响应。我们称此功能为“局部查询缓存(partial query caching)”。它使你能够利用 REST API 已经公开的缓存逻辑。
要了解有关使用 Apollo 数据源进行局部查询缓存的更多信息,请查看此博客文章。
编写数据获取方法
LaunchAPI
数据源需要能够获取数据的方法,这些数据是接下来的查询将请求的数据。
getAllLaunches
方法
根据 schema,需要一种方法来获取所有 SpaceX 发射的列表。在 LaunchAPI
类中添加 getAllLaunches
方法:
async getAllLaunches() {
const response = await this.get('launches');
return Array.isArray(response)
? response.map(launch => this.launchReducer(launch))
: [];
}
RESTDataSource
类提供与 HTTP 动词相对应的辅助方法,例如 GET
和 POST
。在上面的代码中:
- 调用
this.get('launches')
向https://api.spacexdata.com/v2/launches
发送了一个GET
请求,并将返回的launch
数组存在response
中。 - 使用
this.launchReducer
(将在下面编写)将每个返回的launch
转换为 schema 所期望的格式。如果没有launch
,则返回一个空数组。
接下来需要编写 launchReducer
方法,该方法将返回的 launch
数据转换为 schema 期望的格式。这种方法使 schema 的结构与填充字段的数据源的结构脱钩。
首先,回想一下 Launch
对象类型在 schema 中的样子:
# YOU DON'T NEED TO COPY THIS CODE.
type Launch {
id: ID!
site: String
mission: Mission
rocket: Rocket
isBooked: Boolean!
}
现在编写一个 launchReducer
方法,该方法将 launch
数据从 REST API 转换为上面的格式。将以下代码复制到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,
},
};
}
因为对 Launch
的定义可能会随着时间的推移而变化,所以使用这样的 reducer
可以使 getAllLaunches
方法保持简洁。它还有助于测试 LaunchAPI
类,这将在后面介绍。
getLaunchById
方法
schema 还支持通过 ID 提取单次发射的信息。为了支持这一点,将 2 个方法添加到 LaunchAPI
类中:getLaunchById
和 getLaunchesByIds
:
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
方法接收发射的航班号参数,并返回相关发射的数据。getLaunchesByIds
方法将多次调用 getLaunchById
,并将结果拼成数组后返回。
LaunchAPI
类已完成!接下来,将数据库连接到服务。
连接数据库
SpaceX API 是用于获取发射数据的只读数据源。所以还需要一个 可写(writable) 数据源,该数据源允许存储应用程序数据,例如用户身份和预订的座位。为此,将连接到 SQLite 数据库并用 Sequelize 来操作 ORM。package.json
文件包含了这些依赖关系,因此它们已经通过在 npm install
中一起被安装了。
对于理解 Apollo 数据源来说,由于本节包含的 SQL 特定代码不是必需的,因此 UserAPI
的数据源已经提前写在了 src/datasources/user.js
。导航到该文件,以便我们介绍更高级概念。
构建自定义数据源
目前,Apollo 还没有为 SQL 数据库提供规范的 DataSource
子类(如果你有兴趣提供帮助,我们很乐意为你提供指导)。因此,本节通过继承通用的 DataSource
类为 SQLite 数据库创建自定义数据源。
以下是 src/datasources/user.js
中演示的 DataSource 子类的核心概念:
initialize
方法:如果要将任何配置选项传递给子类,必须实现此方法。UserAPI
类使用initialize
来访问我们的 API 的上下文(context)
。this.context
:graph API 的上下文是在 GraphQL 请求中的每个 解析器(resolver) 之间共享的对象。在下一节中将详细介绍解析器。现在,只需要知道上下文对于存储和共享用户信息很有帮助。- 缓存:尽管
RESTDataSource
类提供了内置的缓存,但是通用的DataSource
类却 没有 提供。可以使用cache primitives构建自己的缓存功能。
让我们看一下 src/datasources/user.js
中的一些方法,这些方法用于获取和更新数据库中的数据。将在下一部分中引用这些内容:
findOrCreateUser({email})
:在数据库中查找或创建具有给定email
的用户。bookTrips({launchIds})
:接收一个带有launchIds
数组的对象参数,并给已登录的用户预定它们。cancelTrip({launchId})
:接收一个带有launchId
的对象参数,并取消登录用户的发射预定。getLaunchIdsByUser()
:返回已登录用户的所有预订行程。isBookedOnLaunch({launchId})
:确定登录用户是否已预订特定发射的行程。
将数据源添加到 Apollo Server
现在已经建立了两个数据源,需要将它们添加到 Apollo 服务中。
将 dataSources
选项传给 ApolloServer
构造函数。此选项是一个函数,返回包含新的实例化后的数据源对象。
导航到 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(({ url }) => {});
首先,导入并调用 createStore
函数启动 SQLite 数据库。然后将 dataSources
函数添加到 ApolloServer
构造函数中,以将 LaunchAPI
和 UserAPI
的实例连接到 graph。并确保将数据库传递给了 UserAPI
构造函数。
如果在数据源中使用 this.context
,则需要注意,在 dataSources
函数中创建 新(new) 的实例而不能共享单个实例。否则,在为特定用户执行异步代码时可能会调用 initialize
,将 this.context
替换为了 其他 用户的上下文。
现在已经将数据源连接到 Apollo 服务,是时候进入下一部分,学习如何从解析器内部与数据源进行交互。
前端记事本,不定期更新,欢迎关注!
- 微信公众号: 林景宜的记事本
- 博客:林景宜的记事本
- 掘金专栏:林景宜的记事本
- 知乎专栏: 林景宜的记事本
- Github: MageeLin