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

┌────────────────────┬──────────────┬──────────────┐
│ メトリクス          │ 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統合
    - 分散トレーシング
    - クエリプラン分析

参考: Apollo GraphQL Federation Documentation

まとめ

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

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

この技術を体系的に学びたいですか?

未来学では東証プライム上場企業のITエンジニアが24時間サポート。月額24,800円から、退会金0円のオンラインIT塾です。

LINEで無料相談する
← 一覧に戻る