Saltar al contenido principal

🧩 Arquitectura de Microservicios

📖 Definición

La arquitectura de microservicios (MSA, Microservices Architecture) es un patrón arquitectónico que divide una aplicación grande en múltiples servicios pequeños e independientes para desarrollo y despliegue. Cada servicio es responsable de una función empresarial específica y puede desplegarse y escalarse de forma independiente. A diferencia de la arquitectura monolítica, proporciona flexibilidad y escalabilidad mediante un acoplamiento débil entre servicios.

🎯 Comprensión mediante analogías

Gran empresa vs Startups

Monolítico = Gran empresa
├─ Todos los departamentos en un edificio
├─ Gestión centralizada
├─ Problema en un departamento → Afecta a todo
├─ Cambios difíciles
└─ Decisiones lentas

Microservicios = Federación de startups
├─ Cada equipo tiene oficina independiente
├─ Decisiones autónomas
├─ Problema en un equipo → Otros funcionan normalmente
├─ Cambios rápidos
└─ Escalado flexible

LEGO vs Arcilla

Monolítico = Bloque de arcilla
┌──────────────────────────────┐
│ Usuario │ Producto │ Pedido │
│ Gestión │ Gestión │ Gestión│
│ Todo en uno │
└──────────────────────────────┘
- Necesita rehacerse completamente
- Modificar una parte → Afecta al todo
- Difícil de escalar

Microservicios = Bloques LEGO
┌─────┐ ┌─────┐ ┌─────┐
│Usuario│ │Producto│ │Pedido│
│Servicio│ │Servicio│ │Servicio│
└─────┘ └─────┘ └─────┘
- Fácil reemplazar bloques
- Modificación independiente
- Escalar solo lo necesario

⚙️ Cómo funciona

1. Monolítico vs Microservicios

========== Monolítico ==========
┌─────────────────────────────────────┐
│ Una aplicación │
│ │
│ ┌─────────────────────────────┐ │
│ │ Módulo gestión usuarios │ │
│ └─────────────────────────────┘ │
│ ┌─────────────────────────────┐ │
│ │ Módulo gestión productos│ │
│ └─────────────────────────────┘ │
│ ┌─────────────────────────────┐ │
│ │ Módulo gestión pedidos │ │
│ └─────────────────────────────┘ │
│ ┌─────────────────────────────┐ │
│ │ Módulo de pagos │ │
│ └─────────────────────────────┘ │
│ │
│ Una base de datos │
│ Un código base │
│ Una unidad de despliegue │
└─────────────────────────────────────┘

Ventajas:
✅ Rápido al inicio del desarrollo
✅ Pruebas simples
✅ Despliegue simple (solo uno)
✅ Depuración fácil

Desventajas:
❌ Complejo cuando crece
❌ Interrupción total al desplegar
❌ No se puede escalar parcialmente
❌ Difícil cambiar stack tecnológico

========== Microservicios ==========
┌──────────┐ ┌──────────┐ ┌──────────┐
│Usuario │ │Producto │ │Pedido │
│Servicio │ │Servicio │ │Servicio │
│ │ │ │ │ │
│Node.js │ │Java │ │Go │
│MongoDB │ │MySQL │ │PostgreSQL │
└──────────┘ └──────────┘ └──────────┘
↓ ↓ ↓
┌──────────┐ ┌──────────┐ ┌──────────┐
│Pago │ │Notificación│ │Reseña │
│Servicio │ │Servicio │ │Servicio │
│ │ │ │ │ │
│Python │ │Node.js │ │Ruby │
│Redis │ │Kafka │ │Cassandra │
└──────────┘ └──────────┘ └──────────┘

Ventajas:
✅ Despliegue independiente
✅ Libertad de stack tecnológico
✅ Escalado parcial posible
✅ Independencia de equipos
✅ Aislamiento de fallos

Desventajas:
❌ Alta complejidad inicial
❌ Sobrecarga de comunicación de red
❌ Transacciones distribuidas difíciles
❌ Pruebas complejas
❌ Costos operativos aumentados

2. Comunicación entre servicios

========== Comunicación síncrona (HTTP/REST) ==========
Servicio de Pedidos → Servicio de Productos
"¿Hay stock del producto 123?"

"Sí, hay 5 unidades"

Servicio de Pedidos → Servicio de Pagos
"Por favor procesar pago de 10,000"

"Pago completado"

Pedido completado

Ventajas: Simple, intuitivo
Desventajas: Fallo total si un servicio falla

========== Comunicación asíncrona (Cola de mensajes) ==========
Servicio de Pedidos → Cola de Mensajes
Publicar mensaje "Pedido creado"

┌──────────────────┐
│ Cola de Mensajes│
│ (RabbitMQ, │
│ Kafka, etc) │
└──────────────────┘

┌────┴────┬────────┐
↓ ↓ ↓
Pago Notificación Inventario
Servicio Servicio Servicio
Cada uno procesa independientemente

Ventajas: Acoplamiento débil, aislamiento de fallos
Desventajas: Complejidad aumentada, depuración difícil

========== API Gateway ==========
Cliente (Móvil/Web)

┌──────────────────┐
│ API Gateway │
│ - Enrutamiento │
│ - Autenticación│
│ - Balanceo carga│
│ - Registro │
└──────────────────┘
┌────┴────┬────────┐
↓ ↓ ↓
ServicioA ServicioB ServicioC

Roles:
- Punto de entrada único
- Simplificación del cliente
- Manejo de funciones comunes

3. Gestión de datos

========== Monolítico: Base de datos compartida ==========
┌─────────────────────────────────────┐
│ Aplicación │
│ ┌─────┐ ┌─────┐ ┌─────┐ │
│ │MóduloA│ │MóduloB│ │MóduloC│ │
│ └──┬──┘ └──┬──┘ └──┬──┘ │
└─────┼────────┼───────┼──────────────┘
└────────┼───────┘

┌──────────────────┐
│ Una base de datos│
│ ┌────┬────┬────┐│
│ │TA│TB│TC││
│ └────┴────┴────┘│
└──────────────────┘

Ventajas: JOINs fáciles, consistencia garantizada
Desventajas: Alto acoplamiento, difícil escalar

========== Microservicios: BD por servicio ==========
┌──────────┐ ┌──────────┐ ┌──────────┐
│Servicio A │ │Servicio B │ │Servicio C │
└────┬─────┘ └────┬─────┘ └────┬─────┘
↓ ↓ ↓
┌─────────┐ ┌─────────┐ ┌─────────┐
│ BD A │ │ BD B │ │ BD C │
│ (MySQL) │ │(MongoDB)│ │(PostgreSQL)│
└─────────┘ └─────────┘ └─────────┘

Ventajas: Independencia, libertad tecnológica
Desventajas: No hay JOINs, consistencia difícil

========== Consistencia de datos ==========
// Patrón Saga
1. Servicio de Pedidos: Crear pedido
2. Servicio de Pagos: Procesar pago
3. Servicio de Inventario: Reducir stock
4. Servicio de Envío: Iniciar envío

¿Qué pasa si falla el paso 3?
→ Transacción compensatoria
4. Fallo al reducir stock
3. Cancelar pago ← Compensación
2. Cancelar pedido ← Compensación

💡 Ejemplos prácticos

Ejemplo monolítico (Express.js)

// ========== Aplicación monolítica ==========
// server.js - Un archivo con todas las funciones

const express = require('express');
const app = express();

app.use(express.json());

// Una base de datos
const db = require('./database');

// ========== Gestión de usuarios ==========
app.post('/api/users', async (req, res) => {
const { username, email, password } = req.body;
const user = await db.users.create({ username, email, password });
res.json(user);
});

app.get('/api/users/:id', async (req, res) => {
const user = await db.users.findById(req.params.id);
res.json(user);
});

// ========== Gestión de productos ==========
app.post('/api/products', async (req, res) => {
const { name, price, stock } = req.body;
const product = await db.products.create({ name, price, stock });
res.json(product);
});

app.get('/api/products', async (req, res) => {
const products = await db.products.findAll();
res.json(products);
});

// ========== Gestión de pedidos ==========
app.post('/api/orders', async (req, res) => {
const { userId, productId, quantity } = req.body;

// Transacción para garantizar consistencia
const transaction = await db.sequelize.transaction();

try {
// 1. Verificar stock
const product = await db.products.findById(productId, { transaction });
if (product.stock < quantity) {
throw new Error('Stock insuficiente');
}

// 2. Reducir stock
await product.update(
{ stock: product.stock - quantity },
{ transaction }
);

// 3. Crear pedido
const order = await db.orders.create(
{ userId, productId, quantity, total: product.price * quantity },
{ transaction }
);

// 4. Procesar pago
await processPayment(order.total);

await transaction.commit();
res.json(order);
} catch (error) {
await transaction.rollback();
res.status(400).json({ error: error.message });
}
});

// ========== Procesamiento de pagos ==========
app.post('/api/payments', async (req, res) => {
const { orderId, amount } = req.body;
const payment = await db.payments.create({ orderId, amount });
res.json(payment);
});

// Ejecutar con un solo servidor
app.listen(3000, () => {
console.log('Servidor monolítico: http://localhost:3000');
});

/*
Ventajas:
- Código en un lugar
- Desarrollo rápido
- Depuración fácil
- Transacciones simples

Desventajas:
- Complejo cuando crece
- Reinicio total al desplegar
- No se puede escalar parcialmente
- Fallo en una función → Afecta todo
*/

Ejemplo de microservicios

// ========== 1. Servicio de Usuarios (user-service.js) ==========
// Puerto: 3001
const express = require('express');
const app = express();
const mongoose = require('mongoose');

app.use(express.json());

// Base de datos independiente
mongoose.connect('mongodb://localhost/users-db');

const User = mongoose.model('User', {
username: String,
email: String,
password: String
});

// Crear usuario
app.post('/users', async (req, res) => {
const { username, email, password } = req.body;

try {
const user = new User({ username, email, password });
await user.save();

// Publicar evento (notificar otros servicios)
await publishEvent('user.created', { userId: user._id, email });

res.json(user);
} catch (error) {
res.status(400).json({ error: error.message });
}
});

// Consultar usuario
app.get('/users/:id', async (req, res) => {
const user = await User.findById(req.params.id);
res.json(user);
});

app.listen(3001, () => {
console.log('Servicio de usuarios: http://localhost:3001');
});

// ========== 2. Servicio de Productos (product-service.js) ==========
// Puerto: 3002
const express = require('express');
const app = express();
const { Pool } = require('pg');

app.use(express.json());

// Usar PostgreSQL (¡diferente BD!)
const pool = new Pool({
host: 'localhost',
database: 'products-db',
port: 5432
});

// Lista de productos
app.get('/products', async (req, res) => {
const result = await pool.query('SELECT * FROM products');
res.json(result.rows);
});

// Detalle de producto
app.get('/products/:id', async (req, res) => {
const result = await pool.query(
'SELECT * FROM products WHERE id = $1',
[req.params.id]
);
res.json(result.rows[0]);
});

// Verificar stock
app.get('/products/:id/stock', async (req, res) => {
const result = await pool.query(
'SELECT stock FROM products WHERE id = $1',
[req.params.id]
);
res.json({ stock: result.rows[0].stock });
});

// Reducir stock
app.post('/products/:id/decrease-stock', async (req, res) => {
const { quantity } = req.body;

const client = await pool.connect();
try {
await client.query('BEGIN');

// Verificar stock actual
const result = await client.query(
'SELECT stock FROM products WHERE id = $1 FOR UPDATE',
[req.params.id]
);

const currentStock = result.rows[0].stock;
if (currentStock < quantity) {
throw new Error('Stock insuficiente');
}

// Reducir stock
await client.query(
'UPDATE products SET stock = stock - $1 WHERE id = $2',
[quantity, req.params.id]
);

await client.query('COMMIT');
res.json({ success: true });
} catch (error) {
await client.query('ROLLBACK');
res.status(400).json({ error: error.message });
} finally {
client.release();
}
});

app.listen(3002, () => {
console.log('Servicio de productos: http://localhost:3002');
});

// ========== 3. Servicio de Pedidos (order-service.js) ==========
// Puerto: 3003
const express = require('express');
const axios = require('axios');
const app = express();

app.use(express.json());

const orders = []; // En realidad debería usar base de datos

// Crear pedido
app.post('/orders', async (req, res) => {
const { userId, productId, quantity } = req.body;

try {
// 1. Verificar usuario (llamar servicio de usuarios)
const userResponse = await axios.get(
`http://localhost:3001/users/${userId}`
);
const user = userResponse.data;

if (!user) {
return res.status(404).json({ error: 'Usuario no encontrado' });
}

// 2. Consultar info producto (llamar servicio de productos)
const productResponse = await axios.get(
`http://localhost:3002/products/${productId}`
);
const product = productResponse.data;

// 3. Verificar stock
const stockResponse = await axios.get(
`http://localhost:3002/products/${productId}/stock`
);
const { stock } = stockResponse.data;

if (stock < quantity) {
return res.status(400).json({ error: 'Stock insuficiente' });
}

// 4. Reducir stock
await axios.post(
`http://localhost:3002/products/${productId}/decrease-stock`,
{ quantity }
);

// 5. Procesar pago (llamar servicio de pagos)
const total = product.price * quantity;
const paymentResponse = await axios.post(
'http://localhost:3004/payments',
{ userId, amount: total }
);

// 6. Crear pedido
const order = {
id: orders.length + 1,
userId,
productId,
quantity,
total,
status: 'completed',
createdAt: new Date()
};
orders.push(order);

// 7. Publicar evento
await publishEvent('order.created', order);

res.json(order);
} catch (error) {
// Patrón Saga: Transacción compensatoria
console.error('Fallo en pedido:', error.message);

// Restaurar stock
try {
await axios.post(
`http://localhost:3002/products/${productId}/increase-stock`,
{ quantity }
);
} catch (rollbackError) {
console.error('Fallo al restaurar stock:', rollbackError.message);
}

res.status(500).json({ error: 'Fallo al procesar pedido' });
}
});

// Consultar pedido
app.get('/orders/:id', (req, res) => {
const order = orders.find(o => o.id === parseInt(req.params.id));
res.json(order);
});

app.listen(3003, () => {
console.log('Servicio de pedidos: http://localhost:3003');
});

// ========== 4. Servicio de Pagos (payment-service.js) ==========
// Puerto: 3004
const express = require('express');
const app = express();

app.use(express.json());

const payments = [];

app.post('/payments', async (req, res) => {
const { userId, amount } = req.body;

// Llamar API de pago externa (ej: Stripe, Toss Payments)
try {
// Procesamiento de pago real
const payment = {
id: payments.length + 1,
userId,
amount,
status: 'success',
createdAt: new Date()
};
payments.push(payment);

// Publicar evento
await publishEvent('payment.completed', payment);

res.json(payment);
} catch (error) {
res.status(400).json({ error: 'Fallo en pago' });
}
});

app.listen(3004, () => {
console.log('Servicio de pagos: http://localhost:3004');
});

// ========== 5. API Gateway (gateway.js) ==========
// Puerto: 3000
const express = require('express');
const { createProxyMiddleware } = require('http-proxy-middleware');
const app = express();

// Middleware de autenticación
function authenticate(req, res, next) {
const token = req.headers.authorization;
if (!token) {
return res.status(401).json({ error: 'Se requiere autenticación' });
}
// Verificación JWT, etc.
next();
}

// Middleware de registro
app.use((req, res, next) => {
console.log(`${req.method} ${req.path}`);
next();
});

// Proxy servicio de usuarios
app.use('/api/users', authenticate, createProxyMiddleware({
target: 'http://localhost:3001',
pathRewrite: { '^/api/users': '/users' },
changeOrigin: true
}));

// Proxy servicio de productos
app.use('/api/products', createProxyMiddleware({
target: 'http://localhost:3002',
pathRewrite: { '^/api/products': '/products' },
changeOrigin: true
}));

// Proxy servicio de pedidos
app.use('/api/orders', authenticate, createProxyMiddleware({
target: 'http://localhost:3003',
pathRewrite: { '^/api/orders': '/orders' },
changeOrigin: true
}));

// Proxy servicio de pagos
app.use('/api/payments', authenticate, createProxyMiddleware({
target: 'http://localhost:3004',
pathRewrite: { '^/api/payments': '/payments' },
changeOrigin: true
}));

app.listen(3000, () => {
console.log('API Gateway: http://localhost:3000');
});

// ========== 6. Bus de eventos (event-bus.js) ==========
const amqp = require('amqplib');

let connection, channel;

// Conexión RabbitMQ
async function connect() {
connection = await amqp.connect('amqp://localhost');
channel = await connection.createChannel();
}

// Publicar evento
async function publishEvent(eventType, data) {
await channel.assertQueue(eventType);
channel.sendToQueue(
eventType,
Buffer.from(JSON.stringify(data))
);
console.log(`Evento publicado: ${eventType}`, data);
}

// Suscribirse a evento
async function subscribeEvent(eventType, callback) {
await channel.assertQueue(eventType);
channel.consume(eventType, (msg) => {
const data = JSON.parse(msg.content.toString());
console.log(`Evento recibido: ${eventType}`, data);
callback(data);
channel.ack(msg);
});
}

connect();

module.exports = { publishEvent, subscribeEvent };

Ejecutar microservicios con Docker Compose

# docker-compose.yml
version: '3.8'

services:
# API Gateway
gateway:
build: ./gateway
ports:
- "3000:3000"
depends_on:
- user-service
- product-service
- order-service
- payment-service

# Servicio de usuarios
user-service:
build: ./user-service
ports:
- "3001:3001"
environment:
- MONGO_URL=mongodb://mongo:27017/users
depends_on:
- mongo
- rabbitmq

# Servicio de productos
product-service:
build: ./product-service
ports:
- "3002:3002"
environment:
- POSTGRES_URL=postgres://postgres:password@postgres:5432/products
depends_on:
- postgres
- rabbitmq

# Servicio de pedidos
order-service:
build: ./order-service
ports:
- "3003:3003"
environment:
- MYSQL_URL=mysql://root:password@mysql:3306/orders
depends_on:
- mysql
- rabbitmq

# Servicio de pagos
payment-service:
build: ./payment-service
ports:
- "3004:3004"
depends_on:
- rabbitmq

# Bases de datos
mongo:
image: mongo:6
volumes:
- mongo-data:/data/db

postgres:
image: postgres:15
environment:
- POSTGRES_PASSWORD=password
- POSTGRES_DB=products
volumes:
- postgres-data:/var/lib/postgresql/data

mysql:
image: mysql:8
environment:
- MYSQL_ROOT_PASSWORD=password
- MYSQL_DATABASE=orders
volumes:
- mysql-data:/var/lib/mysql

# Cola de mensajes
rabbitmq:
image: rabbitmq:3-management
ports:
- "5672:5672"
- "15672:15672" # UI de gestión

volumes:
mongo-data:
postgres-data:
mysql-data:

Service Mesh (Ejemplo de Istio)

# istio-config.yaml
# Service mesh - Gestión de comunicación entre servicios

apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: order-service
spec:
hosts:
- order-service
http:
# Distribución de tráfico (despliegue canary)
- match:
- headers:
user-type:
exact: beta
route:
- destination:
host: order-service
subset: v2 # Nueva versión
weight: 20
- destination:
host: order-service
subset: v1 # Versión existente
weight: 80

# Política de reintentos
- route:
- destination:
host: order-service
retries:
attempts: 3
perTryTimeout: 2s

# Timeout
timeout: 10s

---
# Circuit Breaker
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
name: payment-service
spec:
host: payment-service
trafficPolicy:
connectionPool:
tcp:
maxConnections: 100
http:
http1MaxPendingRequests: 50
maxRequestsPerConnection: 2
outlierDetection:
consecutiveErrors: 5
interval: 30s
baseEjectionTime: 30s
maxEjectionPercent: 50

🤔 Preguntas frecuentes

P1. ¿Cuándo debo usar microservicios?

R:

✅ Casos apropiados para microservicios:

1. Aplicación de gran escala
- Equipo: Más de 10 personas
- Código: Más de 100,000 líneas
- Usuarios: Cientos de miles o más

2. Necesidad de despliegue rápido
- Despliegues múltiples por día
- Lanzamiento de funciones independientes
- Pruebas A/B frecuentes

3. Necesidad de stack tecnológico diverso
- Elegir tecnología óptima por servicio
- Integración con sistemas legacy

4. Necesidad de escalado independiente
- Mucho tráfico en funciones específicas
- Requisitos de recursos diferentes por servicio

5. Importancia de independencia de equipos
- Desarrollo simultáneo por múltiples equipos
- Minimizar dependencias entre equipos

Ejemplos:
- Netflix: Cientos de microservicios
- Amazon: 2-pizza team (equipo por servicio)
- Uber: Servicios separados por región y función

❌ Casos apropiados para monolítico:

1. Aplicación pequeña
- Equipo: 5 personas o menos
- Funciones: Claras y simples
- Tráfico: Bajo

2. Startup inicial
- Necesidad de desarrollo rápido de MVP
- Requisitos cambian frecuentemente
- Recursos limitados

3. CRUD simple
- Sin lógica de negocio compleja
- Límites de servicio poco claros

4. Falta de experiencia operativa
- Sin equipo DevOps
- Sin experiencia en sistemas distribuidos

Ejemplos:
- Blog, portafolio
- Comercio electrónico pequeño
- Herramientas internas

📊 Lista de verificación para decisión:

□ ¿Equipo de más de 10 personas?
□ ¿Base de código de más de 100,000 líneas?
□ ¿Necesidad frecuente de despliegue independiente?
□ ¿Necesidad de escalado parcial?
□ ¿Hay equipo DevOps?
□ ¿Hay experiencia en sistemas distribuidos?

3 o más marcados → Considerar microservicios
2 o menos → Mantener monolítico

P2. ¿Cuál es el mayor desafío de los microservicios?

R:

// ========== 1. Transacciones distribuidas ==========

// Monolítico: Transacción simple
await db.transaction(async (t) => {
await createOrder(data, t);
await decreaseStock(productId, t);
await processPayment(amount, t);
// Si falla uno, rollback completo
});

// Microservicios: Patrón Saga complejo
async function createOrderSaga(data) {
try {
// Paso 1
const order = await orderService.create(data);

// Paso 2
await productService.decreaseStock(data.productId);

// Paso 3
await paymentService.process(order.total);

return order;
} catch (error) {
// Transacciones compensatorias (en reversa)
await paymentService.refund(order.total);
await productService.increaseStock(data.productId);
await orderService.cancel(order.id);

throw error;
}
}

// ========== 2. Consistencia de datos ==========

// Problema: Datos dispersos en múltiples servicios
// Servicio de usuarios: userId, name
// Servicio de pedidos: userId, orders
// Servicio de pagos: userId, payments

// Solución 1: Event Sourcing
eventBus.on('user.updated', async (event) => {
// Cuando cambia info de usuario, actualizar otros servicios
await orderService.updateUserInfo(event.userId, event.name);
await paymentService.updateUserInfo(event.userId, event.name);
});

// Solución 2: CQRS (Command Query Responsibility Segregation)
// Separar escritura y lectura
// Escritura: Cada servicio independiente
// Lectura: Vista integrada (Read Model)

// ========== 3. Latencia de red ==========

// Monolítico: Llamada a función (rápida)
const user = getUser(userId); // 1ms

// Microservicios: Petición HTTP (lenta)
const user = await axios.get(`http://user-service/users/${userId}`); // 50ms

// Solución: Caché
const redis = require('redis');
const cache = redis.createClient();

async function getUser(userId) {
// 1. Verificar caché
const cached = await cache.get(`user:${userId}`);
if (cached) return JSON.parse(cached);

// 2. Llamar servicio
const response = await axios.get(`http://user-service/users/${userId}`);
const user = response.data;

// 3. Guardar en caché
await cache.setex(`user:${userId}`, 3600, JSON.stringify(user));

return user;
}

// ========== 4. Manejo de fallos de servicio ==========

// Patrón Circuit Breaker
const CircuitBreaker = require('opossum');

const options = {
timeout: 3000, // Timeout de 3 segundos
errorThresholdPercentage: 50, // Al 50% de fallos
resetTimeout: 30000 // Reintentar después de 30 segundos
};

const breaker = new CircuitBreaker(async (userId) => {
return await axios.get(`http://user-service/users/${userId}`);
}, options);

breaker.fallback(() => ({
id: userId,
name: 'Desconocido', // Datos de respaldo
cached: true
}));

// Usar
breaker.fire(userId)
.then(console.log)
.catch(console.error);

// ========== 5. Monitoreo y depuración ==========

// Rastreo distribuido (Distributed Tracing)
// Usar Jaeger, Zipkin

const tracer = require('jaeger-client').initTracer(config);

app.use((req, res, next) => {
const span = tracer.startSpan('http_request');
span.setTag('http.method', req.method);
span.setTag('http.url', req.url);

req.span = span;
next();
});

// Pasar ID de rastreo al llamar entre servicios
await axios.get('http://order-service/orders', {
headers: {
'x-trace-id': req.span.context().toTraceId()
}
});

P3. ¿Cuál es el rol del API Gateway?

R:

// ========== Funciones principales del API Gateway ==========

const express = require('express');
const rateLimit = require('express-rate-limit');
const jwt = require('jsonwebtoken');
const { createProxyMiddleware } = require('http-proxy-middleware');

const app = express();

// ========== 1. Enrutamiento ==========
// El cliente solo necesita conocer un endpoint
app.use('/api/users', createProxyMiddleware({
target: 'http://user-service:3001',
changeOrigin: true
}));

app.use('/api/products', createProxyMiddleware({
target: 'http://product-service:3002',
changeOrigin: true
}));

// ========== 2. Autenticación y autorización ==========
function authenticate(req, res, next) {
const token = req.headers.authorization?.split(' ')[1];

if (!token) {
return res.status(401).json({ error: 'Se requiere token' });
}

try {
const decoded = jwt.verify(token, process.env.JWT_SECRET);
req.user = decoded;
next();
} catch (error) {
res.status(401).json({ error: 'Token inválido' });
}
}

app.use('/api/orders', authenticate, createProxyMiddleware({
target: 'http://order-service:3003'
}));

// ========== 3. Limitación de tasa ==========
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutos
max: 100 // Máximo 100 peticiones
});

app.use('/api/', limiter);

// ========== 4. Balanceo de carga ==========
const productServiceInstances = [
'http://product-service-1:3002',
'http://product-service-2:3002',
'http://product-service-3:3002'
];

let currentIndex = 0;

app.use('/api/products', createProxyMiddleware({
target: productServiceInstances[currentIndex],
router: () => {
// Round robin
const target = productServiceInstances[currentIndex];
currentIndex = (currentIndex + 1) % productServiceInstances.length;
return target;
}
}));

// ========== 5. Transformación de petición/respuesta ==========
app.use('/api/legacy', createProxyMiddleware({
target: 'http://legacy-service:8080',
onProxyReq: (proxyReq, req) => {
// Transformar petición
proxyReq.setHeader('X-API-Version', '2.0');
},
onProxyRes: (proxyRes, req, res) => {
// Transformar respuesta
proxyRes.headers['X-Custom-Header'] = 'Gateway';
}
}));

// ========== 6. Caché ==========
const redis = require('redis');
const cache = redis.createClient();

app.get('/api/products/:id', async (req, res) => {
const cacheKey = `product:${req.params.id}`;

// Verificar caché
const cached = await cache.get(cacheKey);
if (cached) {
return res.json(JSON.parse(cached));
}

// Llamar servicio
const response = await axios.get(
`http://product-service:3002/products/${req.params.id}`
);

// Guardar en caché
await cache.setex(cacheKey, 3600, JSON.stringify(response.data));

res.json(response.data);
});

// ========== 7. Registro y monitoreo ==========
app.use((req, res, next) => {
const start = Date.now();

res.on('finish', () => {
const duration = Date.now() - start;
console.log({
method: req.method,
path: req.path,
status: res.statusCode,
duration: `${duration}ms`,
user: req.user?.id
});
});

next();
});

// ========== 8. Manejo de errores ==========
app.use((err, req, res, next) => {
console.error('Gateway Error:', err);

if (err.code === 'ECONNREFUSED') {
return res.status(503).json({
error: 'Servicio no disponible'
});
}

res.status(500).json({
error: 'Ocurrió un error del servidor'
});
});

// ========== 9. Descubrimiento de servicios ==========
const consul = require('consul')();

async function getServiceUrl(serviceName) {
const result = await consul.health.service({
service: serviceName,
passing: true // Solo instancias que pasan health check
});

if (result.length === 0) {
throw new Error(`No se encontró el servicio ${serviceName}`);
}

// Seleccionar aleatoriamente
const instance = result[Math.floor(Math.random() * result.length)];
return `http://${instance.Service.Address}:${instance.Service.Port}`;
}

app.listen(3000);

P4. ¿Cuáles son las estrategias de despliegue de microservicios?

R:

# ========== 1. Despliegue Blue-Green ==========
# Desplegar nueva versión (Green) y cambiar tráfico de una vez

# Blue (versión actual)
apiVersion: v1
kind: Service
metadata:
name: order-service
spec:
selector:
app: order-service
version: blue # Tráfico actual
ports:
- port: 80

---
# Desplegar Green (nueva versión)
kubectl apply -f order-service-green.yaml

# Cambiar tráfico después de probar
kubectl patch service order-service -p '{"spec":{"selector":{"version":"green"}}}'

# Rollback inmediato si hay problemas
kubectl patch service order-service -p '{"spec":{"selector":{"version":"blue"}}}'

# ========== 2. Despliegue Canary ==========
# Enviar solo parte del tráfico a nueva versión, expandir gradualmente

apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: order-service
spec:
hosts:
- order-service
http:
- route:
- destination:
host: order-service
subset: v1 # Versión existente
weight: 90 # 90% tráfico
- destination:
host: order-service
subset: v2 # Nueva versión
weight: 10 # 10% tráfico

# Aumentar gradualmente
# 10% → 25% → 50% → 75% → 100%

# ========== 3. Actualización continua ==========
# Estrategia predeterminada de Kubernetes

apiVersion: apps/v1
kind: Deployment
metadata:
name: order-service
spec:
replicas: 5
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1 # Máximo 1 adicional
maxUnavailable: 1 # Máximo 1 no disponible
template:
spec:
containers:
- name: order-service
image: order-service:v2

# Orden:
# 1. Iniciar 1 nuevo Pod
# 2. Terminar 1 Pod existente cuando pase health check
# 3. Repetir (hasta reemplazar los 5)

# ========== 4. Despliegue con Docker Compose ==========
# docker-compose.yml

version: '3.8'

services:
order-service:
image: order-service:latest
deploy:
replicas: 3
update_config:
parallelism: 1 # Uno a la vez
delay: 10s # Intervalo de 10 segundos
failure_action: rollback # Rollback si falla
restart_policy:
condition: on-failure

# Desplegar
docker stack deploy -c docker-compose.yml myapp

# ========== 5. Pipeline CI/CD ==========
# .github/workflows/deploy.yml

name: Deploy Microservices

on:
push:
branches: [main]

jobs:
deploy:
runs-on: ubuntu-latest
steps:
# Desplegar cada servicio independientemente
- name: Detectar servicios modificados
uses: dorny/paths-filter@v2
id: changes
with:
filters: |
user-service:
- 'services/user/**'
product-service:
- 'services/product/**'

- name: Desplegar servicio de usuarios
if: steps.changes.outputs.user-service == 'true'
run: |
docker build -t user-service:${{ github.sha }} services/user
docker push user-service:${{ github.sha }}
kubectl set image deployment/user-service user-service=user-service:${{ github.sha }}

- name: Desplegar servicio de productos
if: steps.changes.outputs.product-service == 'true'
run: |
docker build -t product-service:${{ github.sha }} services/product
docker push product-service:${{ github.sha }}
kubectl set image deployment/product-service product-service=product-service:${{ github.sha }}

P5. ¿Cómo migrar de monolítico a microservicios?

R:

// ========== Estrategia de migración por etapas ==========

// ========== Etapa 1: Patrón Strangler Fig ==========
// Nuevas funciones en microservicios, mantener funciones existentes

┌─────────────────────────────────┐
Aplicación monolítica │
│ ┌──────────┬──────────┬──────┐ │
│ │Gestión │Gestión │Gestión│ │
│ │usuarios │productos │pedidos│ │
│ └──────────┴──────────┴──────┘ │
└─────────────────────────────────┘

// Paso 1: Nueva función (notificaciones) como microservicio
┌─────────────────────┐ ┌──────────────┐
Monolítico │ │Servicio │
│ usuarios│productos│pedidos│ notificaciones│
└─────────────────────┘ └──────────────┘

// Paso 2: Separar función de pedidos
┌─────────────────────┐ ┌──────────────┐
Monolítico │ │Servicio │
│ usuarios│productos│ │pedidos │
└─────────────────────┘ └──────────────┘
┌──────────────┐
│Servicio │
│notificaciones│
└──────────────┘

// Paso 3: Separar todas las funciones
┌──────────┐ ┌──────────┐ ┌──────────┐
│Servicio │ │Servicio │ │Servicio │
│usuarios │ │productos │ │pedidos │
└──────────┘ └──────────┘ └──────────┘
┌──────────┐
│Servicio │
│notificaciones│
└──────────┘

// ========== Etapa 2: Introducir API Gateway ==========

// Antes: Cliente llama directamente al monolítico
const response = await fetch('http://monolith/api/orders');

// Después: Llamar a través de API Gateway
const response = await fetch('http://api-gateway/api/orders');

// API Gateway hace enrutamiento
if (route === '/api/orders') {
// Enrutar a nuevo servicio
proxy('http://order-service/orders');
} else {
// Todavía al monolítico
proxy('http://monolith/api');
}

// ========== Etapa 3: Separar base de datos ==========

// Problema: Base de datos compartida
┌────────────────┐
BD monolítica │
│ ┌──────────┐ │
│ │Users │ │
│ │Products │ │
│ │Orders │ │
│ └──────────┘ │
└────────────────┘

// Solución: Base de datos por servicio

// 1) Escritura dual (Dual Write)
async function createOrder(data) {
// Escribir en BD monolítica
await monolithDB.orders.create(data);

// También escribir en BD del nuevo servicio
await orderServiceDB.orders.create(data);
}

// 2) Captura de cambios de datos (CDC)
// Sincronizar automáticamente cambios de BD monolítica
const debezium = require('debezium');

debezium.on('orders.insert', async (change) => {
// Reflejar en BD del nuevo servicio
await orderServiceDB.orders.create(change.data);
});

// 3) Separación completa
┌──────────┐ ┌──────────┐ ┌──────────┐
BD Users │ │BD │ │BD Orders
(MongoDB) │ │Products │ (Postgres)
│ │ (MySQL) │ │ │
└──────────┘ └──────────┘ └──────────┘

// ========== Etapa 4: Cambio gradual de tráfico ==========

// Ajustar proporción en API Gateway
const MIGRATION_PERCENTAGE = 10; // Solo 10% a nuevo servicio

app.use('/api/orders', (req, res, next) => {
if (Math.random() * 100 < MIGRATION_PERCENTAGE) {
// A nuevo servicio
proxy('http://order-service/orders')(req, res, next);
} else {
// A monolítico
proxy('http://monolith/api/orders')(req, res, next);
}
});

// Aumentar gradualmente
// 10% → 25% → 50% → 75% → 100%

// ========== Etapa 5: Monitoreo y preparar rollback ==========

const NEW_SERVICE_ERROR_THRESHOLD = 0.05; // 5% tasa de error

async function monitorNewService() {
const errorRate = await getErrorRate('order-service');

if (errorRate > NEW_SERVICE_ERROR_THRESHOLD) {
// Rollback si tasa de error es alta
console.error('Tasa de error alta! Rollback a monolítico');
MIGRATION_PERCENTAGE = 0;

// Notificar
await sendAlert('Rollback de migración ocurrió');
}
}

// ========== Lista de verificación práctica ==========

Lista de verificación de migración:

1. Identificar límites
- Diseño orientado al dominio (DDD)
- Dividir por función empresarial

2. Comenzar con función más independiente
- Pocas dependencias
- Poco impacto empresarial
- Ej: notificaciones, registro, búsqueda

3. Introducir API Gateway
- Cambio gradual de tráfico

4. Estrategia de separación de BD
- Escritura dual → CDCSeparación completa

5. Reforzar monitoreo
- Tasa de error, tiempo de respuesta, tráfico
- Preparar rollback

6. Capacitación del equipo
- Arquitectura de microservicios
- Herramientas DevOps (Docker, Kubernetes)

7. Documentación
- Catálogo de servicios
- Documentación de API
- Guía de despliegue

🎓 Siguientes pasos

Después de comprender la arquitectura de microservicios, aprende lo siguiente:

  1. ¿Qué es Docker? (documento en desarrollo) - Contenerización
  2. ¿Qué es CI/CD? - Despliegue automatizado
  3. REST API vs GraphQL - Diseño de API

Práctica

# ========== 1. Práctica simple de microservicios ==========

# Estructura del proyecto
mkdir microservices-demo
cd microservices-demo

# Crear servicios
mkdir -p services/{user,product,order}
mkdir gateway

# Escribir Docker Compose
cat > docker-compose.yml

# Ejecutar
docker-compose up -d

# Verificar logs
docker-compose logs -f

# ========== 2. Despliegue en Kubernetes ==========

# Instalar minikube (K8s local)
brew install minikube
minikube start

# Desplegar
kubectl apply -f kubernetes/

# Verificar servicios
kubectl get pods
kubectl get services

# Verificar logs
kubectl logs <pod-name>

# ========== 3. Instalar Istio (Service Mesh) ==========

# Instalar Istio
istioctl install --set profile=demo -y

# Inyectar sidecar en servicios
kubectl label namespace default istio-injection=enabled

# Dashboard de Istio
istioctl dashboard kiali

🎬 Conclusión

La arquitectura de microservicios proporciona escalabilidad y flexibilidad:

  • Independencia: Desarrollo, despliegue y escalado independiente por servicio
  • Diversidad tecnológica: Elegir la mejor tecnología para cada servicio
  • Aislamiento de fallos: Un fallo de servicio no afecta al conjunto
  • Autonomía de equipos: Equipos responsables de servicios

Pero la complejidad aumenta, ¡así que elija según la escala del proyecto y la capacidad del equipo! 🧩