ApolloFederationのスーパーグラフをコマンドラインで生成する方法
はじめに
こんにちはスペースマーケットでエンジニアをしているderaです。
弊社では、複数のサービスのGraphQLを取りまとめて、単一のGraphQLエンドポイントを提供するために、ApolloFederationを採用しています。この記事ではその際に行われているスキーマのマージのみ行う方法の紹介です。
ApolloFederationを利用したスキーマ合成に関しては過去のこちらの記事に詳しく解説をしています。
環境
- macOS Ventura 13.5.2
- Rover 0.19.1
スーパーグラフとは?
前文に貼っているリンクで詳しく解説されていますが、ApolloFederationは複数のGraphQLサービスを統合して、単一のGraphQLエンドポイントとしてを提供できます。統合前の各サービスをサブグラフで、統合後をスーパーグラフとApolloFederationで唱えています。
今回は、RoverCLIというApolloFederationが提供しているコマンドラインのツールでスーパーグラフを生成する方法を紹介します。
スキーマ合成
スキーマの合成は次の手順で行います。
前準備
前準備としてサブグラフを提供するGraphQLサービスを用意します。
次のコードでUserと商品を提供する2つのサービスを用意しました。
user-graph.js
import { ApolloServer } from '@apollo/server';
import { startStandaloneServer } from '@apollo/server/standalone';
import { gql } from 'graphql-tag';
import { buildSubgraphSchema } from '@apollo/subgraph';
const typeDefs = gql`
type User @key(fields: "id") {
id: ID!
name: String!
birthday: String!
}
extend type Query {
users: [User]
}
`;
const users = [
{
id: "1",
name: '山田太郎',
birthday: '1980-01-01',
},
{
id: "2",
name: '鈴木一郎',
birthday: '1990-02-02',
},
];
const resolvers = {
Query: {
users: () => users,
},
};
const server = new ApolloServer({
schema: buildSubgraphSchema({ typeDefs, resolvers })
});
const { url } = await startStandaloneServer(server, {
listen: { port: 4001 },
});
console.log(`🚀 Server ready at: ${url}`);
product-graph.js
import { ApolloServer } from '@apollo/server';
import { startStandaloneServer } from '@apollo/server/standalone';
import { gql } from 'graphql-tag';
import { buildSubgraphSchema } from '@apollo/subgraph';
const typeDefs = gql`#graphql
type Product @key(fields: "id") {
id: ID!
name: String!
price: Int!
}
extend type Query {
products: [Product]
}
`;
const products = [
{
id: "1",
name: 'キャベツ太郎',
price: 30,
},
{
id: "2",
name: 'うまい棒',
price: 10,
},
{
id: "3",
name: 'ポテトフライ',
price: 40,
},
];
const resolvers = {
Query: {
products: () => products
},
};
const server = new ApolloServer({
schema: buildSubgraphSchema({ typeDefs, resolvers })
});
const { url } = await startStandaloneServer(server, {
listen: { port: 4002 },
});
console.log(`🚀 Server ready at: ${url}`);
Userサービスが4001ポート、Productサービスが4002ポートで動作するようにしています。これらは単体で動作するGraphQLサービスなので、単体で確認する事も可能です。
設定ファイルの作成
次にスキーマ合成時に利用するコンフィグを用意します。
supergraph-config.yaml
# Federationのバージョンを指定する事が可能
federation_version: =2.3.2
# federation_version: =0.36.0
# サブグラフの定義を列挙する
subgraphs:
# Userサブグラフ
user:
routing_url: http://localhost:4001
schema:
subgraph_url: http://localhost:4001
# Productサブグラフ
product:
routing_url: http://localhost:4002
schema:
subgraph_url: http://localhost:4002
今回のサンプルでは起動中のサブグラフのサービスからスキーマを取得していますが、スキーマファイルを指定する事も可能です。
product:
routing_url: http://localhost:4002
schema:
file: products/schema.graphql
出力コマンドの実行
両サービスが起動している状態で、以下のコマンドを実行する事でカレントディレクトリにスーパーグラフが生成されます。
$ rover supergraph compose --config supergraph-config.yaml --output supergraph.graphql --elv2-license accept
--elv2-license accept
のオプションですが、機能を利用するにはElastic License v2 への同意が必要な為、許諾するために指定しています。
コマンドが成功すると、下記のスーパーグラフが出力されました。2つのサブグラフのスキーマ情報が含まれています。
supergraph.graphql
schema
@link(url: "https://specs.apollo.dev/link/v1.0")
@link(url: "https://specs.apollo.dev/join/v0.3", for: EXECUTION)
{
query: Query
}
directive @join__enumValue(graph: join__Graph!) repeatable on ENUM_VALUE
directive @join__field(graph: join__Graph, requires: join__FieldSet, provides: join__FieldSet, type: String, external: Boolean, override: String, usedOverridden: Boolean) repeatable on FIELD_DEFINITION | INPUT_FIELD_DEFINITION
directive @join__graph(name: String!, url: String!) on ENUM_VALUE
directive @join__implements(graph: join__Graph!, interface: String!) repeatable on OBJECT | INTERFACE
directive @join__type(graph: join__Graph!, key: join__FieldSet, extension: Boolean! = false, resolvable: Boolean! = true, isInterfaceObject: Boolean! = false) repeatable on OBJECT | INTERFACE | UNION | ENUM | INPUT_OBJECT | SCALAR
directive @join__unionMember(graph: join__Graph!, member: String!) repeatable on UNION
directive @link(url: String, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA
scalar join__FieldSet
enum join__Graph {
PRODUCT @join__graph(name: "product", url: "http://localhost:4002")
USER @join__graph(name: "user", url: "http://localhost:4001")
}
scalar link__Import
enum link__Purpose {
"""
`SECURITY` features provide metadata necessary to securely resolve fields.
"""
SECURITY
"""
`EXECUTION` features provide metadata necessary for operation execution.
"""
EXECUTION
}
type Product
@join__type(graph: PRODUCT, key: "id")
{
id: ID!
name: String!
price: Int!
}
type Query
@join__type(graph: PRODUCT)
@join__type(graph: USER)
{
products: [Product] @join__field(graph: PRODUCT)
users: [User] @join__field(graph: USER)
}
type User
@join__type(graph: USER, key: "id")
{
id: ID!
name: String!
birthday: String!
}
Gatewayを動かしてテストする
次に出力されたスーパーグラフを読み込んで、Gatewayを起動してみましょう。
Gateway起動に次のコードを作成しました。
gateway.js
import { ApolloServer } from 'apollo-server';
import { ApolloGateway } from '@apollo/gateway';
const gateway = new ApolloGateway({
supergraphSdl: fs.readFileSync('./supergraph.graphql', 'utf8'),
});
const server = new ApolloServer({
gateway,
subscriptions: false,
});
server.listen().then(({ url }) => {
console.log(`🚀 Server ready at ${url}`);
});
Gatewayを起動して、スーパーグラフが正しく動作しているか確認しました。
サブグラフの内容が1エンドポイント、1リクエストで取得できているので、スキーマ合成もGateway起動も正しく動作していますね!
まとめ
以上、ApolloFederationのスーパーグラフをコマンドから合成する方法の紹介でした。
今回作成したコードはこちらに置いているので興味があるようでしたらご覧くださいませ!
スペースを簡単に貸し借りできるサービス「スペースマーケット」のエンジニアによる公式ブログです。 弊社採用技術スタックはこちら -> whatweuse.dev/company/spacemarket
Discussion