从多个位置获取数据
既然我们已经构建好了架构,我们现在需要 将数据源连接 至 Apollo Server。 数据源是可以用来填充模式字段的任何数据库、服务或 API。您的 GraphQL API 可与任意组合的 数据源 交互。
Apollo 提供了一个DataSource
我们可以扩展此类来处理特定类型 数据源的交互逻辑.在此部分中,我们将扩展 DataSource
以将 REST API 和 SQL 数据库连接至 Apollo Server.不用担心,你无需了解这两项技术就能理解这些示例。
连接 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
的 数据源 :
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
方法::
// class LaunchAPI... {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')
的调用会将GET
请求发送到https://api.spacexdata.com/v2/launches
,并将返回的发射信息存储在response
中。 - 我们使用
this.launchReducer
(我们会在接下来撰写)将每个返回的发射信息 launch 转换为我们的架构所期望的格式。如果不存在任何发射信息,则返回一个空数组。
现在我们需要撰写 launchReducer
方法,它将返回的发射信息 launch 数据转换为我们的架构所期望的形状。这种方法将架构的结构与填充其 字段 的各种 数据源 的结构分隔开。
首先让我们回想一下一个 Launch
对象类型 在我们的架构中是什么样子的:
type Launch {id: ID!site: Stringmission: Missionrocket: RocketisBooked: Boolean!}
现在,让我们编写一个 launchReducer
方法,将 REST API 中的 launch 数据转换为此模式定义的形状。将以下代码复制到您的 LaunchAPI
类中:
// 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 获取 launch。为支持这一功能,让我们在 LaunchAPI
类中添加 两个 方法: getLaunchById
和 getLaunchesByIds
:
// 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 的飞行编号并返回关联的 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
中演示:
我们来回顾一下 src/datasources/user.js
中的一些方法,我们用这些方法来获取和更新数据库中的数据。你可以在下一部分中参考这些方法:
findOrCreateUser({ email })
:在数据库中查找或创建具有给定email
的用户。bookTrips({ launchIds })
:获取包含launchIds
数组的对象,并使用此对象为已登录用户预订旅行。cancelTrip({ launchId })
:获取包含launchId
的对象,并取消已登录用户的 发射。getLaunchIdsByUser()
:返回已登录用户的所有预订旅行。isBookedOnLaunch({ launchId })
:确定已登录用户是否已在特定 发射中预订旅行。
向 Apollo Server 中添加数据源
现在我们构建了两个 数据源,我们需要将其添加到 Apollo Server。
传递 dataSources
选项至 ApolloServer
构造函数。该选项是一个返回带有已实例化 数据源 的对象的函数。
导航至 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 4000Explore at https://studio.apollographql.com/sandbox`);});
首先,我们导入并调用 createStore
函数以设置 SQLite 数据库。然后,我们添加 dataSources
函数至 ApolloServer
构造函数,以连接 LaunchAPI
和 UserAPI
的实例至我们的 图形。我们还要确保将数据库传递至 UserAPI
构造函数。
如果在数据源中使用 this.context
,务必在 dataSources
函数中创建此数据源的 新 实例,而不是共享单个实例。否则, initialize
可能会在特定用户的异步代码执行期间被调用,从而使用 另一个 用户的上下文替换 this.context
。
现在我们已将 数据源 链接至 Apollo Server,是时候进入下一部分,了解如何从 解析器 中与我们的数据源进行交互。