Federación GraphQL: Construyendo APIs Distribuidas y Escalables con Apollo Federation
Este tutorial te guiará a través de la implementación de GraphQL Federation utilizando Apollo Federation. Descubrirás cómo construir un grafo de datos unificado a partir de servicios independientes, mejorando la escalabilidad, mantenibilidad y colaboración en equipos grandes.
🚀 Introducción a la Federación GraphQL
En el mundo del desarrollo de software moderno, la tendencia hacia arquitecturas de microservicios es innegable. Estas arquitecturas ofrecen beneficios como la escalabilidad independiente, la autonomía de los equipos y la resiliencia. Sin embargo, cuando se trata de construir una API, integrar datos de múltiples microservicios puede volverse un desafío. Aquí es donde GraphQL Federation entra en juego, ofreciendo una solución elegante para unificar estas APIs dispersas en un solo grafo de datos global.
Tradicionalmente, con una arquitectura de microservicios, cada servicio podría exponer su propia API REST o GraphQL. Esto obligaría a los clientes a realizar múltiples llamadas a diferentes servicios y a ensamblar los datos por sí mismos, lo que aumenta la complejidad en el lado del cliente y dificulta la evolución de la API.
GraphQL Federation, popularizada por Apollo, permite que cada microservicio implemente una parte de tu esquema GraphQL global (un subgrafo), mientras que un gateway se encarga de componer y resolver las consultas de los clientes. El resultado es un grafo de datos unificado y coherente, donde los clientes solo interactúan con un único endpoint GraphQL.
¿Por qué Federación GraphQL? 🤔
La Federación GraphQL no es solo una moda; resuelve problemas reales y ofrece ventajas significativas:
- Escalabilidad: Cada subservicio puede escalar de forma independiente.
- Mantenibilidad: Los equipos son dueños de sus propios subgrafos, facilitando el desarrollo y despliegue.
- Colaboración: Diferentes equipos pueden trabajar en partes separadas del grafo sin colisiones.
- Flexibilidad del Cliente: Los clientes obtienen un único endpoint y un esquema cohesivo.
- Rendimiento: El gateway puede optimizar la resolución de consultas entre servicios.
🛠️ Conceptos Clave en Apollo Federation
Para entender cómo funciona Apollo Federation, es crucial familiarizarse con algunos conceptos fundamentales:
1. El Gateway (API Gateway) 🚪
El Gateway es el punto de entrada para todas las consultas de los clientes. Es responsable de:
- Recibir las consultas GraphQL de los clientes.
- Analizar la consulta para determinar qué subservicios son necesarios para resolverla.
- Distribuir las partes relevantes de la consulta a los subservicios apropiados.
- Componer las respuestas de los subservicios en una única respuesta GraphQL coherente para el cliente.
El Gateway actúa como un orquestador inteligente, ocultando la complejidad de la arquitectura de microservicios al cliente.
2. Los Subgrafos (Subgraphs) 🌿
Cada microservicio expone una porción de tu esquema GraphQL global. Esta porción es lo que llamamos un subgrafo. Un subgrafo es una API GraphQL estándar con algunas directivas Federation especiales que le indican al Gateway cómo interactuar con él.
Cada subgrafo es responsable de su propio dominio de datos y de resolver las consultas relacionadas con ese dominio. Por ejemplo, un servicio de Usuarios tendría un subgrafo que define el tipo User, y un servicio de Productos tendría un subgrafo que define el tipo Product.
3. Tipos extend y la Directiva @key 🔑
Aquí es donde la Federación se vuelve realmente poderosa. La directiva @key se usa para identificar un campo o conjunto de campos que actúan como una clave única para un tipo de entidad en un subgrafo. Esto permite que el Gateway sepa cómo identificar y referenciar ese tipo en otros subgrafos.
Por ejemplo, el servicio de Productos podría querer incluir información sobre el creador de un producto. En lugar de duplicar los datos del usuario, puede extender el tipo User (definido en el servicio de Usuarios) y agregar campos específicos del dominio del producto a ese tipo, haciendo referencia a la clave id del usuario.
# En el subgrafo de Usuarios:
type User @key(fields: "id") {
id: ID!
name: String!
email: String!
}
# En el subgrafo de Productos:
extend type User @key(fields: "id") {
id: ID! @external
products: [Product!]
}
type Product @key(fields: "sku") {
sku: String!
name: String!
price: Float!
createdBy: User!
}
En este ejemplo:
Useres definido en el subgrafo deUsuariosy su clave esid.- El subgrafo de
Productosextiende el tipoUser, indicando que tiene un campoidque esexternal(externo) y que lo usará para obtener más información sobre el usuario del servicio deUsuarios. - El campo
productsen elUserextendido es resuelto por el subgrafo deProductos.
4. Directivas Federation Fundamentales 📖
Además de @key y extend, otras directivas clave incluyen:
@external: Marca un campo como definido en otro subgrafo, indicando que el subgrafo actual no lo resolverá directamente.@requires: Indica que un campo solo se puede resolver si otros campos (marcados con@external) también están presentes en la consulta. Esto asegura que el subgrafo tenga todos los datos necesarios para resolver un campo.@provides: Usada en campos de entidades para indicar que un subgrafo puede proporcionar un campo que normalmente sería resuelto por otro subgrafo. Esto puede optimizar consultas evitando viajes de ida y vuelta adicionales.@shareable: Permite que un campo se defina en múltiples subgrafos si sus implementaciones son idénticas. (Introducida en Federation 2).@override: Permite que un subgrafo reemplace la implementación de un campo de una entidad definido en otro subgrafo. (Introducida en Federation 2).
🏗️ Creando un Proyecto de Federación GraphQL: Paso a Paso
Vamos a construir un ejemplo práctico de Federación GraphQL. Necesitaremos al menos dos subgrafos (servicios) y un Gateway.
Nuestro Escenario:
Imaginemos una plataforma de comercio electrónico. Tendremos dos microservicios principales:
- Servicio de Usuarios: Gestiona usuarios (ID, nombre, email).
- Servicio de Productos: Gestiona productos (ID, nombre, precio, descripción) y el usuario que lo publicó.
El Gateway unificará estos dos servicios.
Pre-requisitos ✅
- Node.js (versión LTS recomendada)
- npm o yarn
- Un editor de código (VS Code es una excelente opción)
1. Inicializar el Proyecto Principal 📁
Crea una carpeta para tu proyecto y entra en ella:
mkdir graphql-federation-example
cd graphql-federation-example
npm init -y
2. Crear los Subgrafos (Microservicios) 🧱
Crearemos dos subgrafos, users y products, usando Apollo Server.
2.1. Subgrafo de Usuarios (services/users) 👤
mkdir services
mkdir services/users
cd services/users
npm init -y
npm install apollo-server @apollo/subgraph graphql
Crea el archivo index.js en services/users:
const { ApolloServer, gql } = require('apollo-server');
const { buildSubgraphSchema } = require('@apollo/subgraph');
const typeDefs = gql`
type User @key(fields: "id") {
id: ID!
name: String!
email: String!
}
extend type Query {
user(id: ID!): User
users: [User!]
}
`;
const users = [
{ id: '1', name: 'Alice', email: 'alice@example.com' },
{ id: '2', name: 'Bob', email: 'bob@example.com' },
{ id: '3', name: 'Charlie', email: 'charlie@example.com' },
];
const resolvers = {
Query: {
user: (_, { id }) => users.find(user => user.id === id),
users: () => users,
},
User: {
__resolveReference(object) {
return users.find(user => user.id === object.id);
},
},
};
const server = new ApolloServer({
schema: buildSubgraphSchema([{ typeDefs, resolvers }]),
});
server.listen({ port: 4001 }).then(({ url }) => {
console.log(`🚀 User Subgraph ready at ${url}`);
});
Explicación:
@key(fields: "id"): Declara queUseres una entidad y su clave única esid.extend type Query: Extiende el tipoQueryraíz para agregar consultas específicas de usuarios.__resolveReference: Una función especial de Federación que el Gateway usa para resolver referencias a tipos extendidos. Si el Gateway necesita información sobre unUser(por ejemplo, por suid), llamará a esta función en el subgrafo deUsuariospara obtener los datos completos del usuario.
2.2. Subgrafo de Productos (services/products) 📦
cd ../products # Volvemos a la carpeta services y creamos products
mkdir services/products
cd services/products
npm init -y
npm install apollo-server @apollo/subgraph graphql
Crea el archivo index.js en services/products:
const { ApolloServer, gql } = require('apollo-server');
const { buildSubgraphSchema } = require('@apollo/subgraph');
const typeDefs = gql`
extend type Query {
product(sku: String!): Product
products: [Product!]
}
type Product @key(fields: "sku") {
sku: String!
name: String!
price: Float!
description: String
createdBy: User!
}
extend type User @key(fields: "id") {
id: ID! @external
products: [Product!]
}
`;
const products = [
{ sku: 'P1', name: 'Laptop', price: 1200, description: 'Potente laptop', createdBy: '1' },
{ sku: 'P2', name: 'Mouse', price: 25, description: 'Mouse ergonómico', createdBy: '1' },
{ sku: 'P3', name: 'Teclado', price: 75, description: 'Teclado mecánico', createdBy: '2' },
];
const resolvers = {
Query: {
product: (_, { sku }) => products.find(product => product.sku === sku),
products: () => products,
},
Product: {
createdBy: (product) => ({
__typename: "User",
id: product.createdBy,
}),
},
User: {
products: (user) => products.filter(product => product.createdBy === user.id),
},
};
const server = new ApolloServer({
schema: buildSubgraphSchema([{ typeDefs, resolvers }]),
});
server.listen({ port: 4002 }).then(({ url }) => {
console.log(`🚀 Product Subgraph ready at ${url}`);
});
Explicación:
type Product @key(fields: "sku"):Productes una entidad conskucomo clave.extend type User @key(fields: "id"): El subgrafo deProductosextiende el tipoUserdefinido en el subgrafo deUsuarios. Declara que suides externo.createdBy: User!: El campocreatedByenProductes de tipoUser!. El resolver paracreatedBydevuelve un stub deUser(solo con__typenameyid), y el Gateway se encargará de resolver el resto de los campos deUserconsultando al subgrafo deUsuarios.User.products: Este resolver se ejecuta cuando se consulta el campoproductsen unUser(que ha sido extendido por este subgrafo). Filtra los productos que pertenecen a ese usuario.
3. Crear el Gateway 🌐
El Gateway unirá ambos subgrafos. Volvemos a la carpeta raíz del proyecto (graphql-federation-example).
cd ../../ # Volvemos a graphql-federation-example
npm install @apollo/gateway apollo-server graphql
Crea el archivo gateway.js en la raíz del proyecto:
const { ApolloServer } = require('apollo-server');
const { ApolloGateway, IntrospectAndCompose } = require('@apollo/gateway');
const gateway = new ApolloGateway({
supergraphSdl: new IntrospectAndCompose({
subgraphs: [
{ name: 'users', url: 'http://localhost:4001' },
{ name: 'products', url: 'http://localhost:4002' },
],
}),
});
(async () => {
const server = new ApolloServer({
gateway,
// Para Apollo Studio / Playground
// Desactiva la introspección y el playground en producción
apollo: {
graphRef: 'your-graph-id@current',
},
});
const { url } = await server.listen({ port: 4000 });
console.log(`🚀 Gateway ready at ${url}`);
})();
Explicación:
ApolloGateway: La clase principal para configurar el Gateway.IntrospectAndCompose: Una utilidad que permite al Gateway introspectar automáticamente los esquemas de los subgrafos en tiempo de ejecución y componer el supergrafo. Para producción, se recomienda unsupergraphSdlestático o generado previamente para mayor estabilidad.subgraphs: Un array que lista todos los subgrafos que el Gateway debe conocer, con sus nombres y URLs.
🏃 Ejecutando y Probando la Federación
Ahora que tenemos todos los componentes, es hora de ponerlos en marcha.
1. Iniciar los Subgrafos 🟢
Abre dos terminales separadas y, en cada una, navega a la carpeta de un subgrafo y ejecútalo:
Terminal 1 (Subgrafo de Usuarios):
cd services/users
node index.js
Deberías ver: 🚀 User Subgraph ready at http://localhost:4001/
Terminal 2 (Subgrafo de Productos):
cd services/products
node index.js
Deberías ver: 🚀 Product Subgraph ready at http://localhost:4002/
2. Iniciar el Gateway 🟢
Abre una tercera terminal, navega a la carpeta raíz del proyecto y ejecuta el Gateway:
Terminal 3 (Gateway):
node gateway.js
Deberías ver: 🚀 Gateway ready at http://localhost:4000/
3. Probar con Consultas GraphQL 🧪
Accede al Apollo Playground (o interfaz GraphQL de tu preferencia) en http://localhost:4000/.
Aquí puedes ejecutar consultas que combinan datos de ambos servicios:
Consulta 1: Obtener un usuario y sus productos
query GetUserWithProducts {
user(id: "1") {
id
name
email
products {
sku
name
price
}
}
}
Resultado esperado:
{
"data": {
"user": {
"id": "1",
"name": "Alice",
"email": "alice@example.com",
"products": [
{
"sku": "P1",
"name": "Laptop",
"price": 1200
},
{
"sku": "P2",
"name": "Mouse",
"price": 25
}
]
}
}
}
Consulta 2: Obtener un producto y el nombre de su creador
query GetProductWithCreator {
product(sku: "P3") {
sku
name
price
description
createdBy {
id
name
}
}
}
Resultado esperado:
{
"data": {
"product": {
"sku": "P3",
"name": "Teclado",
"price": 75,
"description": "Teclado mecánico",
"createdBy": {
"id": "2",
"name": "Bob"
}
}
}
}
En este caso, el Gateway primero consulta el subgrafo de Productos para obtener la información del producto, incluyendo el id del createdBy. Luego, usa ese id para consultar el subgrafo de Usuarios y obtener el name del usuario Bob.
📊 Funcionamiento Interno del Gateway (Ejemplo Visual)
El Gateway es el cerebro detrás de la Federación. Cuando recibe una consulta, realiza una serie de pasos:
- Análisis de la Consulta: El Gateway parsea la consulta GraphQL para entender qué datos se solicitan.
- Planificación de la Ejecución: Basándose en el esquema supergrafo (compuesto de todos los subgrafos), el Gateway determina qué subgrafos deben ser consultados y en qué orden.
- Ejecución Paralela/Secuencial: Envía las consultas parciales a los subgrafos correspondientes. Puede ejecutar consultas en paralelo si no hay dependencias, o secuencialmente si un subgrafo necesita la respuesta de otro.
- Composición de la Respuesta: Una vez que recibe las respuestas de todos los subgrafos, las une en una única respuesta GraphQL que envía al cliente.
Ejemplo de Plan de Ejecución para GetUserWithProducts:
- El Gateway recibe
query { user(id: "1") { id name email products { sku name } } }. - Identifica que
user(id: "1") { id name email }necesita ser resuelto por el Subgrafo de Usuarios. - Identifica que
products { sku name }necesita ser resuelto por el Subgrafo de Productos, pero requiere eliddelUser. - Paso 1: Envía
query { user(id: "1") { id name email } }al Subgrafo de Usuarios. Recibe{ "id": "1", "name": "Alice", "email": "alice@example.com" }. - Paso 2: Con el
id(1) del usuario, envíaquery { _entities(representations: [{ __typename: "User", id: "1" }]) { ... on User { products { sku name } } } }al Subgrafo de Productos. El Subgrafo de Productos resuelve los productos para el usuario conid: "1". - Paso 3: Combina las respuestas de ambos subgrafos en una única respuesta para el cliente.
📈 Consideraciones Avanzadas y Mejores Prácticas
La Federación GraphQL es una herramienta potente, pero su implementación óptima requiere considerar algunos aspectos avanzados.
1. Gestión de Errores y Retries ⚠️
En un entorno distribuido, los fallos de red o de servicio son inevitables. El Gateway debe ser robusto y capaz de manejar estas situaciones. Implementar reintentos (retries) con retroceso exponencial y un circuito de interrupción (circuit breaker) puede mejorar la resiliencia.
2. Autenticación y Autorización 🔒
La autenticación generalmente se maneja en el Gateway. El token de autenticación del cliente (por ejemplo, JWT) se valida en el Gateway y luego se propaga a los subgrafos a través de los headers HTTP o un contexto específico. Cada subgrafo es entonces responsable de aplicar sus propias reglas de autorización basadas en la información recibida.
// Ejemplo de contexto en el Gateway para propagar headers
const gateway = new ApolloGateway({
// ...
buildService({
ame, url}) {
return new RemoteGraphQLDataSource({
url,
willSendRequest({ request, context }) {
// Propagar el token de autorización del cliente a los subgrafos
if (context.token) {
request.http.headers.set('authorization', context.token);
}
},
});
},
});
const server = new ApolloServer({
gateway,
context: ({ req }) => {
return { token: req.headers.authorization };
},
});
3. Rendimiento y Caching 🏎️
- Caching a nivel de Gateway: Implementa caching para respuestas de consultas comunes. Apollo proporciona herramientas para esto.
- Caching a nivel de Subgrafo: Cada subgrafo puede implementar su propia estrategia de caching para sus datos internos (por ejemplo, Redis).
- Batching y Dataloader: Asegúrate de que tus subgrafos utilicen patrones como
DataLoaderpara agrupar consultas y evitar el problema N+1, especialmente cuando se resuelven relaciones (createdByoproductsen nuestro ejemplo).
4. Monitoreo y Observabilidad 👀
Es crucial monitorear el rendimiento del Gateway y de cada subgrafo. Herramientas como Apollo Studio (para el supergrafo), Prometheus/Grafana (para métricas) y Jaeger/OpenTelemetry (para trazas distribuidas) son esenciales para diagnosticar problemas en un sistema federado.
5. Federación 2.0 ✨
Apollo Federation 2 introdujo mejoras significativas, como:
@shareable: Permite definir el mismo campo en varios subgrafos si tiene la misma implementación.@override: Permite a un subgrafo reemplazar la implementación de un campo de otro subgrafo.- Mejoras en la composición de esquemas y la gestión de entidades.
Es recomendable usar Federation 2.x para nuevos proyectos debido a estas mejoras.
6. Despliegue y CI/CD 🚀
Considera cómo desplegarás tus subgrafos y tu Gateway. Cada subgrafo puede desplegarse y versionarse independientemente. El Gateway necesitará acceso a las URLs de los subgrafos, lo que puede manejarse a través de variables de entorno o un sistema de descubrimiento de servicios.
¿Cuál es la diferencia entre Federation 1 y Federation 2?
Federation 2 introduce mejoras clave como las directivas `@shareable` y `@override`, que ofrecen más flexibilidad en la composición de esquemas y la colaboración entre equipos. También mejora el rendimiento y la experiencia del desarrollador con una composición de esquemas más robusta y herramientas de desarrollo avanzadas.🏁 Conclusión
La Federación GraphQL es un cambio de paradigma en cómo construimos y gestionamos APIs en arquitecturas de microservicios. Permite a los equipos trabajar de forma independiente en sus dominios de datos, mientras que el Gateway unifica estas piezas en un grafo de datos global y coherente, fácil de consumir para los clientes.
Si bien la configuración inicial puede parecer un poco más compleja que una API monolítica, los beneficios a largo plazo en términos de escalabilidad, mantenibilidad y autonomía de equipo son inmensos. Al seguir este tutorial, has dado tus primeros pasos para dominar esta poderosa tecnología y construir APIs GraphQL de próxima generación.
¡Experimenta con el código, extiende los ejemplos y lleva tus habilidades de GraphQL al siguiente nivel con Apollo Federation!
Tutoriales relacionados
- Explorando GraphQL: Un Viaje Práctico para Construir APIs Flexibles y Eficientesintermediate25 min
- Optimización de Consultas GraphQL: Estrategias para APIs Más Rápidas y Eficientesintermediate12 min
- Uniones y Fragmentos GraphQL: Dominando la Composición de Datos Avanzadaintermediate18 min
- Optimización de GraphQL con Dataloader: Estrategias para Evitar el Problema N+1intermediate15 min
- Suscribiendo Datos en Tiempo Real con GraphQL: Implementando Subscriptions para Experiencias Dinámicasintermediate15 min
Comentarios (0)
Aún no hay comentarios. ¡Sé el primero!