Skip to content
SP StackPractices
advanced Por StackPractices

Guía Completa de GraphQL Federation

Construye APIs GraphQL unificadas across múltiples servicios con Apollo Federation. Cubre subgraphs, supergraph composition, entity resolution y gateway deployment.

Nota para desarrolladores hispanohablantes: Esta guía incluye ejemplos y convenciones de nomenclatura adaptadas a equipos que trabajan en español. Cuando existen diferencias significativas en terminología técnica entre el inglés y el español, se indican explícitamente para facilitar la comunicación en equipos multiculturales.

Guía Completa de GraphQL Federation

Introducción

GraphQL Federation te permite splitir una API GraphQL grande across múltiples servicios (subgraphs) mientras expones una API unificada a través de un gateway. Cada equipo posee su subgraph, define sus types, y la capa de federation los compone en un supergraph. Esta guía cubre setup de subgraph, supergraph composition, entity resolution y gateway deployment usando Apollo Federation.

Arquitectura de Federation

Client → Gateway (Supergraph) → Subgraph A (Users)
                              → Subgraph B (Orders)
                              → Subgraph C (Products)
  • Subgraph: Un servicio GraphQL poseído por un equipo, que define parte del schema
  • Supergraph: El schema compuesto de todos los subgraphs
  • Gateway: El entry point que rutear queries a los subgraphs apropiados
  • Entity: Un type compartido con un key field que múltiples subgraphs pueden referenciar y extender

Setup de Subgraph

Subgraph de Users (Node.js)

const { buildSubgraphSchema } = require("@apollo/subgraph");
const { gql, ApolloServer } = require("apollo-server-express");

const typeDefs = gql`
  type User @key(fields: "id") {
    id: ID!
    name: String!
    email: String!
    orders: [Order!]!
  }

  extend type Order @key(fields: "id") {
    id: ID! @external
    user: User! @provides(fields: "name")
  }

  type Query {
    user(id: ID!): User
    users: [User!]!
  }
`;

const resolvers = {
  User: {
    orders(user) {
      return fetch(`http://orders-service/orders?userId=${user.id}`)
        .then((res) => res.json());
    },
  },
  Query: {
    user: (_, { id }) => fetch(`http://users-service/users/${id}`).then((res) => res.json()),
    users: () => fetch("http://users-service/users").then((res) => res.json()),
  },
};

const server = new ApolloServer({
  schema: buildSubgraphSchema([{ typeDefs, resolvers }]),
});

server.listen({ port: 4001 }).then(({ url }) => {
  console.log(`Users subgraph ready at ${url}`);
});

Subgraph de Orders (Node.js)

const typeDefs = gql`
  type Order @key(fields: "id") {
    id: ID!
    total: Float!
    status: String!
    userId: ID!
    user: User!
    items: [OrderItem!]!
  }

  type OrderItem {
    productId: ID!
    quantity: Int!
    price: Float!
  }

  extend type User @key(fields: "id") {
    id: ID! @external
    orders: [Order!]! @external
  }

  extend type Product @key(fields: "id") {
    id: ID! @external
    orders: [OrderItem!]!
  }

  type Query {
    order(id: ID!): Order
    orders: [Order!]!
  }

  type Mutation {
    createOrder(userId: ID!, items: [OrderItemInput!]!): Order!
  }

  input OrderItemInput {
    productId: ID!
    quantity: Int!
  }
`;

const resolvers = {
  Order: {
    user(order) {
      return { __typename: "User", id: order.userId };
    },
    items(order) {
      return order.items;
    },
  },
  Product: {
    orders(product) {
      return fetch(`http://orders-service/orders/items?productId=${product.id}`)
        .then((res) => res.json());
    },
  },
  Query: {
    order: (_, { id }) => fetch(`http://orders-service/orders/${id}`).then((res) => res.json()),
    orders: () => fetch("http://orders-service/orders").then((res) => res.json()),
  },
  Mutation: {
    createOrder: (_, { userId, items }) => {
      return fetch("http://orders-service/orders", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ userId, items }),
      }).then((res) => res.json());
    },
  },
};

Subgraph de Products (Python)

from ariadne import QueryType, make_federated_schema, ObjectType
from ariadne.asgi import GraphQL
import httpx

type_defs = """
    type Product @key(fields: "id") {
        id: ID!
        name: String!
        price: Float!
        description: String
    }

    extend type OrderItem @key(fields: "productId") {
        productId: ID! @external
        product: Product
    }

    type Query {
        product(id: ID!): Product
        products: [Product!]!
    }
"""

query = QueryType()
product_obj = ObjectType("Product")

@query.field("product")
async def resolve_product(_, info, id):
    async with httpx.AsyncClient() as client:
        resp = await client.get(f"http://products-service/products/{id}")
        return resp.json()

@query.field("products")
async def resolve_products(_, info):
    async with httpx.AsyncClient() as client:
        resp = await client.get("http://products-service/products")
        return resp.json()

@product_obj.field("__resolve_reference")
async def resolve_product_reference(reference, info):
    async with httpx.AsyncClient() as client:
        resp = await client.get(f"http://products-service/products/{reference['id']}")
        return resp.json()

schema = make_federated_schema(type_defs, [query, product_obj])
app = GraphQL(schema, debug=True)

Setup de Gateway

const { ApolloGateway } = require("@apollo/gateway");
const { ApolloServer } = require("apollo-server-express");

const gateway = new ApolloGateway({
  serviceList: [
    { name: "users", url: "http://localhost:4001/graphql" },
    { name: "orders", url: "http://localhost:4002/graphql" },
    { name: "products", url: "http://localhost:4003/graphql" },
  ],
  debug: true,
});

const server = new ApolloServer({
  gateway,
  subscriptions: false,
});

server.listen({ port: 4000 }).then(({ url }) => {
  console.log(`Gateway ready at ${url}`);
});

Supergraph Composition

# Instalar Rover CLI
curl -sSL https://rover.apollo.dev/nix/latest | sh

# Componer supergraph desde schemas de subgraphs
rover supergraph compose --config supergraph.yaml > supergraph.graphql
# supergraph.yaml
federation_version: =2.8.0
subgraphs:
  users:
    routing_url: http://localhost:4001/graphql
    schema:
      subgraph_url: http://localhost:4001/graphql
  orders:
    routing_url: http://localhost:4002/graphql
    schema:
      subgraph_url: http://localhost:4002/graphql
  products:
    routing_url: http://localhost:4003/graphql
    schema:
      subgraph_url: http://localhost:4003/graphql

Entity Resolution

Las entities son el core de federation. Permiten a los subgraphs referenciar types poseídos por otros subgraphs.

@key — definir una entity

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

@extends — extender una entity de otro subgraph

extend type User @key(fields: "id") {
  id: ID! @external
  orders: [Order!]!
}

@requires — computar fields basados en fields externos

extend type Product @key(fields: "id") {
  id: ID! @external
  price: Float! @external
  discountedPrice: Float! @requires(fields: "price")
}

@provides — indicar que un subgraph puede proveer fields de otro type

extend type Order @key(fields: "id") {
  id: ID! @external
  user: User! @provides(fields: "name")
}

@shareable — permitir que un field sea resuelto por múltiples subgraphs

type Product @key(fields: "id") {
  id: ID! @shareable
  name: String! @shareable
}

Querying el Federated Graph

# Esta query spanea los tres subgraphs:
# 1. Gateway envía user query al subgraph de Users
# 2. Gateway envía orders query al subgraph de Orders (usando user.id como entity key)
# 3. Gateway envía product query al subgraph de Products (usando orderItem.productId como entity key)

query GetUserWithOrders {
  user(id: "1") {
    id
    name
    email
    orders {
      id
      total
      status
      items {
        quantity
        product {
          name
          price
        }
      }
    }
  }
}

Pautas

  • Un subgraph por equipo — los boundaries de ownership matchean los boundaries de equipo
  • Usar entities para types compartidos@key en types referenciados across subgraphs
  • Mantener subgraphs independientes — cada subgraph debería funcionar standalone
  • Usar @external para fields externos — nunca duplicar definiciones de fields
  • Evitar dependencias circulares — subgraph A extiende User, subgraph B extiende Order, no ambos extendiéndose mutuamente
  • Usar Rover para composition — validar cambios de schema antes de deployar
  • Cachear entity resolution — el gateway llama __resolveReference frecuentemente
  • Monitorear query plans — entender cómo el gateway split queries across subgraphs
  • Usar managed federation (Apollo Studio) — trackear cambios de schema y errores de composition
  • Versionar subgraphs independientemente — el gateway maneja composition, no subgraphs individuales
  • Manejar fallos de subgraph gracefulmente — usar partial results y error extensions
  • Setear timeouts en calls a subgraphs — un subgraph lento no debería bloquear toda la query

Errores Comunes

  • Definir el mismo field en múltiples subgraphs sin @shareable — composition falla
  • No implementar __resolveReference — entity lookups retornan null
  • Crear acoplamiento tight entre subgraphs — derrota el propósito de federation
  • No manejar downtime de subgraph — el gateway errorea en lugar de retornar partial data
  • Usar @requires con fields no externos — la validación de composition falla
  • No testear composition localmente — conflictos de schema aparecen solo en producción
  • Overusar @shareable — derrota boundaries de ownership
  • No monitorear performance de query plans — N+1 entity resolution mata latencia
  • Exponer IDs internos across boundaries de subgraph — leakea detalles de implementación
  • No usar DataLoader para batching de entities — una query triggera cientos de calls a subgraphs

Preguntas Frecuentes

¿Cuál es la diferencia entre schema stitching y federation?

Schema stitching combina schemas manualmente con custom resolvers. Federation usa un protocolo estandarizado (@key, @extends, __resolveReference) para que los subgraphs declaren sus relaciones declarativamente. Federation es el enfoque recomendado para proyectos nuevos — es más mantenible y tiene mejor tooling.

¿Cómo maneja el gateway una query que spanea múltiples subgraphs?

El gateway construye un query plan. Para una query que fetchea un user y sus orders, primero llama al subgraph de Users para el user, luego usa el id del user como entity key para llamar al subgraph de Orders. El gateway joinea los resultados y retorna una sola response al client.

¿Puedo usar federation sin Apollo?

Sí. Federation es una spec abierta. Las alternativas incluyen Apollo Gateway (Node.js), Apollo Router (Rust) y gateways custom. El protocolo de subgraph es language-agnostic — puedes construir subgraphs en Python (Ariadne, Strawberry), Java (DGS), Go (gqlgen) y Ruby (graphql-ruby).