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

  1. Database per service: Cada servicio su BD
  2. API Gateway: Punto único de entrada
  3. Circuit Breakers: Previene cascading failures
  4. Async communication: Usa message queues
  5. Monitoring: Logs centralizados y métricas
  6. 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.