Plantilla 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.
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.
Overview
Esta plantilla guia a un team a traves de onboardear un new service a un existing federated GraphQL graph. Segui cada section en order. No skipees nada — missing steps causan runtime errors en el gateway que son hard de debuggear.
1. Prerequisites
1.1 Before You Start
- Service tiene un GraphQL schema (SDL o code-first)
- Service expone un HTTP POST endpoint para GraphQL queries
- Service deployeado a staging environment
- Team tiene access al Apollo Studio / gateway repo
- Team ha reviewado el API design guidelines
- Domain boundaries identificados (que ownea este subgraph)
1.2 Domain Ownership Declaration
Service name: {service-name}
Team: {team-name}
Domain: {domain description}
Types owned by this subgraph:
- {Type1}: full ownership
- {Type2}: full ownership
Types extended from other subgraphs:
- {Type3}: extends {other-service} with fields {field1}, {field2}
Entities (shared across subgraphs):
- {Entity1}: @key(fields: "id")
- {Entity2}: @key(fields: "id")
2. Subgraph Setup
2.1 Install Dependencies
# Usando Apollo Server
npm install @apollo/server @apollo/subgraph graphql
# Usando Mercurius (Fastify)
npm install mercurius mercurius-federation @mercurius/federation
2.2 Subgraph Schema Definition
# products-subgraph.graphqls
extend schema
@link(url: "https://specs.apollo.dev/federation/v2.0",
import: ["@key", "@external", "@extends", "@shareable"])
type Product @key(fields: "id") {
id: ID!
name: String!
description: String!
price: Float!
currency: String!
categoryId: ID!
inStock: Boolean!
createdAt: DateTime!
updatedAt: DateTime!
}
type Category @key(fields: "id") {
id: ID!
name: String!
productCount: Int! @shareable
}
type Query {
product(id: ID!): Product
products(first: Int = 20, after: String): ProductConnection!
category(id: ID!): Category
}
type Mutation {
createProduct(input: CreateProductInput!): CreateProductPayload!
updateProduct(input: UpdateProductInput!): UpdateProductPayload!
}
type ProductConnection {
edges: [ProductEdge!]!
pageInfo: PageInfo!
totalCount: Int!
}
type ProductEdge {
node: Product!
cursor: String!
}
type PageInfo {
hasNextPage: Boolean!
hasPreviousPage: Boolean!
startCursor: String
endCursor: String
}
scalar DateTime
2.3 Resolver Configuration
import { ApolloServer } from '@apollo/server';
import { startStandaloneServer } from '@apollo/server/standalone';
import { buildSubgraphSchema } from '@apollo/subgraph';
const typeDefs = `#graphql
extend schema
@link(url: "https://specs.apollo.dev/federation/v2.0",
import: ["@key", "@external", "@extends"])
type Product @key(fields: "id") {
id: ID!
name: String!
price: Float!
}
type Query {
product(id: ID!): Product
}
`;
const resolvers = {
Query: {
product: (_, { id }, context) => {
return context.dataSources.productDb.findById(id);
},
},
// Entity resolver — llamado cuando gateway resolvee un Product por key
Product: {
__resolveReference: (reference, context) => {
return context.dataSources.productDb.findById(reference.id);
},
},
};
const server = new ApolloServer({
schema: buildSubgraphSchema([{ typeDefs, resolvers }]),
});
const { url } = await startStandaloneServer(server, {
listen: { port: 4001 },
context: async ({ req }) => ({
dataSources: createDataSources(req),
user: authenticateUser(req),
}),
});
3. Entity Resolution
3.1 Entity Resolver Rules
- Every type con
@keydebe tener__resolveReference - El resolver recibe el key fields y returna el full entity
- Si el entity no es found, returna null (no un error)
- Cachea entity lookups con DataLoader per request
- Entity resolvers deben ser fast (< 50ms p95)
import DataLoader from 'dataloader';
const resolvers = {
Product: {
__resolveReference: (reference, context) => {
// reference = { id: "123", __typename: "Product" }
return context.loaders.productById.load(reference.id);
},
},
Category: {
__resolveReference: (reference, context) => {
return context.loaders.categoryById.load(reference.id);
},
},
};
function createLoaders(db) {
return {
productById: new DataLoader(async (ids) => {
const products = await db.products.findMany({ where: { id: { in: ids } } });
return ids.map(id => products.find(p => p.id === id) || null);
}),
categoryById: new DataLoader(async (ids) => {
const categories = await db.categories.findMany({ where: { id: { in: ids } } });
return ids.map(id => categories.find(c => c.id === id) || null);
}),
};
}
3.2 Extending Entities from Other Subgraphs
# reviews-subgraph.graphqls
# Extends Product entity from Products subgraph
extend type Product @key(fields: "id") {
id: ID! @external
reviews: [Review!]!
averageRating: Float!
reviewCount: Int!
}
type Review @key(fields: "id") {
id: ID!
productId: ID!
userId: ID!
rating: Int!
comment: String!
createdAt: DateTime!
}
type Query {
reviews(productId: ID!, first: Int = 10, after: String): ReviewConnection!
}
// Reviews subgraph resolvers
const resolvers = {
Product: {
// Resolve extended fields cuando Product es accessed via gateway
reviews: (parent, _, context) => {
return context.dataSources.reviewDb.findByProduct(parent.id);
},
averageRating: (parent, _, context) => {
return context.dataSources.reviewDb.getAverageRating(parent.id);
},
reviewCount: (parent, _, context) => {
return context.dataSources.reviewDb.getCount(parent.id);
},
},
};
4. Gateway Integration
4.1 Gateway Configuration
// gateway/index.js
import { ApolloGateway } from '@apollo/gateway';
import { ApolloServer } from '@apollo/server';
import { startStandaloneServer } from '@apollo/server/standalone';
const gateway = new ApolloGateway({
serviceList: [
{ name: 'products', url: 'https://products-service.internal/graphql' },
{ name: 'reviews', url: 'https://reviews-service.internal/graphql' },
{ name: 'users', url: 'https://users-service.internal/graphql' },
{ name: 'orders', url: 'https://orders-service.internal/graphql' },
],
// Pollea para schema changes cada 10 seconds en staging
debug: process.env.NODE_ENV !== 'production',
});
const server = new ApolloServer({
gateway,
// Disablea subscriptions (no supported en federation)
subscriptions: false,
});
const { url } = await startStandaloneServer(server, {
listen: { port: 4000 },
context: async ({ req }) => ({
user: authenticateUser(req),
correlationId: req.headers['x-correlation-id'],
}),
});
4.2 Supergraph Composition
# supergraph.yaml
federation_version: =2.0.0
subgraphs:
products:
routing_url: https://products-service.internal/graphql
schema:
file: ./subgraphs/products/products.graphqls
reviews:
routing_url: https://reviews-service.internal/graphql
schema:
file: ./subgraphs/reviews/reviews.graphqls
users:
routing_url: https://users-service.internal/graphql
schema:
file: ./subgraphs/users/users.graphqls
orders:
routing_url: https://orders-service.internal/graphql
schema:
file: ./subgraphs/orders/orders.graphqls
# Compone el supergraph schema
rover supergraph compose --config supergraph.yaml > supergraph-schema.graphql
# Valida composition
rover subgraph check products@production \
--schema ./subgraphs/products/products.graphqls \
--routing-url https://products-service.internal/graphql
5. Testing
5.1 Subgraph Tests
// Testea entity resolution
describe('Product entity resolver', () => {
it('resolvee un product por ID', async () => {
const result = await executeOperation({
query: `{ product(id: "1") { id name price } }`,
});
expect(result.data.product).toEqual({
id: '1',
name: 'Widget',
price: 9.99,
});
});
it('returna null para non-existent product', async () => {
const result = await executeOperation({
query: `{ product(id: "999") { id name } }`,
});
expect(result.data.product).toBeNull();
});
});
// Testea __resolveReference directly
describe('Product __resolveReference', () => {
it('resolvee entity reference', async () => {
const result = await resolvers.Product.__resolveReference(
{ id: '1', __typename: 'Product' },
testContext
);
expect(result.id).toBe('1');
expect(result.name).toBeDefined();
});
});
5.2 Federation Integration Tests
// Testea cross-subgraph queries
describe('Federated queries', () => {
it('resolvee product con reviews across subgraphs', async () => {
const result = await gatewayExecute({
query: `
query {
product(id: "1") {
id
name
reviews {
rating
comment
}
averageRating
}
}
`,
});
expect(result.data.product.id).toBe('1');
expect(result.data.product.name).toBeDefined();
expect(result.data.product.reviews).toBeInstanceOf(Array);
expect(result.data.product.averageRating).toBeGreaterThan(0);
});
});
5.3 Testing Checklist
- Subgraph resolvee all own types correctamente
-
__resolveReferenceworks para all entities - Extended fields resolveen correctamente
- Cross-subgraph queries returnean correct data
- Null handling: missing entity returnea null, no error
- Error handling: subgraph errors propagatean al gateway
- Authentication context pasa a traves del gateway
- DataLoader batching works dentro del subgraph
6. Deployment
6.1 Deployment Checklist
- Subgraph deployeado a staging
- Schema composed con
rover supergraph compose - Composition validada (no conflicts, no circular dependencies)
- Gateway updated con new service URL
- Integration tests pasan en staging
- Performance benchmarks corren (p95 < 200ms para cross-subgraph queries)
- Monitoring dashboards configurados
- Alerting rules set up
- Rollback plan documentado
6.2 Deployment Steps
1. Deployea subgraph service a staging
- Verifica health check pasa
- Verifica GraphQL endpoint responde
- Corre subgraph test suite
2. Compone supergraph con new subgraph
- rover supergraph compose --config supergraph.yaml
- Verifica no composition errors
- Checkea por schema conflicts
3. Updatea gateway configuration
- Addea new service al serviceList
- Deployea gateway a staging
- Corre integration tests
4. Verifica en staging
- Testea cross-subgraph queries
- Checkea gateway logs por errors
- Monitora subgraph health
5. Deployea a production
- Deployea subgraph service
- Updatea gateway serviceList
- Monitora error rates por 1 hour
- Verifica query success rate > 99.9%
7. Monitoring
7.1 Key Metrics
Metric | Source | Alert Threshold
────────────────────────────────┼───────────────┼──────────────────
Subgraph request rate | Gateway | < 10 req/s (check)
Subgraph error rate | Gateway | > 1%
Subgraph latency p95 | Gateway | > 200ms
Entity resolver latency p95 | Subgraph | > 50ms
Gateway query latency p95 | Gateway | > 500ms
Schema composition failures | CI/CD | Any
Subgraph health check failures | Gateway > 3 consecutive
Cross-subgraph query errors | Gateway | > 0.5%
7.2 Monitoring Setup
// Subgraph health check endpoint
app.get('/health', (req, res) => {
res.json({
status: 'healthy',
subgraph: 'products',
version: process.env.APP_VERSION,
schemaHash: currentSchemaHash,
});
});
// Apollo Studio integration para schema tracking
const server = new ApolloServer({
schema: buildSubgraphSchema([{ typeDefs, resolvers }]),
plugins: [
process.env.NODE_ENV === 'production' && ApolloServerPluginInlineTrace(),
],
});
Preguntas Frecuentes
¿Cómo comparto custom scalars across subgraphs?
Define custom scalars (DateTime, JSON, URL) en un shared npm package. Cada subgraph importa el scalar definition y resolver. El gateway automaticamente mergea scalar definitions durante composition. Si scalars estan definidos differently en dos subgraphs, composition failea. Usa un shared package como @company/graphql-scalars para asegurar consistency.
¿Qué pasa si un subgraph esta down?
El gateway returna partial results para queries que pueden ser resolvedos por available subgraphs. Si una query requiere data del down subgraph, el gateway returna un error para ese field mientras still resolvee other fields. Usa @requires directives cuidadosamente — si un required field no puede ser resolvedo, el entire entity resolution failea. Configura circuit breakers en el gateway para fail fast en vez de waitear por timeouts.
¿Cómo handleo authentication en un federated graph?
Autentica en el gateway level. Extrae el JWT del Authorization header en el gateway context. Pasa el decoded user object a subgraphs via el x-user header o un custom context extension. Cada subgraph recibe el user en su context y performa field-level authorization. No autentiques en cada subgraph independently — eso causa redundant work y inconsistent behavior.
¿Puedo usar subscriptions con federation?
Standard Apollo Federation no supportea subscriptions a traves del gateway. Si necesitas real-time updates, corre un separate subscription endpoint outside del gateway. Clients connectan al subscription endpoint directly y al gateway para queries y mutations. Usa un pub/sub system (Redis, NATS) para sharear events entre subgraphs y el subscription endpoint. Apollo esta trabajando en native subscription support en future federation versions.
¿Cómo debuggeo un failing entity resolution?
Checkea tres cosas: (1) Esta __resolveReference implemented para el entity type? (2) Returna el resolver null cuando el entity no es found? (3) Esta el key field en el reference object spelled correctamente? Enablea query tracing en Apollo Studio para ver que subgraph resolveo que field. Checkea gateway logs por ENTITY_RESOLUTION_FAILED errors. Testea el entity resolver en isolation con un direct subgraph query antes de testear a traves del gateway.
See Also
Recursos Relacionados
GraphQL Schema Review Checklist
Checklist for reviewing GraphQL schemas: naming conventions, type design, pagination, error handling, security, performance, deprecation, and federation readiness with code examples and validation rules.
DocGraphQL API Design Guideline
Internal guidelines for designing GraphQL APIs: schema structure, naming, mutation patterns, error handling, pagination, authentication, rate limiting, versioning, and federation rules with code examples.
DocGraphQL Deprecation Policy Template
Policy template for deprecating GraphQL fields, types, arguments, and enum values safely. Includes deprecation timeline, communication plan, usage tracking, removal criteria, and migration examples.
RecipeSet Up a GraphQL Federation Gateway with Apollo
Compose multiple GraphQL services into a single federated supergraph using Apollo Federation and a gateway that routes queries across subgraphs
PatternGraphQL Federated Entity Pattern
Share entity types across federated GraphQL services so the gateway can resolve fields from multiple subgraphs transparently.
GuideGraphQL Federation in Production
Run federated GraphQL in production with confidence. Covers subgraph composition, gateway deployment, entity resolution, schema coordination, observability, and failure handling.