UI はローカライズ済みです。スキルの紹介と説明文書は選択した言語へ AI が自動翻訳し、コード・コマンド・パス・専門用語は原文のままです。原作者の文書が優先されます。
スキルを探す
デザイン · SKILL.md

api-graphql-federation

api-graphql-federation

Use when the user asks about GraphQL Federation, Apollo Federation, federated schema, subgraphs, supergraph, schema composition, @key/@requires/@external directives, or distributed GraphQL architecture. Do NOT use for: basic GraphQL schema design (backend-graphql-patterns), or single-service GraphQL APIs.

Independent GitHub snapshot · Automated classification · Original documentation governs

スキルの説明

GitHub

name: api-graphql-federation description: > Use when the user asks about GraphQL Federation, Apollo Federation, federated schema, subgraphs, supergraph, schema composition, @key/@requires/@external directives, or distributed GraphQL architecture. Do NOT use for: basic GraphQL schema design (backend-graphql-patterns), or single-service GraphQL APIs. version: "2.0.0" author: "j4flmao" license: "MIT" type: skill compatibility: claude-code: true cursor: true codex: true windsurf: true tags: [api, graphql-federation, phase-3]

GraphQL Federation

Purpose

Design and implement GraphQL Federation: compose multiple subgraphs into a unified supergraph, manage distributed GraphQL architecture, and scale GraphQL across teams. Make informed build-vs-buy decisions between Apollo Federation and alternative distributed graph strategies.

Workflow

Federation Architecture

Supergraph (Apollo Router / Gateway)
  ├── Subgraph A (Users service) — @key(fields: "id")
  ├── Subgraph B (Orders service) — @key(fields: "id") @extends User
  ├── Subgraph C (Reviews service) — @key(fields: "id") @extends User
  └── Subgraph D (Inventory service) — standalone

Apollo Federation vs. GraphQL Mesh Decision Tree

Use this decision framework when choosing between Apollo Federation and GraphQL Mesh:

CriterionApollo FederationGraphQL Mesh
Schema ownershipEach subgraph owns its schemaSources are existing APIs (REST, gRPC, SOAP, etc.)
Team topologyMultiple teams own separate subgraphsSingle team integrating existing backends
Source typesNative GraphQL onlyAny source (REST, OpenAPI, gRPC, SQL, SOAP, etc.)
Composition modelStatic composition via Rover CLIRuntime schema stitching
GatewayApollo Router (Rust) or Gateway (Node.js)Envelop, Yoga, or custom
Federation directives@key, @extends, @requires, @provides, @shareableTransformers, handlers, mesh config
Entity resolutionBuilt-in via __resolveReferenceManual stitching resolvers
Query planningAutomatic query plannerManual or custom merge config
PerformanceOptimized Rust router, plan cachingDepends on underlying gateway
Ecosystem maturityMature (Apollo GraphOS, Studio)Growing (The Guild ecosystem)
When to chooseGreenfield GraphQL with multiple teamsWrapping legacy/heterogeneous backends

Decision flow:

  1. Are all your sources already GraphQL? → Apollo Federation
  2. Do you need to wrap REST/gRPC/SQL backends? → GraphQL Mesh
  3. Do you have 3+ teams owning separate domains? → Apollo Federation
  4. Is your primary goal API unification of legacy systems? → GraphQL Mesh
  5. Hybrid approach: Use Mesh to convert REST → GraphQL, then compose those as subgraphs via Federation

Federation Directives (Federation v2)

DirectivePurposeExample
@keyPrimary key for entity@key(fields: "id")
@extendsExtend type from another subgraph (implied in v2)type User @key(fields: "id")
@externalField defined in another subgraph (implied in v2)id: ID!
@requiresField requires data from another subgraph@requires(fields: "shippingZip")
@providesField provides data to other subgraphs@provides(fields: "name")
@shareableField can be resolved by multiple subgraphs@shareable
@overrideMigrate field resolution between subgraphs@override(from: "inventory")
@inaccessibleHide field from supergraph@inaccessible
@composeDirectivePropagate directive to supergraph@composeDirective(name: "@authorized")
@interfaceObjectExpose interface fields on entity@interfaceObject

Subgraph Schema Example (Federation v2)

# Users subgraph
type User @key(fields: "id") {
    id: ID!
    name: String!
    email: String!
}

# Orders subgraph (extends User — no @extends needed in v2)
type User @key(fields: "id") {
    id: ID!
    orders: [Order!]!
}

type Order @key(fields: "id") {
    id: ID!
    userId: ID!
    total: Float!
    status: OrderStatus!
}

Entity Resolution Deep Dive

Each subgraph that extends an entity must implement __resolveReference:

// Users subgraph — entity origin
const resolvers = {
  User: {
    __resolveReference(ref, context) {
      return context.dataSources.users.findById(ref.id);
    },
  },
};

// Orders subgraph — entity extension
const resolvers = {
  User: {
    __resolveReference(ref, context) {
      // Return just enough to resolve orders
      return { id: ref.id };
    },
    orders(parent, _, context) {
      return context.dataSources.orders.findByUserId(parent.id);
    },
  },
};

Resolution flow:

  1. Router receives query spanning multiple subgraphs
  2. Router sends _entities query to Orders subgraph with representations [{"__typename": "User", "id": "1"}]
  3. Subgraph calls __resolveReference with each representation
  4. Subgraph returns the entity with only the fields requested from it
  5. Router merges fields from all subgraph responses into a unified result

@requires Resolution Flow

# Shipping subgraph needs weight from another subgraph
type Product @key(fields: "id") {
  id: ID!
  weight: Int @external
  shippingCost: Float @requires(fields: "weight")
}
  1. Router fetches weight from the subgraph that owns it
  2. Router includes weight in the representation sent to Shipping subgraph
  3. Shipping subgraph receives {"__typename": "Product", "id": "1", "weight": 10}
  4. Computes shippingCost using the pre-fetched weight

Supergraph Composition Pipeline

┌──────────────┐    ┌──────────────┐    ┌──────────────┐
│ Subgraph A   │    │ Subgraph B   │    │ Subgraph C   │
│ Schema       │    │ Schema       │    │ Schema       │
└──────┬───────┘    └──────┬───────┘    └──────┬───────┘
       │                   │                   │
       └───────────────────┼───────────────────┘
                           ▼
                   ┌───────────────┐
                   │  Composition  │  rover supergraph compose
                   │    Engine     │
                   └───────┬───────┘
                           ▼
                   ┌───────────────┐
                   │   Supergraph  │  unified schema sent to router
                   │    Schema     │
                   └───────────────┘
# supergraph.yaml
federation_version: 2
subgraphs:
  accounts:
    routing_url: http://accounts:4001/graphql
    schema:
      file: ./schemas/accounts.graphql
  products:
    routing_url: http://products:4002/graphql
    schema:
      file: ./schemas/products.graphql
  orders:
    routing_url: http://orders:4003/graphql
    schema:
      file: ./schemas/orders.graphql
rover supergraph compose --config ./supergraph.yaml > supergraph.graphql

Schema Evolution & Breaking Change Detection

# Check backward compatibility before deploying
rover subgraph check my-graph@current \
    --schema ./updated-accounts.graphql \
    --name accounts

# If check passes, publish the new schema
rover subgraph publish my-graph@current \
    --schema ./updated-accounts.graphql \
    --name accounts \
    --routing-url http://accounts:4001/graphql

What rover subgraph check validates:

  • Field/type removals
  • Argument changes
  • @key directive changes
  • Value type to entity conversion
  • Enum value additions/removals

Federated Tracing & Observability

OpenTelemetry in Apollo Router

# router.yaml — federated tracing
telemetry:
  tracing:
    otlp:
      endpoint: http://otel-collector:4318
      protocol: http
    propagation:
      context: cloudtrace
      # Forward trace context to subgraphs
      forward:
        - "traceparent"
        - "x-cloud-trace-context"
    sampling:
      default: 0.1
      subgraphs:
        accounts: 1.0       # Trace all accounts requests
        reviews: 0.01       # Sample reviews at 1%

What federated tracing reveals:

  • Query plan generation — time spent planning the multi-subgraph fetch
  • Per-subgraph latency — which subgraph is the bottleneck
  • Fetch boundary count — number of subgraph hops per query
  • Entity resolution — time spent in __resolveReference
  • Representation passing — overhead of data transfer between services

Apollo Studio Integration

# router.yaml
telemetry:
  apollo:
    graphs:
      - graph_ref: my-graph@current
        key: ${APOLLO_KEY}
    field_usage: true        # Track field-level usage stats
    operation_counts: true   # Track operation frequency

Studio provides:

  • Field usage heatmaps — which fields are most requested
  • Operation traces — waterfall view across subgraphs
  • Schema checks — breaking change detection in CI
  • Cost analysis — query complexity scoring

Security in Federated Architecture

Layered Security Model

Client → Router (authN + rate limit) → Subgraph (authZ + validation)

Router-Level Authentication (JWT)

# router.yaml
authentication:
  jwt:
    jwks_urls:
      - https://auth.example.com/.well-known/jwks.json
    issuer: https://auth.example.com/
    audiences:
      - my-api

headers:
  all:
    request:
      - propagate:
          matching: .*
      - insert:
          name: "x-user-id"
          value: "{{ authentication.jwt.sub }}"
      - insert:
          name: "x-user-roles"
          value: "{{ authentication.jwt.claims.roles }}"

Subgraph-Level Authorization

// Each subgraph independently validates permissions
const resolvers = {
  Query: {
    userOrders: async (_, { userId }, { userId: authUserId, roles }) => {
      if (authUserId !== userId && !roles.includes('admin')) {
        throw new GraphQLError('Forbidden', {
          extensions: { code: 'FORBIDDEN' },
        });
      }
      return db.orders.findByUserId(userId);
    },
  },
  User: {
    __resolveReference(ref, { userId, roles }) {
      // Guard entity resolution — don't leak existence
      if (!userId) return null;
      return db.users.findById(ref.id);
    },
  },
};

Protecting Against GraphQL-Specific Attacks

AttackMitigationConfig
Deeply nested queriesDepth limitingmax_depth: 10
Query aliasing abuseAlias limitingmax_aliases: 15
Entity list bombingMax entities per requestmax_entities_per_request: 100
Introspection scrapingDisable in productionintrospection: false
Costly queriesCost analysisdemand_control.strategy: cost_bound
# router.yaml — demand control
demand_control:
  strategy: cost_bound
  list_cost: 1
  object_cost: 2
  scoring:
    max_cost: 1000
    max_depth: 10
  reject_on_limit_exceeded: true

rate_limiting:
  global:
    capacity: 1000
    time_window: 60s
  per_user:
    capacity: 100
    time_window: 60s

Production Considerations

Traffic Shaping Per Subgraph

traffic_shaping:
  all:
    timeout: 30s
    compression: true
    http2:
      keepalive_interval: 30s
      keepalive_timeout: 10s
  subgraphs:
    accounts:
      timeout: 5s
      retry:
        max_retries: 3
        base_interval: 100ms
    reviews:
      timeout: 3s
      circuit_breaker:
        error_threshold: 0.5
        request_volume_threshold: 20
        sleep_window: 30s
        half_open_requests: 5
    inventory:
      timeout: 10s
      retry:
        max_retries: 2

Query Plan Caching

query_planning:
  cache:
    enabled: true
    size: 10000
    ttl: 3600s
  experimental_plans: false
  incremental_delivery:
    enable_single_entity: true

Warm the cache on deploy:

// Pre-warm common query plans
const commonQueries = [
  `query { me { id name } }`,
  `query { products(first: 10) { id name price } }`,
  `query { order(id: "hot") { id status total } }`,
];
await Promise.all(commonQueries.map(q => router.execute(q)));

Blue-Green Supergraph Deployment

# 1. Compose new supergraph
rover supergraph compose --config ./supergraph.v2.yaml --output supergraph.v2.graphql

# 2. Deploy to staging router
cp supergraph.v2.graphql /etc/apollo/supergraph.staging.graphql

# 3. Health check staging router
curl -f http://localhost:4001/.well-known/apollo/server-health

# 4. Promote to production
cp supergraph.v2.graphql /etc/apollo/supergraph.graphql

# 5. Reload production router
kill -HUP $(cat /var/run/apollo-router.pid)

Performance Budgets

performance_budget:
  max_fetch_count: 5        # Max subgraph hops per query
  max_query_depth: 8        # Max nesting depth
  max_latency_p99_ms: 500   # P99 latency across all subgraphs
  max_cost_per_query: 100   # Cost analysis limit

Reducing Fetch Boundaries with @provides

# Products subgraph: users browsing products don't hit accounts
type Query {
  topProducts: [Product!]!
}

extend type Product @key(fields: "id") {
  id: ID! @external
  name: String! @external @provides(fields: "name")
  price: Float! @provides(fields: "currency")
}

DataLoader Across Subgraphs

// Batch-load entities to avoid N+1 resolution calls
class UserLoader {
  private batch = new Map<string, Promise<User>>();

  load(id: string): Promise<User> {
    if (!this.batch.has(id)) {
      this.batch.set(id, this.fetchBatch());
    }
    return this.batch.get(id)!;
  }

  private async fetchBatch(): Promise<void> {
    const ids = [...this.batch.keys()];
    const users = await db.users.findByIds(ids);
    for (const user of users) {
      this.batch.set(user.id, Promise.resolve(user));
    }
  }
}

Testing Federated Graphs

Unit Test: Subgraph Entity Resolution

describe('Accounts Subgraph', () => {
  it('resolves User entity by key', async () => {
    const result = await subgraph.executeQuery(`
      query ($representations: [_Any!]!) {
        _entities(representations: $representations) {
          ... on User { id name email }
        }
      }
    `, {
      representations: [{ __typename: 'User', id: '1' }],
    });

    expect(result.data._entities[0].name).toBe('Alice');
  });
});

Integration Test: Cross-Subgraph Query

describe('Supergraph: User with Orders', () => {
  it('resolves fields across subgraphs', async () => {
    const query = `
      query { user(id: "1") { name orders { total status } } }
    `;
    const result = await gateway.execute(query);
    expect(result.data.user.name).toBe('Alice');
    expect(result.data.user.orders).toHaveLength(3);
  });
});

Contract Testing Between Subgraphs

// Each subgraph publishes its schema contract
// CI validates that contracts remain compatible
describe('Contracts', () => {
  it('accounts subgraph schema is valid', async () => {
    const schema = fs.readFileSync('./schemas/accounts.graphql', 'utf8');
    const errors = await validateSchema(schema);
    expect(errors).toHaveLength(0);
  });

  it('orders extension of User is compatible', () => {
    // Verify @key fields match across subgraphs
    const userKey = extractKey(accountsSchema, 'User');
    const orderUserKey = extractKey(ordersSchema, 'User');
    expect(userKey).toEqual(orderUserKey);
  });
});

Common Composition Errors

ErrorCauseFix
ENUM_MISMATCHEnum values differ between subgraphsAlign enum definitions
TYPE_MISMATCHSame name used for type vs interfaceUnify type kind
EXTERNAL_MISSINGReferenced field not @externalMark field as external
KEY_MISSINGType extended but no @key in originAdd @key to origin
REQUIRES_MISSING@requires field unavailableEnsure field is resolvable
DUPLICATE_FIELDField defined in multiple subgraphs without @shareableAdd @shareable

Migration Guide: Federation 1 → Federation 2

Federation 1Federation 2
@extendsUnnecessary (all type extensions are implicit)
@externalUnnecessary (fields owned elsewhere assumed external)
gateway.js@apollo/gateway v2+ handles automatically
Compositionrover supergraph compose with federation_version: 2

Steps:

  1. Update federation_version to 2 in supergraph config
  2. Remove all @extends and @external directives from subgraph schemas
  3. Update to @apollo/gateway@^2.0 or deploy Apollo Router
  4. Run rover supergraph compose to validate
  5. Deploy new supergraph

Key Metrics Dashboard

MetricSourceWhat It Reveals
apollo_router_query_planning_duration_msRouterPlan cache hit/miss
apollo_router_subgraph_request_duration_msRouterPer-subgraph health
apollo_router_fetch_countRouterSchema design efficiency
apollo_router_cache_hit_ratioRouterCache tuning needed
apollo_router_error_rateRouterSubgraph failures
apollo_router_entities_per_requestRouterEntity list bombing
Subgraph P99 latencySubgraph metricsData-level bottlenecks

Team Topology Patterns

Subgraph Ownership Models

ModelDescriptionTeam StructureWhen to Use
Domain-alignedOne subgraph per bounded contextEach domain team owns 1 subgraphClear domain boundaries, DDD org
Split by volatilitySeperate read vs write subgraphsPlatform + domain teamsHigh read/write asymmetry
API gateway patternSubgraph wraps existing REST/gRPCIntegration team owns gatewayLegacy system integration
Shared subgraphCross-cutting concerns (auth, audit)Platform teamtruly shared capabilities

Team API Contract

team_contract:
  subgraph_owners:
    accounts: team-identity
    orders: team-commerce
    catalog: team-product
    reviews: team-engagement
    inventory: team-fulfillment

  communication:
    - Subgraph schema changes communicated 2 weeks in advance
    - Breaking changes require API council approval
    - Monthly cross-team schema sync meeting
    - Shared Slack channel for federation alerts

  dependencies:
    - Teams maintain their subgraph's @key definitions
    - Extension subgraphs coordinate with origin subgraph owners
    - Schema registry (Apollo GraphOS) tracks all published schemas

Schema Lifecycle Management

Subgraph Schema Version Flow

Development → PR Review → Contract Tests → Staging → Canary → Production
                                                                   ↓
                                                            Apollo GraphOS
                                                          Schema Registry
                                                                   ↓
                                                         Supergraph Compose
                                                                   ↓
                                                         Router Deploy

Schema Change Notification

schema_change_process:
  additive: # New fields, types, endpoints
    notification: optional (Slack channel)
    review: none required
    deploy: any time

  breaking: # Field removal, type change, @key change
    notification: required (2 weeks minimum)
    review: API council approval
    approval_period: 2 weeks
    deploy: coordinated rollout with all affected teams
    rollback: pre-approved rollback plan required

Schema Tagging Strategy

schema_tags:
  - tag: current
    description: Currently deployed supergraph schema
    updated_on: each successful deployment

  - tag: candidate
    description: Next schema to be deployed (canary)
    updated_on: after composition validation

  - tag: previous
    description: Previously deployed schema (for rollback)
    updated_on: each deployment (rotate: keep last 3)

  - tag: stable-{date}
    description: Snapshot of stable schemas
    updated_on: monthly

Incident Management for Federated Graphs

Incident Classification

SeverityDefinitionResponse TimeExample
P0Complete supergraph outage< 5 minRouter crash, composition failure
P1Subgraph degradation< 15 minHigh latency in Orders subgraph
P2Isolated field errors< 1 hourSpecific resolver returns errors
P3Non-critical anomalies< 1 daySuboptimal query plan

Incident Response Playbook

playbook:
  p0_outage:
    detection:
      - Error rate > 5% spike (Prometheus alert)
      - Health check failure
      - PagerDuty escalation
    immediate:
      - Check router health: curl -f http://router:8088/health
      - Check last supergraph deployment: git log --oneline -1
      - Rollback to previous supergraph:
          cp /etc/apollo/supergraph.previous.graphql /etc/apollo/supergraph.graphql
          kill -HUP $(cat /var/run/apollo-router.pid)
      - Verify recovery: curl -f http://router:4000/.well-known/apollo/server-health
    investigation:
      - Check router logs for composition errors
      - Check Apollo Studio for failed operations
      - Check subgraph health endpoints individually
    resolution:
      - Isolate failing subgraph or schema change
      - Deploy hotfix or revert change
      - Post-mortem within 24 hours

  p1_subgraph_degradation:
    detection:
      - Subgraph P99 > 500ms (Grafana alert)
      - Subgraph error rate > 2%
    immediate:
      - Enable circuit breaker for degraded subgraph
      - Reduce timeout for affected subgraph
      - Scale subgraph horizontally if possible
    investigation:
      - Review subgraph deployment timeline
      - Check DB connection pool saturation
      - Review recent code changes to subgraph
    resolution:
      - Apply subgraph-level fix
      - Monitor for 1 hour post-fix
      - Restore normal traffic_shaping config

Advanced Tracing with Custom Spans

Router Rhai Telemetry Script

// router.rhai — custom span injection
fn supergraph_service(service) {
    service.map_request(|request| {
        // Tag requests by client type
        let user_agent = request.router_request.headers["user-agent"];
        if user_agent contains "MobileApp" {
            request.context.client_type = "mobile";
        } else if user_agent contains "WebKit" {
            request.context.client_type = "web";
        } else {
            request.context.client_type = "api";
        }
        request
    });

    service.map_response(|response| {
        // Log slow queries
        if response.context.duration > 1000 {
            log_warning(`Slow query detected: ${response.context.operation_name}`);
        }
        response
    });
}

Custom Subgraph Span Attributes

const resolvers = {
  Query: {
    users: async (_, __, { tracer }) => {
      return tracer.startActiveSpan('users.query', (span) => {
        span.setAttribute('db.system', 'postgresql');
        span.setAttribute('db.table', 'users');
        span.setAttribute('query.plan', 'index_scan');
        return db.users.findAll();
      });
    },
  },
  User: {
    __resolveReference: async (ref, { tracer }) => {
      return tracer.startActiveSpan('entity_resolution', (span) => {
        span.setAttribute('entity.type', 'User');
        span.setAttribute('entity.id', ref.id);
        span.setAttribute('resolver', 'primary');
        return db.users.findById(ref.id);
      });
    },
  },
};

References

保存したスキル