Patrón de entidades federadas en GraphQL
Compartí una entidad entre subgraphs de Apollo Federation. Usá @key, @external y @shareable para que cada servicio maneje los campos que conoce.
En Apollo Federation, una entidad es un object type al que varios subgraphs pueden contribuir al mismo tiempo. Un servicio posee los campos base, otros agregan los suyos y el gateway fusiona todo en un único tipo GraphQL.
Usé este patrón en producción con 8 subgraphs y es la forma más limpia de dividir un grafo entre equipos sin un esquema monolítico. La alternativa — schema stitching — funciona, pero mete demasiada lógica de glue en el gateway. Federation mantiene cada subgraph enfocado en los datos que ya maneja.
Este patrón mantiene cada subgraph enfocado en los datos que ya maneja — un servicio de usuarios
posee User.name, un servicio de órdenes agrega User.orders y uno de reseñas agrega
User.reviews. El cliente consulta un único esquema, pero el gateway direcciona cada campo al
servicio correcto. Para un análisis más profundo, mirá la guía completa de GraphQL Federation.
Cuándo Usarlo
- Diferentes microservicios poseen distintos campos del mismo objeto de dominio.
- Querés una API GraphQL unificada sin construir un esquema monolítico.
- El schema stitching se siente demasiado manual o propenso a errores.
- Los equipos necesitan desplegar y ser dueños de su parte del grafo de forma independiente.
Cuándo NO Usarlo
- La entidad solo se lee o escribe desde un servicio. Saltá federation — un esquema GraphQL común es más simple y evita el overhead.
- Los subgraphs están fuertemente acoplados y comparten la misma base de datos. Federation agrega complejidad innecesaria.
- Todavía estás en un servidor GraphQL que no soporta el spec de Apollo Federation.
- Necesitás paginación basada en connections en campos federados — funciona, pero los query plans se vuelven costosos rápido.
Solución
Subgraph A: Servicio de Usuarios (posee la entidad)
El subgraph base declara la entidad con @key e implementa __resolveReference para que el gateway
pueda obtenerla por su clave.
import { ApolloServer } from '@apollo/server';
import { buildSubgraphSchema } from '@apollo/subgraph';
import { startStandaloneServer } from '@apollo/server/standalone';
const typeDefs = `#graphql
type User @key(fields: "id") {
id: ID!
name: String!
email: String!
}
`;
const resolvers = {
User: {
__resolveReference(user: { id: string }) {
return userService.getById(user.id);
},
},
Query: {
user(_: unknown, { id }: { id: string }) {
return userService.getById(id);
},
},
};
const server = new ApolloServer({
schema: buildSubgraphSchema([{ typeDefs, resolvers }]),
});
const { url } = await startStandaloneServer(server, {
listen: { port: 4001 },
});
console.log(`Servicio de usuarios listo en ${url}`);
Subgraph B: Servicio de Órdenes (extiende la entidad)
El subgraph de órdenes no posee User, pero le agrega User.orders. El campo id se marca como
@external porque otro servicio lo posee. resolvable: false indica que este subgraph no puede
traer un User por clave por sí solo.
const typeDefs = `#graphql
type Order @key(fields: "id") {
id: ID!
userId: ID!
total: Float!
}
type User @key(fields: "id", resolvable: false) {
id: ID! @external
orders: [Order!]!
}
`;
const resolvers = {
User: {
orders(user: { id: string }) {
return orderService.getByUserId(user.id);
},
},
Query: {
order(_: unknown, { id }: { id: string }) {
return orderService.getById(id);
},
},
};
Subgraph C: Servicio de Reseñas (extiende la entidad nuevamente)
Otro subgraph también puede extender User. Cada uno declara solo los campos que conoce.
const typeDefs = `#graphql
type Review @key(fields: "id") {
id: ID!
productId: ID!
rating: Int!
comment: String
}
type User @key(fields: "id", resolvable: false) {
id: ID! @external
reviews: [Review!]!
}
`;
const resolvers = {
User: {
reviews(user: { id: string }) {
return reviewService.getByUserId(user.id);
},
},
};
Gateway
El gateway consulta cada subgraph por su esquema, arma el supergraph y enruta campos automáticamente. Para una guía completa de setup con Docker, mirá la receta de federation gateway setup.
import { ApolloGateway, IntrospectAndCompose } from '@apollo/gateway';
import { ApolloServer } from '@apollo/server';
import { startStandaloneServer } from '@apollo/server/standalone';
const gateway = new ApolloGateway({
supergraphSdl: new IntrospectAndCompose({
subgraphs: [
{ name: 'users', url: 'http://localhost:4001/graphql' },
{ name: 'orders', url: 'http://localhost:4002/graphql' },
{ name: 'reviews', url: 'http://localhost:4003/graphql' },
],
}),
});
const server = new ApolloServer({ gateway });
const { url } = await startStandaloneServer(server, {
listen: { port: 4000 },
});
console.log(`Gateway listo en ${url}`);
Consulta del Cliente
El cliente ve un único tipo User, aunque los campos provengan de tres servicios.
query GetUserWithOrdersAndReviews {
user(id: "123") {
id
name
email
orders {
id
total
}
reviews {
id
rating
comment
}
}
}
Explicación
Federation resuelve esa consulta en tres pasos:
- El gateway pide al subgraph de usuarios
user(id: "123")y los campos baseid,name,email. - Envía el
ida los subgraphs de órdenes y reseñas a través de la query_entities, pidiendoordersyreviews. - Fusiona los resultados en un único objeto
Usery lo devuelve al cliente.
Las piezas clave son:
@key(fields: "id")— declara cómo el gateway identifica la entidad entre subgraphs. Pensalo como la clave primaria para el ruteo de federation.@external— marca un campo que este subgraph usa pero no posee.resolvable: false— señala que este subgraph no puede resolver la entidad por clave por sí solo; solo aporta campos cuando otro subgraph aporta la clave.__resolveReference— el resolver que el gateway invoca para obtener una entidad por su clave. Lo implementás en el subgraph propietario; los subgraphs extensores no lo necesitan.
Federation 1 vs Federation 2
Apollo Federation 2 (lanzado en 2022) simplificó varias reglas. Los cambios más importantes desde Federation 1:
- No se necesita
@extends: En Federation 1, extender una entidad requeríaextend type User. Federation 2 lo infiere de@key+@external. El código de este patrón funciona con Federation 1 y 2. @shareablees opt-in en Federation 1, default en Federation 2 para campos definidos en dos o más subgraphs. En Federation 2, marcás los campos no compartibles con@inaccessible.- Claves de un solo campo: Federation 2 permite que los subgraphs resuelvan entidades usando solo un subconjunto de los campos clave, lo cual no era posible en Federation 1.
Si estás arrancando un proyecto nuevo hoy, Federation 2 es el camino a seguir. La sintaxis es más limpia y el tooling (Rover, Apollo Studio) viene configurado por defecto.
Debuggear query plans de federation
El problema más común en producción con federation es el N+1 en __resolveReference. El gateway
lo llama una vez por cada referencia de entidad, así que una query que devuelve 100 usuarios con
orders dispara 100 llamadas separadas al subgraph de órdenes. Una vez debuggeé un query plan que
hacía 47 llamadas a subgraphs porque alguien olvidó @external en el campo id — el gateway no
podía batchear las referencias.
Para diagnosticar query plans:
- Apollo Studio — el query plan viewer muestra la secuencia exacta de llamadas a subgraphs.
Buscá llamadas repetidas a
_entitiesque se podrían batchear. rover supergraph compose— valida la composición del esquema localmente antes de deployear. Atrapa errores de@external/@keytemprano.- Logs del gateway — habilitá
debug: trueen la config del gateway para loguear query plans a stdout. - Datadog / OpenTelemetry — traceá el span
_entitiespara ver cuántas veces se llama cada subgraph por query.
Migrar campos con @override
Federation 2 introdujo @override para mover un campo de un subgraph a otro sin downtime:
type User @key(fields: "id") {
id: ID!
# El subgraph viejo todavía resuelve este campo
displayName: String! @override(from: "users")
}
El subgraph nuevo toma la resolución gradualmente. Una vez que todo el tráfico usa el resolver
nuevo, eliminá el campo del subgraph viejo. Usé esto para migrar un campo User.avatarUrl de un
servicio legacy a un servicio nuevo de media sin un solo deploy window — el gateway rutea a
cualquier subgraph que esté activo.
Variantes
| Variante | Directiva | Caso de Uso |
|---|---|---|
| Clave simple | @key(fields: "id") | La mayoría de entidades con un ID primario |
| Clave compuesta | @key(fields: "tenantId id") | Entidades multi-tenant o particionadas |
| Múltiples claves | @key(fields: "id") @key(fields: "email") | Búsqueda por más de un identificador |
| Campo calculado | @requires(fields: "address") | Un campo que necesita datos de otro subgraph primero |
| Campo compartido | @shareable | Un campo resoluble por más de un subgraph |
Ejemplo de clave compuesta
type User @key(fields: "tenantId id") {
tenantId: ID!
id: ID!
name: String!
}
Ejemplo de @requires
El subgraph de envío calcula shippingCost a partir del address del usuario, que pertenece al
subgraph de usuarios.
type User @key(fields: "id", resolvable: false) {
id: ID! @external
address: String! @external
shippingCost: Float! @requires(fields: "address")
}
El gateway obtiene address primero y luego lo pasa al resolver de envío.
Buenas Prácticas
- Mantené una sola definición base por entidad. Los otros subgraphs agregan campos, pero solo un servicio posee el tipo core.
- Agrupá llamadas en
__resolveReference. El gateway lo llama una vez por cada referencia de entidad, así que una query separada por entidad no escala — batchealas. - Marcá los campos clave como
@externalcuando el subgraph no los posea. Olvidar esto genera un error de validación de esquema. - Agregá nuevas claves antes de eliminar las viejas. Cambiar una clave en vuelo rompe los query plans existentes que los clientes puedan tener cacheados.
- Usá Apollo Studio o el query plan viewer del gateway para verificar que los campos pasen por los subgraphs correctos.
Errores Comunes
- Faltar
__resolveReferenceen el subgraph propietario. Sin este resolver, el gateway no tiene forma de obtener la entidad por clave — las queries que tocan el tipo base van a fallar. - Olvidar
@externalen el campoidde una extensión. El gateway rechazará el esquema. - Declarar el tipo base
Useren dos subgraphs. Solo un subgraph debe poseer la definición base. - Devolver campos extra en
__resolveReference. Solo devolvé lo que el resolver pidió; el gateway obtiene el resto de los otros subgraphs. - Referencias circulares profundas sin límites.
User.orders.user.orderspuede explotar los query plans — agregá límites de profundidad o paginación para prevenirlo.
Ver También
- Apollo Federation docs — documentación oficial
- Apollo Federation directives spec — @key, @external, @requires, @shareable, @override
- Apollo Studio query plan viewer — debuggear ruteo de subgraphs
- Rover CLI — composición y publicación de esquemas
- Anuncio de GraphQL Federation 2 — qué cambió desde Federation 1
- Patrón schema stitching — enfoque alternativo para fusión de esquemas
- Setup de federation gateway — configuración práctica del gateway
Preguntas frecuentes
¿En qué se diferencia federation de schema stitching?
Federation usa directivas y el protocolo _entities para fusionar esquemas de forma declarativa.
Schema stitching escribe resolvers personalizados en el gateway para delegar campos manualmente.
Federation es más limpio para servicios Apollo; stitching es más flexible para integrar APIs
externas.
¿Puede un subgraph extender una entidad que no posee?
Sí. La declara con @key y marca el campo clave como @external. El gateway enruta los campos base
al subgraph propietario y los nuevos al subgraph extensor.
¿Qué hace @requires?
Indica al gateway que debe obtener uno o más campos de otro subgraph antes de resolver el campo anotado. Usalo cuando un campo se calcula a partir de datos que pertenecen a otro lugar.
¿Puedo federar servicios REST?
No directamente. Cada subgraph debe exponer un esquema GraphQL que cumpla el spec de Apollo Federation. Si es necesario, podés envolver una API REST en un subgraph GraphQL liviano.
¿Debería marcar todos los campos como @shareable?
No. Usá @shareable solo cuando el mismo campo pueda resolverse por más de un subgraph. Para la
mayoría de las extensiones, @key y @external alcanzan.
Recursos Relacionados
Patrón de Schema Stitching en GraphQL
Combina múltiples esquemas GraphQL independientes en un único esquema unificado que los clientes pueden consultar como un solo grafo.
PatternPatron GraphQL Connection Pagination
Implementa paginacion basada en cursores estilo Relay con edges, nodes y pageInfo para queries de listas estables en GraphQL.
RecipeConfigurar un gateway de GraphQL Federation con Apollo
Compone multiples servicios GraphQL en un supergrafo federado usando Apollo Federation y un gateway que enruta consultas entre subgrafos
PatternGraphQL Mutation Validation: Validación Centralizada
Centralizá la validación de mutaciones GraphQL con reglas reutilizables, scalars y errores estructurados. Ejemplos en TypeScript y mejores prácticas.
DocPlantilla de Onboarding a GraphQL Federation
Plantilla para onboardear un servicio a un federated GraphQL graph: subgraph setup, entity definitions, resolver configuration, gateway integration, testing, deployment y monitoring con ejemplos de codigo.
GuideGuía Completa de GraphQL Federation
Construye APIs GraphQL unificadas con Apollo Federation. Cubre subgraphs, supergraph, resolución de entidades y despliegue de gateway.