Sistema de Microservicios
Fox Framework proporciona un conjunto completo de herramientas y utilidades para implementar arquitecturas de microservicios escalables, resilientes y fáciles de mantener.
Características Principales
- Descubrimiento de Servicios: Registro y descubrimiento automático
- Comunicación entre Servicios: Métodos síncronos y asíncronos
- Balanceo de Carga: Distribución inteligente de tráfico
- Circuit Breaker: Prevención de fallos en cascada
- Orquestación de Servicios: Gestión del ciclo de vida
- API Gateway: Enrutamiento y agregación de servicios
- Rastreo Distribuido: Seguimiento de peticiones entre servicios
- Healthchecks: Monitorización de salud de servicios
Arquitectura de Microservicios
Conceptos Básicos
Fox Framework implementa una arquitectura de microservicios moderna que sigue estos principios:
- Servicios Independientes: Cada microservicio es una aplicación autónoma
- Base de Datos Dedicada: Cada servicio gestiona sus propios datos
- Comunicación mediante APIs: REST, GraphQL o gRPC
- Resiliencia: Tolerancia a fallos mediante patrones avanzados
- Despliegue Independiente: CI/CD por servicio
- Escalabilidad Horizontal: Capacidad para escalar servicios individualmente
Topología de Servicios
┌─────────────────┐
│ │
│ API Gateway │
│ │
└────────┬────────┘
│
┌───────────┬─────────┼─────────┬───────────┐
│ │ │ │ │
┌─────────▼─────┐ ┌───▼───┐ ┌───▼───┐ ┌───▼───┐ ┌─────▼─────┐
│ │ │ │ │ │ │ │ │ │
│ Auth Service │ │ User │ │ Order │ │Product│ │ Payment │
│ │ │Service│ │Service│ │Service│ │ Service │
│ │ │ │ │ │ │ │ │ │
└───────┬───────┘ └───┬───┘ └───┬───┘ └───┬───┘ └─────┬─────┘
│ │ │ │ │
┌─────▼─────┐ ┌───▼───┐ ┌───▼───┐ ┌───▼───┐ ┌───▼───┐
│ │ │ │ │ │ │ │ │ │
│ Auth DB │ │User DB│ │Order DB│ │Prod DB│ │Pay DB │
│ │ │ │ │ │ │ │ │ │
└───────────┘ └───────┘ └───────┘ └───────┘ └───────┘Configuración Básica
Definición de Servicio
import { FoxFactory, MicroserviceConfig } from '@foxframework/core';
// Configuración del microservicio
const serviceConfig: MicroserviceConfig = {
// Información básica del servicio
name: 'user-service',
version: '1.0.0',
description: 'User management service',
// Puerto para la API HTTP
port: parseInt(process.env.PORT || '3001'),
// Descubrimiento de servicios
discovery: {
// Tipo de proveedor: consul, etcd, zookeeper, etc.
provider: 'consul',
options: {
host: process.env.CONSUL_HOST || 'localhost',
port: parseInt(process.env.CONSUL_PORT || '8500'),
serviceName: 'user-service',
serviceId: `user-service-${generateServiceId()}`,
// Intervalo de registro en ms
ttl: 30000,
// Tags para metadatos
tags: ['api', 'users', 'v1']
}
},
// Comunicación entre servicios
communication: {
// Comunicación síncrona
http: {
enabled: true,
// Opciones para balanceo de carga
loadBalancing: 'round-robin', // 'round-robin', 'random', 'least-conn'
},
// Comunicación asíncrona vía eventos
events: {
enabled: true,
transport: 'rabbitmq',
options: {
url: process.env.RABBITMQ_URL || 'amqp://localhost',
exchange: 'fox-events',
queue: 'user-service',
durable: true
},
// Eventos publicados por este servicio
publications: [
'user.created',
'user.updated',
'user.deleted',
'user.login'
],
// Eventos consumidos por este servicio
subscriptions: [
'order.created',
'payment.completed'
]
},
// Soporte para gRPC (opcional)
grpc: {
enabled: false,
port: 50051,
protoFile: './proto/user.proto'
}
},
// Patrones de resiliencia
resilience: {
// Circuit Breaker
circuitBreaker: {
enabled: true,
failureThreshold: 5, // Número de fallos para abrir el circuito
resetTimeout: 30000, // Tiempo para reset en ms
fallbackResponse: { status: 'degraded', message: 'Service temporarily unavailable' }
},
// Reintentos
retry: {
enabled: true,
maxAttempts: 3,
backoff: {
type: 'exponential',
initialDelay: 100
}
},
// Timeouts
timeout: {
enabled: true,
duration: 5000 // ms
}
},
// Monitorización
monitoring: {
health: {
enabled: true,
path: '/health',
interval: 10000 // ms
},
metrics: {
enabled: true,
provider: 'prometheus',
path: '/metrics'
},
tracing: {
enabled: true,
provider: 'jaeger',
options: {
serviceName: 'user-service',
endpoint: process.env.JAEGER_ENDPOINT
}
}
},
// Configuración de API
api: {
// Documentación automática
docs: {
enabled: true,
path: '/docs',
title: 'User Service API',
version: '1.0.0'
},
// Cors
cors: {
enabled: true,
origins: ['*'],
methods: ['GET', 'POST', 'PUT', 'DELETE'],
allowedHeaders: ['Content-Type', 'Authorization']
}
}
};
// Crear el servicio con FoxFactory
const service = FoxFactory.createMicroservice(serviceConfig);
// Definir rutas y controladores
const router = service.router;
router.get('/users', userController.getAll);
router.get('/users/:id', userController.getById);
router.post('/users', userController.create);
router.put('/users/:id', userController.update);
router.delete('/users/:id', userController.delete);
// Iniciar el servicio
service.start()
.then(() => {
console.log(`User service started on port ${service.config.port}`);
})
.catch(err => {
console.error('Failed to start service:', err);
process.exit(1);
});
// Manejo graceful shutdown
process.on('SIGTERM', async () => {
await service.stop();
process.exit(0);
});Comunicación entre Servicios
Cliente HTTP para Comunicación Síncrona
import { ServiceClient } from '@foxframework/microservices';
// Crear un cliente para el servicio de productos
const productClient = new ServiceClient({
// Nombre del servicio a consumir
serviceName: 'product-service',
// Opciones de descubrimiento (mismas que en la configuración del servicio)
discovery: {
provider: 'consul',
options: {
host: process.env.CONSUL_HOST || 'localhost',
port: parseInt(process.env.CONSUL_PORT || '8500')
}
},
// Circuit Breaker
circuitBreaker: {
enabled: true,
failureThreshold: 3,
resetTimeout: 10000
},
// Retry
retry: {
enabled: true,
maxAttempts: 3
},
// Timeout
timeout: 5000,
// Opciones de caché
cache: {
enabled: true,
ttl: 60000, // 1 minuto
maxSize: 1000
}
});
// Uso del cliente en servicios
export class OrderService {
async createOrder(userId: string, items: Array<{productId: string, quantity: number}>) {
// Verificar productos y precios
const productDetails = await Promise.all(
items.map(async item => {
// Llamada al servicio de productos
const product = await productClient.get(`/products/${item.productId}`);
if (!product) {
throw new Error(`Product not found: ${item.productId}`);
}
return {
...item,
price: product.price,
name: product.name
};
})
);
// Calcular total
const total = productDetails.reduce(
(sum, item) => sum + (item.price * item.quantity),
0
);
// Crear orden en la base de datos
const order = await orderRepository.create({
userId,
items: productDetails,
total,
status: 'pending',
createdAt: new Date()
});
// Emitir evento de orden creada
await eventBus.emit('order.created', {
orderId: order.id,
userId,
items: productDetails,
total
});
return order;
}
}Comunicación Basada en Eventos
import { EventSubscriber, EventPublisher } from '@foxframework/microservices';
// Subscriber para eventos
class OrderEventSubscriber extends EventSubscriber {
constructor(private orderService: OrderService) {
super();
// Registrar manejadores de eventos
this.on('payment.completed', this.handlePaymentCompleted.bind(this));
this.on('shipment.created', this.handleShipmentCreated.bind(this));
}
async handlePaymentCompleted(event) {
const { orderId, paymentId, amount } = event.payload;
try {
// Actualizar estado de la orden
await this.orderService.updateOrderStatus(orderId, 'paid', {
paymentId,
paidAmount: amount,
paidAt: new Date()
});
console.log(`Order ${orderId} marked as paid`);
} catch (error) {
console.error(`Error processing payment completion for order ${orderId}:`, error);
// Importante: manejar fallos adecuadamente para evitar pérdida de eventos
}
}
async handleShipmentCreated(event) {
const { orderId, shipmentId, trackingNumber } = event.payload;
try {
await this.orderService.updateOrderStatus(orderId, 'shipped', {
shipmentId,
trackingNumber,
shippedAt: new Date()
});
console.log(`Order ${orderId} marked as shipped`);
} catch (error) {
console.error(`Error processing shipment for order ${orderId}:`, error);
}
}
}
// Publisher para eventos
class OrderEventPublisher extends EventPublisher {
async publishOrderCreated(order) {
await this.publish('order.created', {
orderId: order.id,
userId: order.userId,
items: order.items,
total: order.total,
createdAt: order.createdAt
});
}
async publishOrderStatusChanged(order, prevStatus) {
await this.publish('order.status.changed', {
orderId: order.id,
userId: order.userId,
prevStatus,
newStatus: order.status,
updatedAt: new Date()
});
}
async publishOrderCancelled(order, reason) {
await this.publish('order.cancelled', {
orderId: order.id,
userId: order.userId,
reason,
cancelledAt: new Date()
});
}
}
// Uso en controladores
export class OrderController {
constructor(
private orderService: OrderService,
private publisher: OrderEventPublisher
) {}
async createOrder(req, res) {
try {
const { userId, items } = req.body;
// Crear orden
const order = await this.orderService.createOrder(userId, items);
// Publicar evento
await this.publisher.publishOrderCreated(order);
res.status(201).json(order);
} catch (error) {
res.status(400).json({ error: error.message });
}
}
async cancelOrder(req, res) {
try {
const { id } = req.params;
const { reason } = req.body;
const order = await this.orderService.getOrderById(id);
if (!order) {
return res.status(404).json({ error: 'Order not found' });
}
// Verificar si se puede cancelar
if (!['pending', 'processing'].includes(order.status)) {
return res.status(400).json({
error: `Cannot cancel order in status: ${order.status}`
});
}
// Cancelar orden
const prevStatus = order.status;
await this.orderService.updateOrderStatus(id, 'cancelled', { reason });
// Publicar eventos
await Promise.all([
this.publisher.publishOrderStatusChanged(order, prevStatus),
this.publisher.publishOrderCancelled(order, reason)
]);
res.json({ message: 'Order cancelled successfully' });
} catch (error) {
res.status(500).json({ error: error.message });
}
}
}Comunicación gRPC
import { GrpcService } from '@foxframework/microservices';
// Definir un servicio gRPC
class UserGrpcService extends GrpcService {
constructor(private userRepository) {
super({
protoFile: './proto/user.proto',
packageName: 'user',
serviceName: 'UserService'
});
// Registrar implementaciones de métodos
this.registerMethod('GetUser', this.getUser.bind(this));
this.registerMethod('CreateUser', this.createUser.bind(this));
this.registerMethod('UpdateUser', this.updateUser.bind(this));
this.registerMethod('DeleteUser', this.deleteUser.bind(this));
this.registerMethod('ListUsers', this.listUsers.bind(this));
}
async getUser(call) {
const { id } = call.request;
try {
const user = await this.userRepository.findById(id);
if (!user) {
return {
status: 'NOT_FOUND',
message: 'User not found'
};
}
return {
status: 'SUCCESS',
user: {
id: user.id,
name: user.name,
email: user.email,
role: user.role,
createdAt: user.createdAt.toISOString()
}
};
} catch (error) {
console.error('Error in getUser:', error);
return {
status: 'ERROR',
message: error.message
};
}
}
async createUser(call) {
const { name, email, password, role } = call.request;
try {
// Validar datos
if (!name || !email || !password) {
return {
status: 'INVALID_ARGUMENT',
message: 'Name, email and password are required'
};
}
// Verificar si el email ya existe
const existing = await this.userRepository.findByEmail(email);
if (existing) {
return {
status: 'ALREADY_EXISTS',
message: 'Email already registered'
};
}
// Crear usuario
const user = await this.userRepository.create({
name,
email,
password, // Asumir que el repository maneja el hash
role: role || 'user',
createdAt: new Date()
});
return {
status: 'SUCCESS',
user: {
id: user.id,
name: user.name,
email: user.email,
role: user.role,
createdAt: user.createdAt.toISOString()
}
};
} catch (error) {
console.error('Error in createUser:', error);
return {
status: 'ERROR',
message: error.message
};
}
}
// Otros métodos...
}
// Cliente gRPC
import { GrpcClient } from '@foxframework/microservices';
// Crear cliente
const userGrpcClient = new GrpcClient({
protoFile: './proto/user.proto',
packageName: 'user',
serviceName: 'UserService',
serverAddress: 'user-service:50051'
});
// Uso del cliente
async function getUserDetails(userId) {
try {
const response = await userGrpcClient.call('GetUser', { id: userId });
if (response.status !== 'SUCCESS') {
throw new Error(`Failed to get user: ${response.message}`);
}
return response.user;
} catch (error) {
console.error('Error calling user service:', error);
throw error;
}
}API Gateway
Fox Framework proporciona una implementación de API Gateway para centralizar el acceso a los microservicios:
import { ApiGateway, RouteConfig } from '@foxframework/microservices';
// Configurar API Gateway
const gateway = new ApiGateway({
// Puerto donde se ejecutará el gateway
port: parseInt(process.env.GATEWAY_PORT || '8000'),
// Descubrimiento de servicios
discovery: {
provider: 'consul',
options: {
host: process.env.CONSUL_HOST || 'localhost',
port: parseInt(process.env.CONSUL_PORT || '8500')
}
},
// Autenticación centralizada
auth: {
enabled: true,
provider: 'jwt',
options: {
secret: process.env.JWT_SECRET,
algorithms: ['HS256'],
credentialsRequired: false // Algunas rutas pueden ser públicas
},
// Rutas públicas
publicPaths: [
'/api/auth/login',
'/api/auth/register',
'/api/products',
{ path: '/api/docs', method: 'GET' }
]
},
// Límite de tasa de peticiones
rateLimit: {
enabled: true,
windowMs: 15 * 60 * 1000, // 15 minutos
max: 100, // Límite por IP
message: 'Too many requests, please try again later'
},
// CORS
cors: {
enabled: true,
origins: ['*'],
methods: ['GET', 'POST', 'PUT', 'DELETE'],
allowedHeaders: ['Content-Type', 'Authorization'],
credentials: true
},
// Compresión de respuestas
compression: true,
// Caché
cache: {
enabled: true,
ttl: 60, // segundos
maxSize: 100, // máximo número de entradas
methods: ['GET'], // solo cachear peticiones GET
// Excluir rutas que no deben cachearse
excludePaths: [
'/api/users/me',
'/api/orders/active'
]
},
// Circuit breaker
circuitBreaker: {
enabled: true,
options: {
failureThreshold: 5,
resetTimeout: 30000
}
},
// Logging
logging: {
level: 'info',
format: 'json',
request: true,
response: true
}
});
// Definir rutas y servicios
gateway.addRoutes([
// Rutas para el servicio de autenticación
{
paths: ['/api/auth/*'],
service: 'auth-service',
stripPrefix: '/api/auth'
},
// Rutas para el servicio de usuarios
{
paths: ['/api/users/*'],
service: 'user-service',
stripPrefix: '/api/users',
// Middleware adicional específico para este servicio
middleware: [
// Ejemplo: middleware de autorización para rutas específicas
(req, res, next) => {
if (req.path.includes('/admin') && req.user?.role !== 'admin') {
return res.status(403).json({ error: 'Forbidden' });
}
next();
}
]
},
// Rutas para el servicio de productos
{
paths: ['/api/products/*'],
service: 'product-service',
stripPrefix: '/api/products',
// Caché específica para este servicio
cache: {
enabled: true,
ttl: 300 // 5 minutos
}
},
// Rutas para el servicio de pedidos
{
paths: ['/api/orders/*'],
service: 'order-service',
stripPrefix: '/api/orders',
// Solo usuarios autenticados
requireAuth: true
},
// Agregación de múltiples servicios
{
paths: ['/api/dashboard'],
aggregation: [
{
service: 'user-service',
path: '/stats',
key: 'users'
},
{
service: 'order-service',
path: '/stats',
key: 'orders'
},
{
service: 'product-service',
path: '/stats',
key: 'products'
}
]
}
]);
// Iniciar gateway
gateway.start()
.then(() => {
console.log(`API Gateway running on port ${gateway.config.port}`);
})
.catch(err => {
console.error('Failed to start API Gateway:', err);
process.exit(1);
});Rastreo Distribuido (Tracing)
Fox Framework integra rastreo distribuido para seguir peticiones entre múltiples servicios:
import { TracingService, SpanContext } from '@foxframework/microservices';
// Configurar servicio de rastreo
const tracer = new TracingService({
serviceName: 'order-service',
provider: 'jaeger',
options: {
endpoint: process.env.JAEGER_ENDPOINT || 'http://jaeger:14268/api/traces',
sampler: {
type: 'const',
param: 1 // Muestrear todas las peticiones
}
}
});
// Middleware para rastreo automático de peticiones HTTP
app.use(tracer.middleware());
// Uso manual para operaciones personalizadas
export class OrderService {
constructor(
private orderRepository,
private tracer: TracingService
) {}
async createOrder(userId, items) {
// Crear un span para esta operación
const span = this.tracer.startSpan('createOrder');
try {
// Añadir información al span
span.setTag('userId', userId);
span.setTag('itemCount', items.length);
// Operación de base de datos
const dbSpan = this.tracer.startSpan('database_operation', { childOf: span });
const order = await this.orderRepository.create({
userId,
items,
status: 'pending',
createdAt: new Date()
});
dbSpan.finish();
// Llamada a otro servicio con propagación del contexto de tracing
const context = this.tracer.extract(span.context());
await this.notifyInventory(order, context);
// Añadir metadatos al resultado
span.setTag('orderId', order.id);
span.setTag('success', true);
return order;
} catch (error) {
// Registrar error en el span
span.setTag('error', true);
span.log({
event: 'error',
'error.message': error.message,
stack: error.stack
});
throw error;
} finally {
// Finalizar span
span.finish();
}
}
async notifyInventory(order, parentContext) {
// Crear span como hijo del contexto recibido
const span = this.tracer.startSpan('notifyInventory', {
childOf: parentContext
});
try {
// Añadir contexto de tracing a la llamada HTTP
const headers = {};
this.tracer.inject(span.context(), headers);
// Llamada al servicio de inventario con los headers de tracing
await axios.post(
'http://inventory-service/reserve',
{
orderId: order.id,
items: order.items.map(item => ({
productId: item.productId,
quantity: item.quantity
}))
},
{ headers }
);
span.setTag('success', true);
} catch (error) {
span.setTag('error', true);
span.log({
event: 'error',
message: error.message
});
throw error;
} finally {
span.finish();
}
}
}Monitorización y Métricas
import { MonitoringService, Metrics } from '@foxframework/microservices';
// Configurar servicio de monitorización
const monitoring = new MonitoringService({
serviceName: 'order-service',
provider: 'prometheus',
endpoint: '/metrics',
defaultLabels: {
environment: process.env.NODE_ENV,
serviceVersion: '1.0.0'
}
});
// Crear métricas personalizadas
const metrics = {
// Contador de órdenes creadas
ordersCreated: monitoring.createCounter({
name: 'orders_created_total',
help: 'Total number of orders created',
labelNames: ['status', 'paymentMethod']
}),
// Histograma para tiempo de procesamiento
orderProcessingTime: monitoring.createHistogram({
name: 'order_processing_seconds',
help: 'Time taken to process orders',
buckets: [0.1, 0.5, 1, 2, 5, 10]
}),
// Gauge para órdenes activas
activeOrders: monitoring.createGauge({
name: 'active_orders',
help: 'Currently active orders'
}),
// Summary para valor de órdenes
orderValue: monitoring.createSummary({
name: 'order_value_summary',
help: 'Summary of order values',
percentiles: [0.5, 0.9, 0.95, 0.99]
})
};
// Uso en el servicio
export class OrderService {
async createOrder(userId, items, paymentMethod) {
const startTime = Date.now();
try {
// Lógica de creación de orden
const order = await orderRepository.create({
userId,
items,
status: 'pending',
paymentMethod,
createdAt: new Date()
});
// Incrementar contador de órdenes
metrics.ordersCreated.inc({
status: 'pending',
paymentMethod
});
// Aumentar gauge de órdenes activas
metrics.activeOrders.inc();
// Registrar valor de la orden
metrics.orderValue.observe(order.total);
return order;
} finally {
// Registrar tiempo de procesamiento
const processingTime = (Date.now() - startTime) / 1000;
metrics.orderProcessingTime.observe(processingTime);
}
}
async completeOrder(orderId) {
// Lógica para completar orden
await orderRepository.updateStatus(orderId, 'completed');
// Decrementar gauge de órdenes activas
metrics.activeOrders.dec();
}
}
// Endpoint para exponer métricas
app.get('/metrics', (req, res) => {
res.set('Content-Type', monitoring.contentType);
res.end(monitoring.getMetrics());
});Health Checks
import { HealthCheckService } from '@foxframework/microservices';
// Configurar health check
const healthCheck = new HealthCheckService({
serviceName: 'order-service',
version: '1.0.0',
path: '/health',
// Intervalo de verificación en ms
checkInterval: 30000
});
// Añadir checks
healthCheck.addCheck('database', async () => {
try {
await db.ping();
return {
status: 'up',
responseTime: 15 // ms
};
} catch (error) {
return {
status: 'down',
message: error.message
};
}
});
healthCheck.addCheck('redis', async () => {
try {
await redisClient.ping();
return {
status: 'up'
};
} catch (error) {
return {
status: 'down',
message: error.message
};
}
});
healthCheck.addCheck('product-service', async () => {
try {
const response = await axios.get('http://product-service/health', {
timeout: 2000
});
return {
status: response.data.status === 'ok' ? 'up' : 'down',
details: response.data
};
} catch (error) {
return {
status: 'down',
message: error.message
};
}
});
// Registrar middleware
app.use('/health', healthCheck.middleware());
// También se puede consultar manualmente
app.get('/admin/system-status', async (req, res) => {
const status = await healthCheck.getStatus();
// Determinar estado general
const isSystemUp = Object.values(status.checks)
.every(check => check.status === 'up');
res.json({
status: isSystemUp ? 'healthy' : 'degraded',
timestamp: status.timestamp,
checks: status.checks
});
});Despliegue y Orquestación
Fox Framework proporciona utilidades para desplegar microservicios en diferentes plataformas:
Docker Compose
version: '3.8'
services:
# API Gateway
api-gateway:
build: ./api-gateway
ports:
- "8000:8000"
environment:
- NODE_ENV=production
- PORT=8000
- CONSUL_HOST=consul
- JWT_SECRET=${JWT_SECRET}
depends_on:
- consul
- auth-service
- user-service
- product-service
- order-service
networks:
- microservices-net
# Servicio de autenticación
auth-service:
build: ./auth-service
environment:
- NODE_ENV=production
- PORT=3000
- CONSUL_HOST=consul
- DB_HOST=auth-db
- JWT_SECRET=${JWT_SECRET}
depends_on:
- consul
- auth-db
networks:
- microservices-net
# Base de datos del servicio de autenticación
auth-db:
image: mongo:5
volumes:
- auth-db-data:/data/db
networks:
- microservices-net
# Servicio de usuarios
user-service:
build: ./user-service
environment:
- NODE_ENV=production
- PORT=3001
- CONSUL_HOST=consul
- DB_HOST=user-db
- RABBITMQ_URL=amqp://guest:guest@rabbitmq:5672
depends_on:
- consul
- user-db
- rabbitmq
networks:
- microservices-net
# Base de datos del servicio de usuarios
user-db:
image: postgres:14-alpine
environment:
- POSTGRES_DB=users
- POSTGRES_USER=appuser
- POSTGRES_PASSWORD=${DB_PASSWORD}
volumes:
- user-db-data:/var/lib/postgresql/data
networks:
- microservices-net
# Servicio de productos
product-service:
build: ./product-service
environment:
- NODE_ENV=production
- PORT=3002
- CONSUL_HOST=consul
- DB_HOST=product-db
- RABBITMQ_URL=amqp://guest:guest@rabbitmq:5672
depends_on:
- consul
- product-db
- rabbitmq
networks:
- microservices-net
# Base de datos del servicio de productos
product-db:
image: postgres:14-alpine
environment:
- POSTGRES_DB=products
- POSTGRES_USER=appuser
- POSTGRES_PASSWORD=${DB_PASSWORD}
volumes:
- product-db-data:/var/lib/postgresql/data
networks:
- microservices-net
# Servicio de órdenes
order-service:
build: ./order-service
environment:
- NODE_ENV=production
- PORT=3003
- CONSUL_HOST=consul
- DB_HOST=order-db
- RABBITMQ_URL=amqp://guest:guest@rabbitmq:5672
depends_on:
- consul
- order-db
- rabbitmq
networks:
- microservices-net
# Base de datos del servicio de órdenes
order-db:
image: postgres:14-alpine
environment:
- POSTGRES_DB=orders
- POSTGRES_USER=appuser
- POSTGRES_PASSWORD=${DB_PASSWORD}
volumes:
- order-db-data:/var/lib/postgresql/data
networks:
- microservices-net
# Consul para descubrimiento de servicios
consul:
image: consul:1.12
ports:
- "8500:8500"
command: agent -server -ui -node=server-1 -bootstrap-expect=1 -client=0.0.0.0
networks:
- microservices-net
# RabbitMQ para comunicación asíncrona
rabbitmq:
image: rabbitmq:3-management
ports:
- "15672:15672"
volumes:
- rabbitmq-data:/var/lib/rabbitmq
networks:
- microservices-net
# Jaeger para tracing distribuido
jaeger:
image: jaegertracing/all-in-one:1.35
ports:
- "16686:16686"
- "14268:14268"
environment:
- COLLECTOR_ZIPKIN_HOST_PORT=9411
networks:
- microservices-net
# Prometheus para métricas
prometheus:
image: prom/prometheus:v2.36.0
volumes:
- ./prometheus/prometheus.yml:/etc/prometheus/prometheus.yml
ports:
- "9090:9090"
networks:
- microservices-net
# Grafana para visualización de métricas
grafana:
image: grafana/grafana:9.0.0
depends_on:
- prometheus
ports:
- "3000:3000"
volumes:
- grafana-data:/var/lib/grafana
- ./grafana/provisioning:/etc/grafana/provisioning
environment:
- GF_SECURITY_ADMIN_PASSWORD=${GRAFANA_PASSWORD}
networks:
- microservices-net
networks:
microservices-net:
driver: bridge
volumes:
auth-db-data:
user-db-data:
product-db-data:
order-db-data:
rabbitmq-data:
grafana-data:Kubernetes
Fox Framework proporciona utilidades para generar archivos de configuración de Kubernetes:
import { KubernetesGenerator } from '@foxframework/microservices';
// Configurar generador
const k8sGenerator = new KubernetesGenerator({
// Información del proyecto
project: 'ecommerce',
namespace: 'ecommerce-app',
// Ruta de salida para archivos K8s
outputDir: './k8s',
// Imagen y registro
registry: 'my-registry.com',
imagePullSecret: 'registry-credentials',
// Etiquetas comunes
commonLabels: {
app: 'ecommerce',
environment: 'production'
},
// Configuración para ingress
ingress: {
enabled: true,
className: 'nginx',
annotations: {
'kubernetes.io/tls-acme': 'true',
'cert-manager.io/cluster-issuer': 'letsencrypt-prod'
},
domain: 'api.example.com'
},
// Configuración global
resources: {
limits: {
cpu: '500m',
memory: '512Mi'
},
requests: {
cpu: '100m',
memory: '256Mi'
}
}
});
// Generar configuración para API Gateway
k8sGenerator.generateService({
name: 'api-gateway',
port: 8000,
replicas: 2,
// Exponer como servicio público
expose: true,
// Rutas de ingress
ingressPaths: ['/'],
// Variables de entorno
env: [
{ name: 'NODE_ENV', value: 'production' },
{ name: 'PORT', value: '8000' },
{ name: 'CONSUL_HOST', value: 'consul.ecommerce-app.svc.cluster.local' },
{ name: 'JWT_SECRET', valueFrom: { secretKeyRef: { name: 'api-secrets', key: 'jwt-secret' } } }
],
// Sondas de salud
healthCheck: {
path: '/health',
port: 8000,
initialDelaySeconds: 30,
periodSeconds: 10
}
});
// Generar configuración para servicio de autenticación
k8sGenerator.generateService({
name: 'auth-service',
port: 3000,
replicas: 2,
// No exponer públicamente
expose: false,
// Variables de entorno
env: [
{ name: 'NODE_ENV', value: 'production' },
{ name: 'PORT', value: '3000' },
{ name: 'DB_HOST', value: 'auth-db.ecommerce-app.svc.cluster.local' },
{ name: 'CONSUL_HOST', value: 'consul.ecommerce-app.svc.cluster.local' },
{ name: 'JWT_SECRET', valueFrom: { secretKeyRef: { name: 'api-secrets', key: 'jwt-secret' } } }
],
// Sondas de salud
healthCheck: {
path: '/health',
port: 3000
},
// Base de datos asociada
database: {
type: 'mongodb',
version: '5.0',
storage: '1Gi',
resources: {
limits: {
cpu: '1',
memory: '1Gi'
},
requests: {
cpu: '500m',
memory: '512Mi'
}
}
}
});
// Generar configuración para otros servicios...
// Generar infraestructura compartida
k8sGenerator.generateInfrastructure({
consul: {
enabled: true,
version: '1.12'
},
rabbitmq: {
enabled: true,
version: '3.9',
storage: '2Gi'
},
jaeger: {
enabled: true
},
prometheus: {
enabled: true
},
grafana: {
enabled: true,
adminPassword: { secretKeyRef: { name: 'monitoring-secrets', key: 'grafana-password' } }
}
});
// Generar todos los archivos
k8sGenerator.generate();Simulación de Microservicios para Desarrollo
Fox Framework incluye un simulador de microservicios para desarrollo local:
import { MicroserviceSimulator } from '@foxframework/microservices';
// Crear simulador
const simulator = new MicroserviceSimulator({
// Puerto para interfaz web del simulador
port: 9000,
// Directorio para almacenar datos simulados
dataDir: './.simulator',
// Servicios a simular
services: [
{
name: 'user-service',
port: 3001,
routes: [
{
path: '/users',
method: 'GET',
response: { statusCode: 200, body: 'users' }
},
{
path: '/users/:id',
method: 'GET',
handler: (req, res) => {
const userId = req.params.id;
if (userId === '123') {
return res.json({
id: '123',
name: 'John Doe',
email: 'john@example.com'
});
}
res.status(404).json({ error: 'User not found' });
}
}
]
},
{
name: 'product-service',
port: 3002,
routes: [
{
path: '/products',
method: 'GET',
response: { statusCode: 200, body: 'products' }
}
]
},
// Más servicios simulados...
],
// Simular latencia en milisegundos
latency: {
min: 50,
max: 200
},
// Simular errores ocasionales
faults: {
// Probabilidad de error (0-1)
probability: 0.05,
// Tipos de error a simular
types: ['timeout', 'error500', 'error503']
},
// Persistencia de datos
persistence: true,
// Dashboard web para monitorizar y controlar
dashboard: {
enabled: true,
path: '/simulator'
}
});
// Iniciar simulador
simulator.start()
.then(() => {
console.log(`Microservice Simulator running on port ${simulator.config.port}`);
console.log(`Dashboard available at http://localhost:${simulator.config.port}/simulator`);
})
.catch(err => {
console.error('Failed to start simulator:', err);
process.exit(1);
});
// Opcional: registrar simulador en Consul para desarrollo local
simulator.registerInConsul({
host: 'localhost',
port: 8500
});Versionado de APIs
import { ApiVersion } from '@foxframework/microservices';
// Configurar versionado de API
const apiVersioning = new ApiVersion({
// Tipo de versionado
type: 'header', // 'header', 'url', 'accept-header', 'query'
// Nombre del header o param
headerName: 'X-API-Version',
paramName: 'version',
// Versión por defecto
defaultVersion: '1',
// Estrategia para URLs
urlType: 'path', // 'path' (/v1/users) o 'subdomain' (v1.api.example.com)
});
// Aplicar middleware
app.use(apiVersioning.middleware());
// Definir rutas versionadas
app.get('/users', apiVersioning.route('1', (req, res) => {
// Implementación v1
res.json({ version: '1', users: [{ id: 1, nombre: 'Ana García' }] });
}));
app.get('/users', apiVersioning.route('2', (req, res) => {
// Implementación v2
res.json({
version: '2',
data: { users: [{ id: 1, nombre: 'Ana García' }] },
meta: { count: 100 }
});
}));
// O usar router versionado
const v1Router = apiVersioning.router('1');
const v2Router = apiVersioning.router('2');
v1Router.get('/products', productControllerV1.getAll);
v2Router.get('/products', productControllerV2.getAll);
app.use(v1Router);
app.use(v2Router);Mejores Prácticas
Fox Framework recomienda las siguientes mejores prácticas para arquitecturas de microservicios:
- Service Boundaries: Definir límites claros de servicios basados en dominios de negocio
- API First: Diseñar APIs antes de implementar servicios
- Error Handling: Implementar patrones de resiliencia como Circuit Breaker y Retry
- Idempotencia: Asegurar que las operaciones se pueden repetir sin efectos secundarios
- Comunicación Asíncrona: Favorecer eventos para comunicación entre servicios
- Testing Independiente: Probar cada servicio de forma aislada
- Diseño para Fallos: Asumir que los servicios pueden fallar y diseñar en consecuencia
- Monitorización: Implementar métricas, logs y traces distribuidos
- Despliegue Independiente: Cada servicio debe poder desplegarse por separado
- CI/CD: Automatizar pruebas y despliegue
Conclusión
El sistema de microservicios de Fox Framework proporciona todas las herramientas necesarias para implementar arquitecturas distribuidas modernas, con un enfoque en la resiliencia, escalabilidad y mantenibilidad.