Arquitectura de Microservicios con Node.js
19 de diciembre de 2025
Osman Jimenez
Microservicios Node.js Arquitectura de Software
Diseñando Microservicios Escalables
Los microservicios permiten escalar y desplegar servicios independientemente. Aprende a diseñar una arquitectura robusta con Node.js.
Principios de Microservicios
- Un servicio, una responsabilidad
- Despliegue independiente
- Base de datos por servicio
- Comunicación via API
- Tolerancia a fallos
Estructura de Proyecto
microservices/
├── services/
│ ├── auth-service/
│ │ ├── src/
│ │ ├── Dockerfile
│ │ └── package.json
│ ├── user-service/
│ ├── product-service/
│ └── order-service/
├── api-gateway/
├── docker-compose.yml
└── k8s/API Gateway con Express
// api-gateway/src/index.js
import express from 'express';
import { createProxyMiddleware } from 'http-proxy-middleware';
const app = express();
// Rutas a microservicios
app.use('/api/auth', createProxyMiddleware({
target: 'http://auth-service:3001',
changeOrigin: true
}));
app.use('/api/users', createProxyMiddleware({
target: 'http://user-service:3002',
changeOrigin: true
}));
app.use('/api/products', createProxyMiddleware({
target: 'http://product-service:3003',
changeOrigin: true
}));
app.listen(3000);Comunicación entre Servicios
1. HTTP/REST
// user-service llama a auth-service
import axios from 'axios';
async function validateToken(token) {
try {
const response = await axios.post(
'http://auth-service:3001/validate',
{ token },
{ timeout: 5000 }
);
return response.data;
} catch (error) {
console.error('Error validating token:', error);
throw error;
}
}2. Message Queue (RabbitMQ)
// publisher.js
import amqp from 'amqplib';
const connection = await amqp.connect('amqp://localhost');
const channel = await connection.createChannel();
await channel.assertQueue('orders');
channel.sendToQueue('orders', Buffer.from(JSON.stringify({
orderId: '123',
userId: '456',
total: 99.99
})));
// consumer.js
channel.consume('orders', (msg) => {
const order = JSON.parse(msg.content.toString());
console.log('Processing order:', order);
// Procesar orden
processOrder(order);
// Acknowledge
channel.ack(msg);
});Service Discovery con Consul
import Consul from 'consul';
const consul = new Consul();
// Registrar servicio
await consul.agent.service.register({
name: 'user-service',
address: 'localhost',
port: 3002,
check: {
http: 'http://localhost:3002/health',
interval: '10s'
}
});
// Descubrir servicio
const services = await consul.health.service('user-service');
const service = services[0];
const url = `http://${service.Service.Address}:${service.Service.Port}`;Circuit Breaker Pattern
import CircuitBreaker from 'opossum';
const options = {
timeout: 3000,
errorThresholdPercentage: 50,
resetTimeout: 30000
};
const breaker = new CircuitBreaker(async (userId) => {
return await axios.get(`http://user-service/users/${userId}`);
}, options);
breaker.fallback(() => ({
id: userId,
name: 'Unknown User'
}));
breaker.on('open', () => console.log('Circuit opened'));
breaker.on('halfOpen', () => console.log('Circuit half-open'));
breaker.on('close', () => console.log('Circuit closed'));
// Uso
const user = await breaker.fire(userId);Docker Compose para Desarrollo
version: '3.8'
services:
api-gateway:
build: ./api-gateway
ports:
- "3000:3000"
depends_on:
- auth-service
- user-service
auth-service:
build: ./services/auth-service
environment:
- DB_HOST=auth-db
- JWT_SECRET=secret
user-service:
build: ./services/user-service
environment:
- DB_HOST=user-db
auth-db:
image: postgres:15
environment:
POSTGRES_DB: auth
user-db:
image: postgres:15
environment:
POSTGRES_DB: users
rabbitmq:
image: rabbitmq:3-management
ports:
- "5672:5672"
- "15672:15672"Logging Centralizado
// logger.js con Winston
import winston from 'winston';
const logger = winston.createLogger({
level: 'info',
format: winston.format.json(),
defaultMeta: {
service: 'user-service',
version: '1.0.0'
},
transports: [
new winston.transports.File({ filename: 'error.log', level: 'error' }),
new winston.transports.File({ filename: 'combined.log' }),
new winston.transports.Console()
]
});
export default logger;Health Checks
app.get('/health', async (req, res) => {
const health = {
uptime: process.uptime(),
timestamp: Date.now(),
status: 'OK'
};
try {
// Check database
await db.query('SELECT 1');
health.database = 'OK';
} catch (error) {
health.database = 'ERROR';
health.status = 'ERROR';
}
const statusCode = health.status === 'OK' ? 200 : 503;
res.status(statusCode).json(health);
});Distributed Tracing
// Con OpenTelemetry
import { NodeTracerProvider } from '@opentelemetry/sdk-trace-node';
import { registerInstrumentations } from '@opentelemetry/instrumentation';
import { HttpInstrumentation } from '@opentelemetry/instrumentation-http';
const provider = new NodeTracerProvider();
provider.register();
registerInstrumentations({
instrumentations: [
new HttpInstrumentation()
]
});Mejores Prácticas
- Database per service: Cada servicio su BD
- API Gateway: Punto único de entrada
- Circuit Breakers: Previene cascading failures
- Async communication: Usa message queues
- Monitoring: Logs centralizados y métricas
- Versioning: Versionado de APIs
Conclusión
Los microservicios ofrecen escalabilidad y flexibilidad, pero añaden complejidad. Úsalos cuando realmente los necesites y asegúrate de tener la infraestructura adecuada para soportarlos.