加入我们,于10月8日至10日在纽约市了解有关 GraphQL 联邦和 API 平台工程的最新技巧、趋势和新闻。加入2024年纽约市的 GraphQL 峰会
文档
免费开始

获取数据

管理到数据库和其他数据源的连接


希望从 REST API 获取数据?查看从 REST 获取.

可以获取您所需的任何来源的数据,例如一个REST API或数据库。您的服务器可以使用任意数量的不同:

ApolloServer
Fetches data
Fetches data
Fetches data
Sends query
RESTDataSource
MongoDBSource
SQLDBSource
REST API
MongoDB Database
SQL Database
ApolloClient

因为服务器可以使用多种不同的数据源,所以保持您的解析器整洁变得更加重要

因此,我们建议为从特定来源获取数据创建单独的类,提供可以使用的方法来整洁地访问数据。您还可以自定义数据源类来帮助缓存、去重或解析数据源时处理错误。

创建数据源类

您的数据源类可以根据您的需求简单或复杂。您知道服务器需要什么数据,可以将这些数据作为您类中包含方法的指南。

以下是一个连接到存储预约的数据库的数据源类示例:

reservations.ts
export class ReservationsDataSource {
private dbConnection;
private token;
private user;
constructor(options: { token: string }) {
this.dbConnection = this.initializeDBConnection();
this.token = options.token;
}
async initializeDBConnection() {
// set up our database details, instantiate our connection,
// and return that database connection
return dbConnection;
}
async getUser() {
if (!this.user) {
// store the user, lookup by token
this.user = await this.dbConnection.User.findByToken(this.token);
}
return this.user;
}
async getReservation(reservationId) {
const user = await this.getUser();
if (user) {
return await this.dbConnection.Reservation.findByPk(reservationId);
} else {
// handle invalid user
}
}
//... more methods for finding and creating reservations
}
reservations.js
export class ReservationsDataSource {
constructor(options) {
this.dbConnection = this.initializeDBConnection();
this.token = options.token;
}
async initializeDBConnection() {
// set up our database details, instantiate our connection,
// and return that database connection
return dbConnection;
}
async getUser() {
if (!this.user) {
// store the user, lookup by token
this.user = await this.dbConnection.User.findByToken(this.token);
}
return this.user;
}
async getReservation(reservationId) {
const user = await this.getUser();
if (user) {
return await this.dbConnection.Reservation.findByPk(reservationId);
} else {
// handle invalid user
}
}
//... more methods for finding and creating reservations
}

批量处理和缓存

如果你想在数据源类中添加批量处理、去重或缓存,我们建议使用 DataLoader软件包。使用DataLoader这样的软件包特别有助于解决 著名的N+1查询问题

DataLoader提供了一个缓存机制,可以避免在单个 请求中多次加载相同的对象(类似于 RESTDataSource 的一个缓存层)。它还可以在单次事件循环中合并批量请求,一次性获取多个对象。

DataLoader实例是针对每个请求的,因此如果你在数据源中使用DataLoader,请确保 每次请求都使用该类创建一个新的实例

import DataLoader from 'dataloader';
class ProductsDataSource {
private dbConnection;
constructor(dbConnection) {
this.dbConnection = dbConnection;
}
private batchProducts = new DataLoader(async (ids) => {
const productList = await this.dbConnection.fetchAllKeys(ids);
// Dataloader expects you to return a list with the results ordered just like the list in the arguments were
// Since the database might return the results in a different order the following code sorts the results accordingly
const productIdToProductMap = productList.reduce((mapping, product) => {
mapping[product.id] = product;
return mapping;
}, {});
return ids.map((id) => productIdToProductMap[id]);
});
async getProductFor(id) {
return this.batchProducts.load(id);
}
}
// In your server file
// Set up our database, instantiate our connection,
// and return that database connection
const dbConnection = initializeDBConnection();
const { url } = await startStandaloneServer(server, {
context: async () => {
return {
dataSources: {
// Create a new instance of our data source for every request!
// (We pass in the database connection because we don't need
// a new connection for every request.)
productsDb: new ProductsDataSource(dbConnection),
},
};
},
});
import DataLoader from 'dataloader';
class ProductsDataSource {
constructor(dbConnection) {
this.dbConnection = dbConnection;
}
batchProducts = new DataLoader(async (ids) => {
const productList = await this.dbConnection.fetchAllKeys(ids);
// Dataloader expects you to return a list with the results ordered just like the list in the arguments were
// Since the database might return the results in a different order the following code sorts the results accordingly
const productIdToProductMap = productList.reduce((mapping, product) => {
mapping[product.id] = product;
return mapping;
}, {});
return ids.map((id) => productIdToProductMap[id]);
});
async getProductFor(id) {
return this.batchProducts.load(id);
}
}
// In your server file
// Set up our database, instantiate our connection,
// and return that database connection
const dbConnection = initializeDBConnection();
const { url } = await startStandaloneServer(server, {
context: async () => {
return {
dataSources: {
// Create a new instance of our data source for every request!
// (We pass in the database connection because we don't need
// a new connection for every request.)
productsDb: new ProductsDataSource(dbConnection),
},
};
},
});

将数据源添加到你的 context 函数中

注意

在下面的示例中,我们使用顶层的 await 调用以异步启动服务器。如果您想了解我们如何设置此功能,请查看 入门教程 中的详细说明。

您可以将 data sources 添加到服务器 context 初始化函数中,如下所示:

index.ts
interface ContextValue {
dataSources: {
dogsDB: DogsDataSource;
catsApi: CatsAPI;
};
token: string;
}
const server = new ApolloServer<ContextValue>({
typeDefs,
resolvers,
});
const { url } = await startStandaloneServer(server, {
context: async ({ req }) => {
const { cache } = server;
const token = req.headers.token;
return {
// We create new instances of our data sources with each request.
// We can pass in our server's cache, contextValue, or any other
// info our data sources require.
dataSources: {
dogsDB: new DogsDataSource({ cache, token }),
catsApi: new CatsAPI({ cache }),
},
token,
};
},
});
console.log(`🚀 Server ready at ${url}`);
index.js
const server = new ApolloServer({
typeDefs,
resolvers,
});
const { url } = await startStandaloneServer(server, {
context: async ({ req }) => {
const { cache } = server;
const token = req.headers.token;
return {
// We create new instances of our data sources with each request.
// We can pass in our server's cache, contextValue, or any other
// info our data sources require.
dataSources: {
dogsDB: new DogsDataSource({ cache, token }),
catsApi: new CatsAPI({ cache }),
},
token,
};
},
});
console.log(`🚀 Server ready at ${url}`);

Apollo Server在每次接收到操作时都会调用 初始化 context函数。这意味着:

  • 对于每个context返回一个包含您数据源类(在本例中是 DogsDataSourceCatsAPI)的对象的新实例。
  • 如果你的数据源是状态相关的(例如,使用一个内存缓存),则context函数应为每个操作创建一个新的数据源类实例。这可以确保你的数据源不会意外地在请求之间缓存结果。

然后,您的解析器可以访问共享的 contextValue对象中的数据源并使用它们来检索数据:

resolvers.ts
const resolvers = {
Query: {
dog: async (_, { id }, { dataSources }) => {
return dataSources.dogsDB.getDog(id);
},
popularDogs: async (_, __, { dataSources }) => {
return dataSources.dogsDB.getMostLikedDogs();
},
bigCats: async (_, __, { dataSources }) => {
return dataSources.catsApi.getCats({ size: 10 });
},
},
};

开源实现

Apollo Server 3包含名为DataSource的抽象类,您的每个数据源都可以从该抽象类派生。然后,您会使用特殊的 dataSources函数初始化每个 DataSource子类,这个函数会在幕后将您的数据源附加到 context上。

Apollo Server 4中,现在您可以在与请求其他设置相同的 context函数中创建数据源,完全避免了 DataSource超类。我们推荐为每个数据源创建一个自定义类,每个类最适合特定数据源。

现代数据源

Apollo为Apollo Server 4维护以下开源数据源:

示例用于与……结合使用
RESTDataSource查看获取RestHTTP/REST API

社区维护以下为Apollo Server 4的开源数据源:

用于与……结合使用
BatchedSQLDataSource社区SQL数据库(通过Knex.js)& 批量处理(通过DataLoader
FirestoreDataSource社区Cloud Firestore

旧数据源类

⚠️ 注意:社区为Apollo Server 3构建了以下每个数据源。如图所示,您仍然可以通过额外的设置在Apollo Server 4中使用这些包。如下,您仍然可以在Apollo Server 4中使用这些包,只需进行一些额外的设置。

以下数据源实现扩展了从已弃用的DataSource抽象类派生出的。DataSource类的子类定义了与特定存储或API通信所需的逻辑。

更大的社区维护以下开源实现

用于与……结合使用
HTTPDataSource社区HTTP/REST API
SQLDataSource社区SQL数据库(通过Knex.js
MongoDataSource社区MongoDB
CosmosDataSource社区Azure Cosmos DB

Apollo不会为社区维护的库提供官方支持。我们无法保证社区维护的库遵循最佳实践,或者它们将得到持续维护。

使用 DataSource 子类

在 Apollo Server 3 中,在构建每个 DataSource 子类之后,服务器会暗中调用每个新的 DataSourceinitialize({ cache, context }) 方法。

要在 Apollo Server 4 中重现此操作,您可以在每个 DataSource 子类的构造函数中手动调用 initialize 方法,如下所示:

import { ApolloServer } from '@apollo/server';
import { startStandaloneServer } from '@apollo/server/standalone';
import { KeyValueCache } from '@apollo/utils.keyvaluecache';
import { Pool } from 'undici';
import { HTTPDataSource } from 'apollo-datasource-http';
class MoviesAPI extends HTTPDataSource {
override baseURL = 'https://movies-api.example.com/';
constructor(options: { cache: KeyValueCache<string>; token: string }) {
// the necessary arguments for HTTPDataSource
const pool = new Pool(baseURL);
super(baseURL, { pool });
// We need to call the initialize method in our data source's
// constructor, passing in our cache and contextValue.
this.initialize({ cache: options.cache, context: options.token });
}
async getMovie(id: string): Promise<Movie> {
return this.get<Movie>(`movies/${encodeURIComponent(id)}`);
}
}
interface MyContext {
dataSources: {
moviesApi: MoviesAPI;
};
token?: string;
}
const server = new ApolloServer<MyContext>({
typeDefs,
resolvers,
});
const { url } = await startStandaloneServer(server, {
context: async ({ req }) => {
const { cache } = server;
const token = req.headers.token;
return {
dataSources: {
moviesApi: new MoviesAPI({ cache, token }),
},
token,
};
},
});
import { ApolloServer } from '@apollo/server';
import { startStandaloneServer } from '@apollo/server/standalone';
import { Pool } from 'undici';
import { HTTPDataSource } from 'apollo-datasource-http';
class MoviesAPI extends HTTPDataSource {
baseURL = 'https://movies-api.example.com/';
constructor(options) {
// the necessary arguments for HTTPDataSource
const pool = new Pool(baseURL);
super(baseURL, { pool });
// We need to call the initialize method in our data source's
// constructor, passing in our cache and contextValue.
this.initialize({ cache: options.cache, context: options.token });
}
async getMovie(id) {
return this.get(`movies/${encodeURIComponent(id)}`);
}
}
const server = new ApolloServer({
typeDefs,
resolvers,
});
const { url } = await startStandaloneServer(server, {
context: async ({ req }) => {
const { cache } = server;
const token = req.headers.token;
return {
dataSources: {
moviesApi: new MoviesAPI({ cache, token }),
},
token,
};
},
});
上一页
订阅
下一页
REST API
评分文章评分在 GitHub 上编辑编辑论坛Discord

©2024Apollo Graph Inc.,即 Apollo GraphQL。

隐私政策

公司