使用 Apollo Server 实现 subgraph
本文演示了如何使用Node.js和子图(subgraph)和Apollo Server创建一个超级图(supergraph)
若要使用不同的语言和/或框架创建子图,请参阅联邦兼容子图实现列表。注意,并非所有列出的库都支持所有联邦功能。
定义子图
要成为超级图的一部分,子图必须符合Apollo Federation子图规范,该规范可向您的图路由器以及Apollo Studio等工具暴露子图的特性。
将现有的单体图转换为单个子图是构建联邦超级图的一个方便的第一步。首先,以下是一个非联邦 Apollo Server的设置:
import { ApolloServer } from '@apollo/server';import { startStandaloneServer } from '@apollo/server/standalone';import gql from 'graphql-tag';const typeDefs = gql`type Query {me: User}type User {id: ID!username: String}`;const resolvers = {Query: {me() {return { id: '1', username: '@ava' };},},};const server = new ApolloServer({typeDefs,resolvers,});// Note the top-level await!const { url } = await startStandaloneServer(server);console.log(`🚀 Server ready at ${url}`);
import { ApolloServer } from '@apollo/server';import { startStandaloneServer } from '@apollo/server/standalone';import gql from 'graphql-tag';const typeDefs = gql`type Query {me: User}type User {id: ID!username: String}`;const resolvers = {Query: {me() {return { id: '1', username: '@ava' };},},};const server = new ApolloServer({typeDefs,resolvers,});// Note the top-level await!const { url } = await startStandaloneServer(server);console.log(`🚀 Server ready at ${url}`);
上面,我们把我们的模式用gql
标签来自 graphql-tag
包,将我们的 schema 转换为 AST(即 DocumentNode
)。虽然 Apollo Server 可以接受一个 string
(或者 DocumentNode
)作为它的 typeDefs
,下面的 buildSubgraphSchema
函数需要我们传递的 schema 是一个 DocumentNode
。
如果你之前已经设置过 Apollo Server,这应该看起来很熟悉。如果你还没有,我们在开始联邦之前推荐你先熟悉一下基础知识。
现在,让我们将其转换为 subgraph!
1. 安装并导入 @apollo/subgraph
第一步是在服务器项目中安装 @apollo/subgraph
@apollo/subgraph
包:
npm install @apollo/subgraph
我们还需要在这个文件中引入 buildSubgraphSchema
buildSubgraphSchema
函数(我们稍后会用到)在初始化 ApolloServer
ApolloServer
的文件中:
import { buildSubgraphSchema } from '@apollo/subgraph';
import { buildSubgraphSchema } from '@apollo/subgraph';
2. 集成 Federation 2
为了使 subgraph 使用 Federation 2 的新功能,它的 schema 需要包含以下 extend schema
extend schema
定义:
import gql from 'graphql-tag';const typeDefs = gql`# highlight-startextend schema @link(url: "https://specs.apollo.dev/federation/v2.0", import: ["@key", "@shareable"])# highlight-endtype Query {me: User}type User {id: ID!username: String}`;
import gql from 'graphql-tag';const typeDefs = gql`extend schema @link(url: "https://specs.apollo.dev/federation/v2.0", import: ["@key", "@shareable"])type Query {me: User}type User {id: ID!username: String}`;
这个定义使得 schema 能够使用 Federation 2 功能。没有它,Federation 2 的 composition 会假定 subgraph 使用 Federation 1,这会为了向后兼容而设置一些默认值。
当你开始使用更多 与 federation 相关的指令(超出 @key
和 @shareable
),你需要在上述的 import
数组中添加这些 directives。
3. 定义实体
实体在 subgraph 中不是必需的,但它们是联邦 supergraph 的核心构建块,所以练习定义它们是有好处的。
作为我们的联邦架构的一部分,我们希望 其他 subgraph 能够向 User
类型贡献 fields。为了启用此功能,我们在 User
类型的定义中添加 @key
directive,将其指定为 entity:
const typeDefs = gql`extend schema @link(url: "https://specs.apollo.dev/federation/v2.0", import: ["@key", "@shareable"])type Query {me: User}type User@key(fields: "id") { # highlight-lineid: ID!username: String}`;
const typeDefs = gql`extend schema @link(url: "https://specs.apollo.dev/federation/v2.0", import: ["@key", "@shareable"])type Query {me: User}type User@key(fields: "id") {id: ID!username: String}`;
该@key
指令告知网关哪个字段能够唯一标识User
实体的特定实例。在这种情况下,网关可以使用单个id
。
接下来,我们为User
实体添加一个引用解析器。一个引用解析器告诉网关如何通过它的@key
字段来获取实体:
const resolvers = {Query: {me() {return { id: '1', username: '@ava' };},},User: {__resolveReference(user, { fetchUserById }) {return fetchUserById(user.id);},},};
const resolvers = {Query: {me() {return { id: '1', username: '@ava' };},},User: {__resolveReference(user, { fetchUserById }) {return fetchUserById(user.id);},},};
(此示例需要定义fetchUserById
函数以从我们的后端数据存储中获取适当的User
。)
4. 生成子图模式
最后,我们使用来自@apollo/subgraph
包的buildSubgraphSchema
函数来添加对联盟支持的增强到我们的模式定义中。我们向ApolloServer
构造函数提供此函数的结果:
const server = new ApolloServer({schema: buildSubgraphSchema({ typeDefs, resolvers }),});// Note the top level await!const { url } = await startStandaloneServer(server);console.log(`🚀 Server ready at ${url}`);
const server = new ApolloServer({schema: buildSubgraphSchema({ typeDefs, resolvers }),});// Note the top level await!const { url } = await startStandaloneServer(server);console.log(`🚀 Server ready at ${url}`);
服务器现在已准备好作为一个联邦图中的子图执行!
综合示例
以下是上述代码片段的合并(请注意,为了使此示例完整,您必须为您的数据源定义fetchUserById
函数):
import { ApolloServer } from '@apollo/server';import { startStandaloneServer } from '@apollo/server/standalone';import gql from 'graphql-tag';import { buildSubgraphSchema } from '@apollo/subgraph';const typeDefs = gql`extend schema @link(url: "https://specs.apollo.dev/federation/v2.0", import: ["@key", "@shareable"])type Query {me: User}type User @key(fields: "id") {id: ID!username: String}`;const resolvers = {Query: {me() {return { id: '1', username: '@ava' };},},User: {__resolveReference(user, { fetchUserById }) {return fetchUserById(user.id);},},};const server = new ApolloServer({schema: buildSubgraphSchema({ typeDefs, resolvers }),});const { url } = await startStandaloneServer(server);console.log(`🚀 Server ready at ${url}`);
import { ApolloServer } from '@apollo/server';import { startStandaloneServer } from '@apollo/server/standalone';import gql from 'graphql-tag';import { buildSubgraphSchema } from '@apollo/subgraph';const typeDefs = gql`extend schema @link(url: "https://specs.apollo.dev/federation/v2.0", import: ["@key", "@shareable"])type Query {me: User}type User @key(fields: "id") {id: ID!username: String}`;const resolvers = {Query: {me() {return { id: '1', username: '@ava' };},},User: {__resolveReference(user, { fetchUserById }) {return fetchUserById(user.id);},},};const server = new ApolloServer({schema: buildSubgraphSchema({ typeDefs, resolvers }),});const { url } = await startStandaloneServer(server);console.log(`🚀 Server ready at ${url}`);
自定义指令
请参阅有关子图中自定义指令的信息子图中的自定义指令。