GraphQL Federation 2025 - マイクロサービスを統合するAPI層

2026.01.12

公式ドキュメント

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):

メトリクスGatewayRouter
スループット5,000 RPS50,000 RPS
レイテンシ (p50)45ms8ms
レイテンシ (p99)200ms25ms
メモリ使用量512MB50MB
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によるスキーマ合成

# Apollo Rover CLI for schema composition
# Reference: https://www.apollographql.com/docs/rover/

# 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 integration for production deployment
# Reference: https://www.apollographql.com/docs/graphos/

# サブグラフを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 - multiple requests required for same data
# Reference: Traditional REST architecture pattern

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

 query_assistance:
 description: "クエリ作成支援との統合"
 use_cases:
- 開発者向けクエリテンプレート生成
 - スキーマ設計支援
 - パフォーマンス最適化提案

エンタープライズ採用

大規模採用企業の特徴(2025年):

企業規模別採用率:

  • Fortune 500企業: 65%がFederation採用
  • スタートアップ: 45%がFederation採用
  • 中堅企業: 38%がFederation採用

業界別:

  • Eコマース: 72%
  • 金融: 58%
  • メディア: 61%
  • SaaS: 67%

注意: 本番環境ではIntrospectionの無効化、クエリ深度制限、Persisted Queriesの適用など、GraphQL固有のセキュリティ対策が必須です

セキュリティ強化

// 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",
 },
};

ベストプラクティス

実践メモ: サブグラフはビジネスドメインの境界に沿って分割し、独立してデプロイできる設計にしましょう。DataLoaderでN+1問題の解決も忘れずに。

サブグラフ設計

サブグラフ設計の原則:

  1. ドメイン境界に沿った分割
  • ビジネスドメインごとにサブグラフ
  • 密結合を避ける
  • 独立したデプロイを可能に
  1. エンティティ設計
  • 適切な@keyの選択
  • 必要最小限の@external
  • @shareableの慎重な使用
  1. スキーマ進化
  • 後方互換性の維持
  • @deprecatedの活用
  • バージョン管理不要
  1. パフォーマンス
  • DataLoaderでN+1問題を解決
  • 適切なキャッシュ戦略
  • クエリ複雑度の制限

運用のポイント

operations:
 monitoring:
 - クエリレイテンシ追跡
 - サブグラフ別メトリクス
 - エラー率監視
 - スロークエリ検出

 deployment:
 - スキーマチェックをCI/CDに統合
 - Blue-Greenデプロイ
 - カナリアリリース
 - ロールバック戦略

 debugging:
 - Apollo Studio Traces
 - OpenTelemetry統合
 - 分散トレーシング
 - クエリプラン分析

参考: Apollo GraphQL Federation Documentation

まとめ

2025年のGraphQL Federationは、マイクロサービス環境でのAPI統合における最も成熟したソリューションとして確立されました。Apollo Federation 2.0とApollo Routerの組み合わせにより、スケーラブルで型安全、かつ高性能な統合APIレイヤーを構築できます。

ドメイン指向のサブグラフ設計、適切なエンティティ参照、そしてセキュリティとパフォーマンスのベストプラクティスを実践することで、複雑なマイクロサービスアーキテクチャを効率的に管理できます。RESTからの移行を検討している組織にとって、Federationは現代的なAPI戦略の有力な選択肢です。

参考リソース

← 一覧に戻る
PR
PR
PR
PR