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

使用 Apollo Server 实现网关

使用 Node.js 网关作为您的图谱路由器


在设置至少一个联邦就绪子图后,您可以为您的图谱路由器(也称为网关)进行配置,使其位于您的之前。

Graph router
Users
subgraph
Products
subgraph
Reviews
subgraph
Web app
iOS app

📣 在大多数情况下,我们推荐使用 GraphOS 路由器 作为您的图谱路由器。 它配置更快,性能更优(特别是在高请求负载下),而且很少需要编写自定义代码。

在某些情况下,如果您子图使用的是当前难以与 配置的自定义身份验证方法,那么您可能需要使用 作为您的图谱路由器。

无论您开始使用的是哪个图路,您都可以在不需要对其他部分的 进行任何更改的情况下切换到另一个。

Node.js 网关设置

本节将指导您如何使用 Apollo Server 和 @apollo/gateway 库设置基本的图谱路由器。它目前需要 Node.js 版本 14 或 16

使用 npm init 创建一个新的 Node.js 项目,然后安装必要的包:

npm install @apollo/gateway @apollo/server graphql

@apollo/gateway@apollo/gateway 包包含 ApolloGateway 类。要配置 Apollo Server 以充当图谱路由器,您需要将一个 ApolloGateway 实例传递给 ApolloServer 构造函数,例如:

index.ts
import { ApolloServer } from '@apollo/server';
import { startStandaloneServer } from '@apollo/server/standalone';
import { ApolloGateway } from '@apollo/gateway';
import { readFileSync } from 'fs';
const supergraphSdl = readFileSync('./supergraph.graphql').toString();
// Initialize an ApolloGateway instance and pass it
// the supergraph schema as a string
const gateway = new ApolloGateway({
supergraphSdl,
});
// Pass the ApolloGateway to the ApolloServer constructor
const server = new ApolloServer({
gateway,
});
// Note the top-level `await`!
const { url } = await startStandaloneServer(server);
console.log(`🚀 Server ready at ${url}`);
index.js
import { ApolloServer } from '@apollo/server';
import { startStandaloneServer } from '@apollo/server/standalone';
import { ApolloGateway } from '@apollo/gateway';
import { readFileSync } from 'fs';
const supergraphSdl = readFileSync('./supergraph.graphql').toString();
// Initialize an ApolloGateway instance and pass it
// the supergraph schema as a string
const gateway = new ApolloGateway({
supergraphSdl,
});
// Pass the ApolloGateway to the ApolloServer constructor
const server = new ApolloServer({
gateway,
});
// Note the top-level `await`!
const { url } = await startStandaloneServer(server);
console.log(`🚀 Server ready at ${url}`);

构建超级图模式

在上面的例子中,我们将supergraphSdl选项传递给ApolloGateway构造函数。这是我们的超级图模式的字符串表示形式,该模式由我们所有的子图模式组成。

要了解如何构建您的,请参阅支持的方法

在生产环境中,我们强烈建议使用Apollo Studio管理模式运行网关,这可以使您的网关无需重启即可更新其配置。有关详细信息,请参阅设置管理联邦

启动时,网关处理您的supergraphSdl,包括子图的路由信息。然后它开始接受传入请求并为它们创建,这些计划将跨一个或多个子图执行。

更新超级图模式

在上面的例子中,我们将一个静态超级图模式传递给网关。这种方法要求网关重启才能更新超级图模式。这对许多应用程序来说是不理想的,因此我们还提供了动态更新超级图模式的能力。

index.ts
import { ApolloServer } from '@apollo/server';
import { startStandaloneServer } from '@apollo/server/standalone';
import { ApolloGateway } from '@apollo/gateway';
import { readFile } from 'fs/promises';
let supergraphUpdate;
const gateway = new ApolloGateway({
async supergraphSdl({ update }) {
// `update` is a function that we'll save for later use
supergraphUpdate = update;
return {
supergraphSdl: await readFile('./supergraph.graphql', 'utf-8'),
};
},
});
// Pass the ApolloGateway to the ApolloServer constructor
const server = new ApolloServer({
gateway,
});
const { url } = await startStandaloneServer(server);
console.log(`🚀 Server ready at ${url}`);
index.js
import { ApolloServer } from '@apollo/server';
import { startStandaloneServer } from '@apollo/server/standalone';
import { ApolloGateway } from '@apollo/gateway';
import { readFile } from 'fs/promises';
let supergraphUpdate;
const gateway = new ApolloGateway({
async supergraphSdl({ update }) {
// `update` is a function that we'll save for later use
supergraphUpdate = update;
return {
supergraphSdl: await readFile('./supergraph.graphql', 'utf-8'),
};
},
});
// Pass the ApolloGateway to the ApolloServer constructor
const server = new ApolloServer({
gateway,
});
const { url } = await startStandaloneServer(server);
console.log(`🚀 Server ready at ${url}`);

这里发生了一些事情。让我们逐个查看每件事情。

请注意,现在supergraphSdl是一个async函数。该函数在ApolloServer初始化网关时恰好被调用一次。它有以下职责:

  • 它接收了 update 函数,我们用它来更新 supergraph 模式
  • 它返回初始化的 supergraph 模式,这是网关启动时使用的。

使用 update 函数,我们现在可以以编程方式更新 supergraph 模式。轮询、webhook 和文件监视器都是我们可以采用的方法。

下面的代码演示了使用文件监视器的更完整示例。在这个例子中,假设我们正在使用 Rover CLI(矢量 CLI) 更新 supergraphSdl.graphql 文件。

index.ts
import { ApolloServer } from '@apollo/server';
import { startStandaloneServer } from '@apollo/server/standalone';
import { ApolloGateway } from '@apollo/gateway';
import { watch } from 'fs';
import { readFile } from 'fs/promises';
const server = new ApolloServer({
gateway: new ApolloGateway({
async supergraphSdl({ update, healthCheck }) {
// create a file watcher
const watcher = watch('./supergraph.graphql');
// subscribe to file changes
watcher.on('change', async () => {
// update the supergraph schema
try {
const updatedSupergraph = await readFile('./supergraph.graphql', 'utf-8');
// optional health check update to ensure our services are responsive
await healthCheck(updatedSupergraph);
// update the supergraph schema
update(updatedSupergraph);
} catch (e) {
// handle errors that occur during health check or while updating the supergraph schema
console.error(e);
}
});
return {
supergraphSdl: await readFile('./supergraph.graphql', 'utf-8'),
// cleanup is called when the gateway is stopped
async cleanup() {
watcher.close();
},
};
},
}),
});
const { url } = await startStandaloneServer(server);
console.log(`🚀 Server ready at ${url}`);
index.js
import { ApolloServer } from '@apollo/server';
import { startStandaloneServer } from '@apollo/server/standalone';
import { ApolloGateway } from '@apollo/gateway';
import { watch } from 'fs';
import { readFile } from 'fs/promises';
const server = new ApolloServer({
gateway: new ApolloGateway({
async supergraphSdl({ update, healthCheck }) {
// create a file watcher
const watcher = watch('./supergraph.graphql');
// subscribe to file changes
watcher.on('change', async () => {
// update the supergraph schema
try {
const updatedSupergraph = await readFile('./supergraph.graphql', 'utf-8');
// optional health check update to ensure our services are responsive
await healthCheck(updatedSupergraph);
// update the supergraph schema
update(updatedSupergraph);
} catch (e) {
// handle errors that occur during health check or while updating the supergraph schema
console.error(e);
}
});
return {
supergraphSdl: await readFile('./supergraph.graphql', 'utf-8'),
// cleanup is called when the gateway is stopped
async cleanup() {
watcher.close();
},
};
},
}),
});
const { url } = await startStandaloneServer(server);
console.log(`🚀 Server ready at ${url}`);

这个例子更完整一些。让我们看看我们增加了哪些内容。

supergraphSdl 回调中,我们还接收了一个 healthCheck 函数。这使我们能够对未来的 supergraph 模式中的每个服务进行健康检查。这有助于确保我们的服务响应良好,并且我们在不安全时不会执行更新。

我们还使用了 try 块包装了我们的 updatehealthCheck 调用。如果在其中任何一个过程中发生错误,我们希望优雅地处理它。在这个例子中,我们继续运行现有的 supergraph 模式并记录错误。

最后,我们返回一个 cleanup 函数。这是一个在网关停止时被调用的回调。这使我们能够在通过 ApolloServer.stop 调用关闭网关时,干净地关闭任何正在运行的过程(如文件监视器或轮询)。网关期望 cleanup 返回一个 Promise,在关闭之前对其进行 await

高级用法

在一个更复杂的应用程序中,您可能希望创建一个处理 updatehealthCheck 函数以及任何其他状态的类。在这种情况下,您可以选择提供带有 initialize 函数的对象(或类)。此函数就像上面讨论的 supergraphSdl 函数那样被调用。有关此示例,请参阅 IntrospectAndCompose 源代码

使用 IntrospectAndCompose 组合子图

⚠️ 我们强烈反对在生产环境中使用 IntrospectAndCompose。有关详细信息,请参阅IntrospectAndCompose 的限制

您可以直接通过将IntrospectAndCompose类的实例与subgraphs数组提供来指示网关获取所有的subgraph模式并自己执行组合。要这样做,请提供如下所示的IntrospectAndCompose类实例:

index.ts
const { ApolloGateway, IntrospectAndCompose } = require('@apollo/gateway');
const gateway = new ApolloGateway({
supergraphSdl: new IntrospectAndCompose({
subgraphs: [
{ name: 'accounts', url: 'https://127.0.0.1:4001' },
{ name: 'products', url: 'https://127.0.0.1:4002' },
// ...additional subgraphs...
],
}),
});
index.js
const { ApolloGateway, IntrospectAndCompose } = require('@apollo/gateway');
const gateway = new ApolloGateway({
supergraphSdl: new IntrospectAndCompose({
subgraphs: [
{ name: 'accounts', url: 'https://127.0.0.1:4001' },
{ name: 'products', url: 'https://127.0.0.1:4002' },
// ...additional subgraphs...
],
}),
});

subgraphs数组中的每个项目都是一个对象,指定了您的的名称和url。您可以指定任何字符串作为name的值,它主要用输出、错误消息和日志。

启动时,网关将从其url获取每个的模式,并将这些模式组合成一个supergraph模式。然后,它开始接受传入的请求并为这些请求创建,在单个或多个上执行。

更多配置选项,请参阅IntrospectAndCompose API 文档

然而,IntrospectAndCompose有一些重要的限制

IntrospectAndCompose 限制

IntrospectAndCompose有时可能对本地开发有帮助,但对于任何其他环境都强烈反对。以下是一些原因:

  • 组合可能会失败。在使用IntrospectAndCompose的情况下,网关在启动时动态执行组合,这需要与每个进行网络通信。如果组合失败,您的网关将引发错误并经历非计划的停机时间。
    • 使用静态或动态 supergraphSdl 配置时,您需要提供一个已经成功组合的 supergraph schema。这样可以防止组合错误并加快启动速度。
  • 网关实例可能不同。如果在部署子图更新时部署多个您的网关实例,网关实例可能会从同一个子图获取不同的 schemas。这可能导致实例间的组合失败或不一致的 supergraph schemas
    • 当使用 supergraphSdl 部署多个实例时,您为每个实例提供了完全相同的静态工件,这可以使行为更加可预测。

更新网关

在更新网关版本之前,检查 变更日志以查找潜在的破坏性更改。

我们强烈建议在部署到预演或生产环境之前,在本地和测试环境中更新您的网关。

您可以使用 npm list 命令来确认 @apollo/gateway 库的当前安装版本:

npm list @apollo/gateway

要更新库,请使用 npm update 命令:

npm update @apollo/gateway

这将更新库到允许的最新版本。 了解更多关于依赖约束的信息。

要将版本更新到特定版本(包括超过你的依赖性约束的版本),请使用npm install代替:

npm install @apollo/[email protected]

自定义请求和响应

网关可以在执行跨你的子图执行之前修改入站请求的详细信息。例如,你的子图可能都使用相同的授权令牌将入站请求与特定用户相关联。网关可以将该令牌添加到它发送到子图的每个

同样,网关可以根据每个子图返回的结果修改其对客户端的响应的详细信息。

自定义请求

在以下示例中,每个到达网关的请求都包含一个Authorization头。网关通过读取该头并使用它来查找关联的用户ID,为操作设置共享的contextValue

在将userId添加到共享的contextValue对象之后,网关可以将该值添加到它包含在每个子图请求中的头中。

如果你使用不同的Apollo Server 集成,则传递给你的context函数的对象字段可能不同。

构造函数buildService使我们能够自定义发送到子图的请求。在此示例中,我们返回一个自定义的RemoteGraphQLDataSource。此数据源使我们能够在发送之前使用来自 Apollo Server 的contextValue信息修改出站请求。在此处,我们将user-id头传递给下游服务,以传递已验证的用户ID。

自定义响应

假设每当子图将其操作结果返回给网关时,它在响应中包括一个Server-Id头。该头的值独特地标识了我们的图中

然后当网关响应客户端时,我们希望它的 Server-Id头包含对响应有贡献的每个子图的标识符。在这种情况下,我们可以让网关将各种服务器ID聚合为单个、以逗号分隔的列表。

从客户端应用程序处理单个的流程如下:

SubgraphsGatewayClient appSubgraphsGatewayClient apploop[For each operation in the query plan]Sends GraphQL operationGenerates query plan for operationSends the operation to the applicable subgraphResponds with result and Server-Id headerAdds the returned Server-Id to the shared contextAdds all Server-Ids in the shared context to the response headerSends operation response

为了实现此流程,我们可以使用didReceiveResponse回调函数的RemoteGraphQLDataSource类来检查子图的结果,作为它们到达。我们可以在该回调中添加Server-Id到共享的context中,然后在向客户端发送最终响应时从context中获取完整的列表。

在此示例中,多次调用 didReceiveResponse 将值推送到共享的 contextValue.serverIds 数组。无法保证这些调用的顺序。如果您编写修改共享 contextValue 对象的逻辑,请确保修改不会造成破坏,并且修改的顺序不需要保证。

有关 buildServiceRemoteGraphQLDataSource 的更多信息,请参阅 API 文档

自定义指令支持

@apollo/gateway 库支持在您的子图模式中使用自定义 指令。这种支持根据给定的 是类型系统指令还是可执行指令而有所不同。

类型系统指令

指令是应用于以下 位置 中的指令。这些 directives 不会被用于操作内部,而是在架构本身的这些位置应用。

下面的 @deprecated 指令是一个类型系统指令的例子:

directive @deprecated(
reason: String = "No longer supported"
) on FIELD_DEFINITION | ENUM_VALUE
type ExampleType {
newField: String
oldField: String @deprecated(reason: "Use `newField`.")
}

网关将剥离所有定义使用类型系统指令从您的's API架构。这在您的子图架构中没有任何影响,它们保留这些信息。

实际上,网关通过忽略支持类型系统指令,使其成为定义它们的子图的责任。

可执行指令

可执行指令是指应用于以下位置之一这些位置的指令。这些指令在您的架构中定义,但它们被用于客户端发送的操作。

以下是一个可执行指令定义的示例:

# Uppercase this field's value (assuming it's a string)
directive @uppercase on FIELD

以下是一个使用该指令的示例:

query GetUppercaseUsernames {
users {
name @uppercase
}
}

强烈建议所有子图都为给定的可执行指令使用完全相同的逻辑。否则,操作可能会产生对客户端不一致或令人困惑的结果。

上一页
@apollo/subgraph 参考
下一页
@apollo/gateway 参考
给文章评分评分在GitHub上编辑编辑论坛Discord

©2024Apollo Graph Inc.,即Apollo GraphQL。

隐私政策

公司