スキーマスティッチングでGraphQLフェデレーションの力を解き放ちましょう。複数のサービスから統一されたGraphQL APIを構築し、スケーラビリティと保守性を向上させる方法を学びます。
GraphQLフェデレーション:スキーマスティッチング - 包括的ガイド
進化し続ける現代のアプリケーション開発の世界では、スケーラブルで保守性の高いアーキテクチャの必要性が最も重要になっています。マイクロサービスは、その固有のモジュール性と独立したデプロイ可能性により、一般的な解決策として浮上しています。しかし、多数のマイクロサービスを管理することは、特にクライアントアプリケーションに統一されたAPIを公開する際に、複雑さを生む可能性があります。ここで、GraphQLフェデレーション、特にスキーマスティッチングが活躍するのです。
GraphQLフェデレーションとは?
GraphQLフェデレーションは、複数の基盤となるGraphQLサービス(多くはマイクロサービスを代表する)から単一の統一されたGraphQL APIを構築できる強力なアーキテクチャです。これにより、開発者はあたかも単一のグラフであるかのように異なるサービス間のデータをクエリでき、クライアント体験を簡素化し、クライアント側での複雑なオーケストレーションロジックの必要性を減らします。
GraphQLフェデレーションには、主に2つのアプローチがあります:
- スキーマスティッチング(Schema Stitching): 複数のGraphQLスキーマをゲートウェイ層で単一の統一スキーマに結合するものです。これは初期のアプローチであり、スキーマの結合とクエリの委任を管理するためにライブラリに依存します。
- Apollo Federation: これはより新しく堅牢なアプローチで、宣言的なスキーマ言語と専用のクエリプランナーを使用してフェデレーションプロセスを管理します。型の拡張、キードレクティブ、分散トレーシングなどの高度な機能を提供します。
この記事では、スキーマスティッチングに焦点を当て、その概念、利点、制限、そして実践的な実装について探ります。
スキーマスティッチングを理解する
スキーマスティッチングは、複数のGraphQLスキーマを単一のまとまりのあるスキーマにマージするプロセスです。この統一されたスキーマはファサードとして機能し、基盤となるサービスの複雑さをクライアントから隠します。クライアントがスティッチされたスキーマにリクエストを送信すると、ゲートウェイはインテリジェントにリクエストを適切な基盤サービスにルーティングし、データを取得し、結果を結合してクライアントに返します。
これを次のように考えてみてください:それぞれ異なる料理を専門とする複数のレストラン(サービス)があるとします。スキーマスティッチングは、各レストランのすべての料理を組み合わせたユニバーサルメニューのようなものです。顧客(クライアント)がユニバーサルメニューから注文すると、注文は適切なレストランのキッチンにインテリジェントにルーティングされ、食事が準備され、そして顧客への単一の配達にまとめられます。
スキーマスティッチングの主要概念
- リモートスキーマ: これらは、各基盤となるサービスの個々のGraphQLスキーマです。各サービスは、自身が提供するデータと操作を定義する独自のスキーマを公開します。
- ゲートウェイ: ゲートウェイは、リモートスキーマをスティッチし、統一されたスキーマをクライアントに公開する責任を持つ中心的なコンポーネントです。クライアントのリクエストを受け取り、適切なサービスにルーティングし、結果を結合します。
- スキーママージング: これは、リモートスキーマを単一のスキーマに結合するプロセスです。これには、競合を避けるために型やフィールドの名前を変更したり、異なるスキーマ間の型の関係を定義したりすることがしばしば含まれます。
- クエリ委任: クライアントがスティッチされたスキーマにリクエストを送信すると、ゲートウェイはデータを取得するために適切な基盤サービスにリクエストを委任する必要があります。これには、クライアントのクエリをリモートサービスが理解できるクエリに変換することが含まれます。
- 結果の集約: ゲートウェイが基盤サービスからデータを取得した後、スティッチされたスキーマの構造に一致するように、結果を単一のレスポンスに結合する必要があります。これには、データを変換する作業がしばしば伴います。
スキーマスティッチングの利点
スキーマスティッチングは、マイクロサービスアーキテクチャを採用する組織にいくつかの魅力的な利点を提供します:
- 統一されたAPI: クライアントに単一で一貫したAPIを提供し、データアクセスを簡素化し、クライアントが複数のサービスと直接やり取りする必要性を減らします。これにより、よりクリーンで直感的な開発者体験が実現します。
- クライアントの複雑性の削減: クライアントは統一されたスキーマとだけやり取りすればよいため、基盤となるマイクロサービスアーキテクチャの複雑さから保護されます。これにより、クライアント側の開発が簡素化され、クライアントで必要なコード量が削減されます。
- スケーラビリティの向上: 個々のサービスを特定のニーズに基づいて独立してスケールさせることができます。これにより、システム全体の scalability と resilience が向上します。例えば、高負荷を経験しているユーザーサービスを、製品カタログのような他のサービスに影響を与えることなくスケールできます。
- 保守性の向上: モジュール性と関心の分離を促進し、個々のサービスの保守と進化を容易にします。あるサービスへの変更が他のサービスに影響を与える可能性が低くなります。
- 段階的な採用: 段階的に実装できるため、モノリシックアーキテクチャからマイクロサービスアーキテクチャへ徐々に移行できます。既存のAPIをスティッチすることから始め、徐々にモノリスをより小さなサービスに分解していくことができます。
スキーマスティッチングの制限
スキーマスティッチングは多くの利点を提供しますが、その制限についても認識しておくことが重要です:
- 複雑さ: スキーマスティッチングの実装と管理は、特に大規模で複雑なシステムでは複雑になる可能性があります。慎重な計画と設計が不可欠です。
- パフォーマンスのオーバーヘッド: ゲートウェイは、追加の間接層と、クエリを委任して結果を集約する必要があるため、ある程度のパフォーマンスオーバーヘッドを導入します。このオーバーヘッドを最小限に抑えるためには、慎重な最適化が不可欠です。
- スキーマの競合: 異なるサービスのスキーマをマージする際に、特に同じ型名やフィールド名を使用している場合、競合が発生する可能性があります。これには、慎重なスキーマ設計と、必要に応じて型やフィールドの名前の変更が必要です。
- 高度な機能の制限: Apollo Federationと比較して、スキーマスティッチングには型の拡張やキードレクティブのような一部の高度な機能が欠けており、異なるスキーマ間の型の関係を管理するのがより困難になる可能性があります。
- ツールの成熟度: スキーマスティッチング周辺のツールとエコシステムは、Apollo Federation周辺のものほど成熟していません。これにより、問題のデバッグやトラブルシューティングがより困難になる可能性があります。
スキーマスティッチングの実践的な実装
Node.jsとgraphql-tools
ライブラリ(スキーマスティッチングで人気の選択肢)を使用して、スキーマスティッチングを実装する簡単な例を見ていきましょう。この例では、ユーザーサービスと製品サービスの2つのマイクロサービスを使用します。
1. リモートスキーマの定義
まず、各リモートサービスのGraphQLスキーマを定義します。
ユーザーサービス (user-service.js
):
const { buildSchema } = require('graphql');
const userSchema = buildSchema(`
type User {
id: ID!
name: String
email: String
}
type Query {
user(id: ID!): User
}
`);
const users = [
{ id: '1', name: 'Alice Smith', email: 'alice@example.com' },
{ id: '2', name: 'Bob Johnson', email: 'bob@example.com' },
];
const userRoot = {
user: (args) => users.find(user => user.id === args.id),
};
module.exports = {
schema: userSchema,
rootValue: userRoot,
};
製品サービス (product-service.js
):
const { buildSchema } = require('graphql');
const productSchema = buildSchema(`
type Product {
id: ID!
name: String
price: Float
userId: ID! # ユーザーサービスへの外部キー
}
type Query {
product(id: ID!): Product
}
`);
const products = [
{ id: '101', name: 'Laptop', price: 1200, userId: '1' },
{ id: '102', name: 'Smartphone', price: 800, userId: '2' },
];
const productRoot = {
product: (args) => products.find(product => product.id === args.id),
};
module.exports = {
schema: productSchema,
rootValue: productRoot,
};
2. ゲートウェイサービスの作成
次に、2つのスキーマをスティッチするゲートウェイサービスを作成します。
ゲートウェイサービス (gateway.js
):
const { stitchSchemas } = require('@graphql-tools/stitch');
const { makeRemoteExecutableSchema } = require('@graphql-tools/wrap');
const { graphqlHTTP } = require('express-graphql');
const express = require('express');
const { introspectSchema } = require('@graphql-tools/wrap');
const { printSchema } = require('graphql');
const fetch = require('node-fetch');
async function createRemoteSchema(uri) {
const fetcher = async (params) => {
const response = await fetch(uri, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(params),
});
return response.json();
};
const schema = await introspectSchema(fetcher);
return makeRemoteExecutableSchema({
schema,
fetcher,
});
}
async function main() {
const userSchema = await createRemoteSchema('http://localhost:4001/graphql');
const productSchema = await createRemoteSchema('http://localhost:4002/graphql');
const stitchedSchema = stitchSchemas({
subschemas: [
{ schema: userSchema },
{ schema: productSchema },
],
typeDefs: `
extend type Product {
user: User
}
`,
resolvers: {
Product: {
user: {
selectionSet: `{ userId }`,
resolve(product, args, context, info) {
return info.mergeInfo.delegateToSchema({
schema: userSchema,
operation: 'query',
fieldName: 'user',
args: {
id: product.userId,
},
context,
info,
});
},
},
},
},
});
const app = express();
app.use('/graphql', graphqlHTTP({
schema: stitchedSchema,
graphiql: true,
}));
app.listen(4000, () => console.log('Gateway server running on http://localhost:4000/graphql'));
}
main().catch(console.error);
3. サービスの実行
ユーザーサービスと製品サービスを異なるポートで実行する必要があります。例えば:
ユーザーサービス(ポート4001):
const express = require('express');
const { graphqlHTTP } = require('express-graphql');
const { schema, rootValue } = require('./user-service');
const app = express();
app.use('/graphql', graphqlHTTP({
schema: schema,
rootValue: rootValue,
graphiql: true,
}));
app.listen(4001, () => console.log('User service running on http://localhost:4001/graphql'));
製品サービス(ポート4002):
const express = require('express');
const { graphqlHTTP } = require('express-graphql');
const { schema, rootValue } = require('./product-service');
const app = express();
app.use('/graphql', graphqlHTTP({
schema: schema,
rootValue: rootValue,
graphiql: true,
}));
app.listen(4002, () => console.log('Product service running on http://localhost:4002/graphql'));
4. スティッチされたスキーマへのクエリ
これで、ゲートウェイ(ポート4000で実行中)を介してスティッチされたスキーマにクエリを実行できます。次のようなクエリを実行できます:
query {
product(id: "101") {
id
name
price
user {
id
name
email
}
}
}
このクエリはID「101」の製品を取得し、関連するユーザーをユーザーサービスからフェッチします。これは、スキーマスティッチングが単一のリクエストで複数のサービスにまたがるデータをクエリできることを示しています。
高度なスキーマスティッチング技術
基本的な例を超えて、スキーマスティッチングの実装を強化するために使用できるいくつかの高度な技術を紹介します:
- スキーマ委任: これにより、要求されているデータに基づいてクエリの一部を異なるサービスに委任できます。例えば、
User
型の解決をユーザーサービスに、Product
型の解決を製品サービスに委任することができます。 - スキーマ変換: これには、リモートサービスのスキーマを統一スキーマにスティッチする前に変更することが含まれます。これは、型やフィールドの名前の変更、新しいフィールドの追加、または既存のフィールドの削除に役立ちます。
- カスタムリゾルバ: ゲートウェイでカスタムリゾルバを定義して、複雑なデータ変換を処理したり、複数のサービスからデータをフェッチして単一の結果に結合したりできます。
- コンテキスト共有: 認証トークンやユーザーIDなどのコンテキスト情報をゲートウェイとリモートサービス間で共有することがしばしば必要になります。これは、クエリ委任プロセスの一部としてコンテキスト情報を渡すことで実現できます。
- エラー処理: リモートサービスで発生したエラーを適切に処理するために、堅牢なエラー処理を実装します。これには、エラーのログ記録、ユーザーフレンドリーなエラーメッセージの返却、または失敗したリクエストの再試行などが含まれる場合があります。
スキーマスティッチングとApollo Federationの選択
スキーマスティッチングはGraphQLフェデレーションの実行可能な選択肢ですが、Apollo Federationはその高度な機能と改善された開発者体験により、より人気のある選択肢となっています。以下に2つのアプローチの比較を示します:
機能 | スキーマスティッチング | Apollo Federation |
---|---|---|
スキーマ定義 | 既存のGraphQLスキーマ言語を使用 | ディレクティブを持つ宣言的なスキーマ言語を使用 |
クエリ計画 | 手動のクエリ委任が必要 | Apollo Gatewayによる自動クエリ計画 |
型の拡張 | 限定的なサポート | 型の拡張を組み込みでサポート |
キードレクティブ | サポートされていない | @key ディレクティブを使用してエンティティを識別 |
分散トレーシング | 手動での実装が必要 | 分散トレーシングを組み込みでサポート |
ツールとエコシステム | ツールが未成熟 | より成熟したツールと大規模なコミュニティ |
複雑さ | 大規模システムでは管理が複雑になることがある | 大規模で複雑なシステム向けに設計 |
スキーマスティッチングを選択する場合:
- 既存のGraphQLサービスがあり、それらを迅速に結合したい場合。
- 単純なフェデレーションソリューションが必要で、高度な機能は不要な場合。
- リソースが限られており、Apollo Federationの設定のオーバーヘッドを避けたい場合。
Apollo Federationを選択する場合:
- 複数のチームやサービスを持つ大規模で複雑なシステムを構築している場合。
- 型の拡張、キードレクティブ、分散トレーシングなどの高度な機能が必要な場合。
- より堅牢でスケーラブルなフェデレーションソリューションを求めている場合。
- より宣言的で自動化されたフェデレーションアプローチを好む場合。
実世界の例とユースケース
以下に、スキーマスティッチングを含むGraphQLフェデレーションがどのように使用できるかの実世界の例をいくつか示します:
- Eコマースプラットフォーム: Eコマースプラットフォームは、製品カタログサービス、ユーザーサービス、注文サービス、決済サービスなど、複数のサービスからのデータを結合するためにGraphQLフェデレーションを使用するかもしれません。これにより、クライアントは製品詳細、ユーザープロファイル、注文履歴、支払い情報を表示するために必要なすべての情報を簡単に取得できます。
- ソーシャルメディアプラットフォーム: ソーシャルメディアプラットフォームは、ユーザープロファイル、投稿、コメント、いいねを管理するサービスからのデータを結合するためにGraphQLフェデレーションを使用できます。これにより、クライアントはユーザーのプロファイル、その投稿、およびそれらの投稿に関連するコメントやいいねを表示するために必要なすべての情報を効率的に取得できます。
- 金融サービスアプリケーション: 金融サービスアプリケーションは、口座、取引、投資を管理するサービスからのデータを結合するためにGraphQLフェデレーションを使用するかもしれません。これにより、クライアントは口座残高、取引履歴、投資ポートフォリオを表示するために必要なすべての情報を簡単に取得できます。
- コンテンツ管理システム(CMS): CMSは、記事、画像、動画、ユーザー生成コンテンツなど、さまざまなソースからのデータを統合するためにGraphQLフェデレーションを活用できます。これにより、特定のトピックや著者に関連するすべてのコンテンツを取得するための統一されたAPIが可能になります。
- ヘルスケアアプリケーション: 電子カルテ(EHR)、検査結果、予約スケジュールなど、さまざまなシステムからの患者データを統合します。これにより、医師は包括的な患者情報に単一のアクセスポイントでアクセスできます。
スキーマスティッチングのベストプラクティス
スキーマスティッチングの実装を成功させるために、以下のベストプラクティスに従ってください:
- スキーマを慎重に計画する: スキーマをスティッチし始める前に、統一スキーマの構造を慎重に計画してください。これには、異なるスキーマ間の型の関係の定義、競合を避けるための型やフィールドの名前の変更、および全体的なデータアクセスパターンの考慮が含まれます。
- 一貫した命名規則を使用する: すべてのサービスにわたって、型、フィールド、操作に一貫した命名規則を採用してください。これにより、競合を避け、統一スキーマを理解しやすくなります。
- スキーマを文書化する: 型、フィールド、操作の説明を含む統一スキーマを徹底的に文書化してください。これにより、開発者がスキーマを理解し、使用しやすくなります。
- パフォーマンスを監視する: ゲートウェイとリモートサービスのパフォーマンスを監視し、パフォーマンスのボトルネックを特定して対処してください。分散トレーシングのようなツールを使用して、複数のサービスにまたがるリクエストを追跡します。
- セキュリティを実装する: ゲートウェイとリモートサービスを不正アクセスから保護するために、適切なセキュリティ対策を実装してください。これには、認証・認可メカニズムの使用、および入力検証と出力エンコーディングが含まれる場合があります。
- スキーマをバージョン管理する: スキーマを進化させるにつれて、クライアントが古いバージョンのスキーマを壊すことなく使用し続けられるように、適切にバージョン管理してください。これにより、破壊的な変更を避け、後方互換性を確保できます。
- デプロイを自動化する: ゲートウェイとリモートサービスのデプロイを自動化して、変更を迅速かつ確実にデプロイできるようにしてください。これにより、エラーのリスクを減らし、システム全体の俊敏性を向上させることができます。
結論
スキーマスティッチングによるGraphQLフェデレーションは、マイクロサービスアーキテクチャにおいて複数のサービスから統一されたAPIを構築するための強力なアプローチを提供します。その中心的な概念、利点、制限、および実装技術を理解することで、スキーマスティッチングを活用してデータアクセスを簡素化し、スケーラビリティを向上させ、保守性を高めることができます。Apollo Federationがより高度なソリューションとして登場していますが、スキーマスティッチングは、より単純なシナリオや既存のGraphQLサービスを統合する際には依然として実行可能な選択肢です。組織の特定のニーズと要件を慎重に検討し、最適なアプローチを選択してください。