GraphQL Federationとは
GraphQL Federationは、複数のGraphQLサービス(サブグラフ)を単一の統合APIとして公開するアーキテクチャパターンです。2025年、Apollo Federation 2.0を中心に、マイクロサービス環境でのAPI統合のデファクトスタンダードとなりました。
なぜFederationが必要か
マイクロサービスの課題
従来のアプローチ:
┌─────────────────────────────────────────────────────┐
│ クライアント │
└─────────────────────────────────────────────────────┘
↓ ↓ ↓
[Users API] [Orders API] [Products API]
問題点:
・複数のエンドポイントを管理
・クライアントでデータ結合が必要
・オーバーフェッチ/アンダーフェッチ
・型安全性の欠如
Federationによる解決
GraphQL Federation:
┌─────────────────────────────────────────────────────┐
│ クライアント │
└─────────────────────────────────────────────────────┘
↓
[Apollo Router/Gateway]
(統合GraphQL API)
↓ ↓ ↓
[Users [Orders [Products
Subgraph] Subgraph] Subgraph]
メリット:
・単一エンドポイント
・自動データ結合
・必要なデータのみ取得
・完全な型安全性
Apollo Federation 2.0の特徴
Federation 1.0との比較
federation_2_improvements:
progressive_overrides:
description: "段階的なフィールド移行"
benefit: "ダウンタイムなしのリファクタリング"
improved_composition:
description: "より柔軟なスキーマ合成"
benefit: "競合解決の改善"
enhanced_directives:
description: "新しいディレクティブ"
benefit: "@shareable, @inaccessible, @override"
better_error_messages:
description: "詳細なエラーメッセージ"
benefit: "デバッグ効率の向上"
query_planner_v2:
description: "新しいクエリプランナー"
benefit: "パフォーマンス最適化"
主要なディレクティブ
# Federation 2.0 ディレクティブ一覧
# @key - エンティティの識別子を定義
type User @key(fields: "id") {
id: ID!
name: String!
}
# @shareable - 複数サブグラフでフィールドを共有
type Product @key(fields: "id") {
id: ID!
name: String! @shareable
price: Float! @shareable
}
# @external - 他サブグラフで定義されたフィールドを参照
type User @key(fields: "id") {
id: ID! @external
reviews: [Review!]!
}
# @requires - 解決に必要な外部フィールド
type Product @key(fields: "id") {
id: ID!
price: Float! @external
priceWithTax: Float! @requires(fields: "price")
}
# @provides - 解決可能な追加フィールドを示す
type Review {
id: ID!
product: Product! @provides(fields: "name")
}
# @override - フィールドの所有権を移行
type Product @key(fields: "id") {
id: ID!
inventory: Int! @override(from: "inventory-subgraph")
}
# @inaccessible - 外部APIから隠蔽
type InternalMetrics @inaccessible {
processingTime: Float!
}
Apollo Router
Routerの役割
Apollo Routerは、Rustで書かれた高性能なGraphQLフェデレーションゲートウェイです。Apollo Gatewayの後継として、2025年に本番環境での採用が急増しています。
# router.yaml - Apollo Router設定
supergraph:
listen: 127.0.0.1:4000
introspection: true
headers:
all:
request:
- propagate:
named: "Authorization"
- propagate:
named: "X-Request-ID"
cors:
origins:
- https://example.com
- https://studio.apollographql.com
allow_credentials: true
telemetry:
instrumentation:
spans:
mode: spec_compliant
exporters:
tracing:
otlp:
enabled: true
endpoint: http://otel-collector:4317
limits:
max_depth: 15
max_height: 200
max_aliases: 30
max_root_fields: 20
traffic_shaping:
router:
timeout: 30s
all:
deduplicate_query: true
coprocessor:
url: http://localhost:8081
timeout: 2s
パフォーマンス比較
Apollo Gateway (Node.js) vs Apollo Router (Rust):
┌────────────────────┬──────────────┬──────────────┐
│ メトリクス │ Gateway │ Router │
├────────────────────┼──────────────┼──────────────┤
│ スループット │ 5,000 RPS │ 50,000 RPS │
│ レイテンシ (p50) │ 45ms │ 8ms │
│ レイテンシ (p99) │ 200ms │ 25ms │
│ メモリ使用量 │ 512MB │ 50MB │
│ CPU効率 │ 標準 │ 10x向上 │
└────────────────────┴──────────────┴──────────────┘
※ 同一ハードウェアでのベンチマーク結果
スキーマ設計パターン
エンティティ設計
# users-subgraph/schema.graphql
extend schema
@link(
url: "https://specs.apollo.dev/federation/v2.3"
import: ["@key", "@shareable", "@external"]
)
type Query {
user(id: ID!): User
users(filter: UserFilter): [User!]!
}
type Mutation {
createUser(input: CreateUserInput!): User!
updateUser(id: ID!, input: UpdateUserInput!): User!
}
type User @key(fields: "id") @key(fields: "email") {
id: ID!
email: String!
name: String!
createdAt: DateTime!
profile: UserProfile
}
type UserProfile {
bio: String
avatarUrl: String
socialLinks: [SocialLink!]!
}
type SocialLink {
platform: String!
url: String!
}
input UserFilter {
name: String
email: String
createdAfter: DateTime
}
input CreateUserInput {
email: String!
name: String!
}
input UpdateUserInput {
name: String
profile: UserProfileInput
}
エンティティ拡張
# orders-subgraph/schema.graphql
extend schema
@link(
url: "https://specs.apollo.dev/federation/v2.3"
import: ["@key", "@external", "@requires"]
)
type Query {
order(id: ID!): Order
orders(userId: ID!): [Order!]!
}
type Order @key(fields: "id") {
id: ID!
status: OrderStatus!
items: [OrderItem!]!
user: User!
createdAt: DateTime!
totalAmount: Float!
}
type OrderItem {
product: Product!
quantity: Int!
unitPrice: Float!
}
enum OrderStatus {
PENDING
CONFIRMED
SHIPPED
DELIVERED
CANCELLED
}
# Userエンティティを拡張
type User @key(fields: "id") {
id: ID!
orders: [Order!]!
orderCount: Int!
totalSpent: Float!
}
# Productエンティティを参照
type Product @key(fields: "id") {
id: ID!
}
製品サブグラフ
# products-subgraph/schema.graphql
extend schema
@link(
url: "https://specs.apollo.dev/federation/v2.3"
import: ["@key", "@shareable"]
)
type Query {
product(id: ID!): Product
products(
category: String
minPrice: Float
maxPrice: Float
first: Int
after: String
): ProductConnection!
}
type Product @key(fields: "id") @key(fields: "sku") {
id: ID!
sku: String!
name: String! @shareable
description: String
price: Float! @shareable
category: Category!
inventory: Int!
images: [ProductImage!]!
reviews: [Review!]!
averageRating: Float
}
type Category {
id: ID!
name: String!
parentCategory: Category
}
type ProductImage {
url: String!
alt: String
isPrimary: Boolean!
}
type Review {
id: ID!
rating: Int!
comment: String
user: User!
createdAt: DateTime!
}
type User @key(fields: "id") {
id: ID!
}
# ページネーション
type ProductConnection {
edges: [ProductEdge!]!
pageInfo: PageInfo!
totalCount: Int!
}
type ProductEdge {
node: Product!
cursor: String!
}
type PageInfo {
hasNextPage: Boolean!
hasPreviousPage: Boolean!
startCursor: String
endCursor: String
}
サブグラフ実装例
TypeScript + Apollo Server
// users-subgraph/src/index.ts
import { ApolloServer } from "@apollo/server";
import { startStandaloneServer } from "@apollo/server/standalone";
import { buildSubgraphSchema } from "@apollo/subgraph";
import { readFileSync } from "fs";
import { parse } from "graphql";
const typeDefs = parse(
readFileSync("./schema.graphql", "utf-8")
);
interface User {
id: string;
email: string;
name: string;
createdAt: Date;
profile?: UserProfile;
}
interface UserProfile {
bio?: string;
avatarUrl?: string;
socialLinks: SocialLink[];
}
interface SocialLink {
platform: string;
url: string;
}
// インメモリデータストア(実際はDBを使用)
const users: Map<string, User> = new Map();
const resolvers = {
Query: {
user: (_: unknown, { id }: { id: string }) => {
return users.get(id);
},
users: (_: unknown, { filter }: { filter?: any }) => {
let result = Array.from(users.values());
if (filter?.name) {
result = result.filter(u =>
u.name.toLowerCase().includes(filter.name.toLowerCase())
);
}
if (filter?.email) {
result = result.filter(u =>
u.email.toLowerCase().includes(filter.email.toLowerCase())
);
}
return result;
},
},
Mutation: {
createUser: (_: unknown, { input }: { input: any }) => {
const id = crypto.randomUUID();
const user: User = {
id,
email: input.email,
name: input.name,
createdAt: new Date(),
};
users.set(id, user);
return user;
},
updateUser: (_: unknown, { id, input }: { id: string; input: any }) => {
const user = users.get(id);
if (!user) throw new Error("User not found");
if (input.name) user.name = input.name;
if (input.profile) user.profile = { ...user.profile, ...input.profile };
users.set(id, user);
return user;
},
},
User: {
// エンティティ解決のための__resolveReference
__resolveReference: (reference: { id?: string; email?: string }) => {
if (reference.id) {
return users.get(reference.id);
}
if (reference.email) {
return Array.from(users.values()).find(
u => u.email === reference.email
);
}
return null;
},
},
};
const server = new ApolloServer({
schema: buildSubgraphSchema({ typeDefs, resolvers }),
});
const { url } = await startStandaloneServer(server, {
listen: { port: 4001 },
});
console.log(`Users subgraph ready at ${url}`);
Go + gqlgen
// products-subgraph/graph/resolver.go
package graph
import (
"context"
"sync"
"github.com/99designs/gqlgen/graphql"
)
type Resolver struct {
products sync.Map
mu sync.RWMutex
}
func NewResolver() *Resolver {
return &Resolver{}
}
// Query resolvers
func (r *queryResolver) Product(ctx context.Context, id string) (*Product, error) {
if val, ok := r.products.Load(id); ok {
return val.(*Product), nil
}
return nil, nil
}
func (r *queryResolver) Products(
ctx context.Context,
category *string,
minPrice *float64,
maxPrice *float64,
first *int,
after *string,
) (*ProductConnection, error) {
var products []*Product
r.products.Range(func(key, value interface{}) bool {
p := value.(*Product)
// フィルタリング
if category != nil && p.Category.Name != *category {
return true
}
if minPrice != nil && p.Price < *minPrice {
return true
}
if maxPrice != nil && p.Price > *maxPrice {
return true
}
products = append(products, p)
return true
})
// ページネーション処理
return buildProductConnection(products, first, after), nil
}
// Entity resolver for Federation
func (r *entityResolver) FindProductByID(
ctx context.Context,
id string,
) (*Product, error) {
return r.Query().Product(ctx, id)
}
func (r *entityResolver) FindProductBySku(
ctx context.Context,
sku string,
) (*Product, error) {
var found *Product
r.products.Range(func(key, value interface{}) bool {
p := value.(*Product)
if p.SKU == sku {
found = p
return false
}
return true
})
return found, nil
}
Python + Strawberry
# orders-subgraph/main.py
import strawberry
from strawberry.federation import Schema
from typing import List, Optional
from datetime import datetime
from decimal import Decimal
@strawberry.federation.type(keys=["id"])
class User:
id: strawberry.ID
@strawberry.federation.type(keys=["id"])
class Product:
id: strawberry.ID
@strawberry.enum
class OrderStatus:
PENDING = "PENDING"
CONFIRMED = "CONFIRMED"
SHIPPED = "SHIPPED"
DELIVERED = "DELIVERED"
CANCELLED = "CANCELLED"
@strawberry.type
class OrderItem:
product: Product
quantity: int
unit_price: Decimal
@strawberry.federation.type(keys=["id"])
class Order:
id: strawberry.ID
status: OrderStatus
items: List[OrderItem]
user: User
created_at: datetime
total_amount: Decimal
@strawberry.field
def total_amount(self) -> Decimal:
return sum(
item.unit_price * item.quantity
for item in self.items
)
# Userエンティティを拡張
@strawberry.federation.type(keys=["id"], extend=True)
class UserExtension:
id: strawberry.ID = strawberry.federation.field(external=True)
@strawberry.field
def orders(self, info) -> List[Order]:
return get_orders_by_user(self.id)
@strawberry.field
def order_count(self, info) -> int:
return len(get_orders_by_user(self.id))
@strawberry.field
def total_spent(self, info) -> Decimal:
orders = get_orders_by_user(self.id)
return sum(order.total_amount for order in orders)
@strawberry.type
class Query:
@strawberry.field
def order(self, id: strawberry.ID) -> Optional[Order]:
return get_order_by_id(id)
@strawberry.field
def orders(self, user_id: strawberry.ID) -> List[Order]:
return get_orders_by_user(user_id)
@strawberry.type
class Mutation:
@strawberry.mutation
def create_order(
self,
user_id: strawberry.ID,
items: List[OrderItemInput]
) -> Order:
return create_new_order(user_id, items)
schema = Schema(
query=Query,
mutation=Mutation,
enable_federation_2=True,
)
# FastAPIと統合
from fastapi import FastAPI
from strawberry.fastapi import GraphQLRouter
app = FastAPI()
graphql_app = GraphQLRouter(schema)
app.include_router(graphql_app, prefix="/graphql")
スーパーグラフの構築
Rover CLIによるスキーマ合成
# Rover CLIのインストール
curl -sSL https://rover.apollo.dev/nix/latest | sh
# サブグラフスキーマの取得と検証
rover subgraph introspect http://localhost:4001/graphql > users.graphql
rover subgraph introspect http://localhost:4002/graphql > orders.graphql
rover subgraph introspect http://localhost:4003/graphql > products.graphql
# ローカルでのスキーマ合成
rover supergraph compose --config ./supergraph-config.yaml > supergraph.graphql
# supergraph-config.yaml
federation_version: =2.3.0
subgraphs:
users:
routing_url: http://localhost:4001/graphql
schema:
file: ./users.graphql
orders:
routing_url: http://localhost:4002/graphql
schema:
file: ./orders.graphql
products:
routing_url: http://localhost:4003/graphql
schema:
file: ./products.graphql
Apollo Studioとの連携
# サブグラフをApollo Studioに公開
rover subgraph publish my-graph@production \
--name users \
--schema ./users.graphql \
--routing-url http://users-service:4001/graphql
# スキーマチェック(CI/CDに統合)
rover subgraph check my-graph@production \
--name users \
--schema ./users.graphql
REST APIとの比較
アーキテクチャ比較
REST API マイクロサービス:
┌─────────────────────────────────────────────────────┐
│ クライアント │
│ ・複数エンドポイントを個別に呼び出し │
│ ・レスポンスを自前で結合 │
│ ・オーバーフェッチが発生 │
└─────────────────────────────────────────────────────┘
↓ GET /users/123
↓ GET /users/123/orders
↓ GET /products?ids=1,2,3
┌─────┐ ┌─────┐ ┌─────┐
│Users│ │Orders│ │Products│
│ API │ │ API │ │ API │
└─────┘ └─────┘ └─────┘
GraphQL Federation:
┌─────────────────────────────────────────────────────┐
│ クライアント │
│ ・単一クエリで必要なデータを取得 │
│ ・型安全なレスポンス │
│ ・必要なフィールドのみ取得 │
└─────────────────────────────────────────────────────┘
↓ POST /graphql
┌───────────────┐
│ Apollo Router │
└───────────────┘
↓ ↓ ↓
┌─────┐ ┌─────┐ ┌─────┐
│Users│ │Orders│ │Products│
│Graph│ │Graph │ │ Graph │
└─────┘ └─────┘ └─────┘
実装工数の比較
rest_approach:
client_development:
- 複数APIクライアントの作成
- データ結合ロジックの実装
- エラーハンドリングの統合
- 型定義の手動メンテナンス
estimated_effort: "高"
backend_development:
- 各サービスのREST API設計
- OpenAPI仕様書の作成
- バージョニング戦略
- ドキュメント維持
estimated_effort: "中"
graphql_federation:
client_development:
- 単一GraphQLクライアント
- スキーマから型自動生成
- 統一されたエラーハンドリング
estimated_effort: "低"
backend_development:
- サブグラフスキーマ設計
- リゾルバー実装
- Federation設定
- Router運用
estimated_effort: "中"
データ取得の効率
# GraphQL - 1回のリクエストで全データ取得
query GetUserDashboard($userId: ID!) {
user(id: $userId) {
id
name
email
profile {
avatarUrl
}
orders(first: 5) {
id
status
totalAmount
items {
product {
name
price
images {
url
}
}
quantity
}
}
orderCount
totalSpent
}
}
# REST - 複数リクエストが必要
GET /api/users/123
GET /api/users/123/profile
GET /api/users/123/orders?limit=5
GET /api/products?ids=1,2,3,4,5
# 各注文の商品画像を個別取得...
2025年の動向
主要トレンド
trends_2025:
apollo_router_adoption:
description: "Rust製Routerが主流に"
adoption_rate: "78%"
key_drivers:
- 10倍のパフォーマンス向上
- メモリ効率
- エンタープライズ機能
federation_2_migration:
description: "Federation 2.0への移行完了"
adoption_rate: "85%"
benefits:
- より柔軟なスキーマ設計
- 改善されたエラーメッセージ
- 新ディレクティブ
schema_governance:
description: "スキーマガバナンスの重要性増加"
tools:
- Apollo Studio
- GraphQL Inspector
- Escape.tech
ai_integration:
description: "AI/LLMとの統合"
use_cases:
- 自然言語からGraphQLクエリ生成
- スキーマ設計支援
- パフォーマンス最適化提案
エンタープライズ採用
大規模採用企業の特徴(2025年):
企業規模別採用率:
・Fortune 500企業: 65%がFederation採用
・スタートアップ: 45%がFederation採用
・中堅企業: 38%がFederation採用
業界別:
・Eコマース: 72%
・金融: 58%
・メディア: 61%
・SaaS: 67%
セキュリティ強化
// Apollo Router でのセキュリティ設定例
const securityConfig = {
// レート制限
rateLimit: {
enabled: true,
strategy: "sliding_window",
limit: 1000,
duration: "1m",
},
// クエリ深度制限
queryDepth: {
max: 10,
reject: true,
},
// フィールドコスト分析
costAnalysis: {
enabled: true,
maxCost: 1000,
defaultFieldCost: 1,
defaultListMultiplier: 10,
},
// Persisted Queries
persistedQueries: {
enabled: true,
enforced: true, // 登録済みクエリのみ許可
},
// Introspection制御
introspection: {
enabled: process.env.NODE_ENV !== "production",
},
};
ベストプラクティス
サブグラフ設計
1. ドメイン境界に沿った分割
- ビジネスドメインごとにサブグラフ
- 密結合を避ける
- 独立したデプロイを可能に
2. エンティティ設計
- 適切な@keyの選択
- 必要最小限の@external
- @shareableの慎重な使用
3. スキーマ進化
- 後方互換性の維持
- @deprecatedの活用
- バージョン管理不要
4. パフォーマンス
- DataLoaderでN+1問題を解決
- 適切なキャッシュ戦略
- クエリ複雑度の制限
運用のポイント
operations:
monitoring:
- クエリレイテンシ追跡
- サブグラフ別メトリクス
- エラー率監視
- スロークエリ検出
deployment:
- スキーマチェックをCI/CDに統合
- Blue-Greenデプロイ
- カナリアリリース
- ロールバック戦略
debugging:
- Apollo Studio Traces
- OpenTelemetry統合
- 分散トレーシング
- クエリプラン分析
まとめ
2025年のGraphQL Federationは、マイクロサービス環境でのAPI統合における最も成熟したソリューションとして確立されました。Apollo Federation 2.0とApollo Routerの組み合わせにより、スケーラブルで型安全、かつ高性能な統合APIレイヤーを構築できます。
ドメイン指向のサブグラフ設計、適切なエンティティ参照、そしてセキュリティとパフォーマンスのベストプラクティスを実践することで、複雑なマイクロサービスアーキテクチャを効率的に管理できます。RESTからの移行を検討している組織にとって、Federationは現代的なAPI戦略の有力な選択肢です。
← 一覧に戻る