Microservicios
Este ejemplo muestra cómo crear una arquitectura de microservicios completa usando Fox Framework con service registry, API gateway, circuit breakers y comunicación entre servicios.
Service Registry
// src/services/registry.service.ts
import { ServiceRegistry, LoadBalancer, HealthChecker } from '@foxframework/core/microservices';
export interface ServiceInstance {
id: string;
name: string;
host: string;
port: number;
version: string;
health: 'healthy' | 'unhealthy' | 'unknown';
metadata: Record<string, any>;
registeredAt: Date;
lastHeartbeat: Date;
}
export class MicroserviceRegistry {
private registry: ServiceRegistry;
private loadBalancer: LoadBalancer;
private healthChecker: HealthChecker;
constructor() {
this.registry = new ServiceRegistry({
provider: 'memory', // En producción usar 'consul' o 'etcd'
ttl: 30000, // 30 segundos
heartbeatInterval: 10000 // 10 segundos
});
this.loadBalancer = new LoadBalancer({
algorithm: 'round-robin' // También: 'weighted', 'health-aware'
});
this.healthChecker = new HealthChecker({
interval: 15000, // Check cada 15 segundos
timeout: 5000, // Timeout de 5 segundos
retries: 3
});
this.startHealthChecking();
}
async registerService(service: Omit<ServiceInstance, 'id' | 'registeredAt' | 'lastHeartbeat' | 'health'>): Promise<string> {
const serviceInstance: ServiceInstance = {
...service,
id: `${service.name}-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
health: 'unknown',
registeredAt: new Date(),
lastHeartbeat: new Date()
};
await this.registry.register(serviceInstance);
console.log(`🚀 Service registered: ${serviceInstance.name} (${serviceInstance.id})`);
return serviceInstance.id;
}
async unregisterService(serviceId: string): Promise<void> {
await this.registry.unregister(serviceId);
console.log(`🔴 Service unregistered: ${serviceId}`);
}
async discoverService(serviceName: string): Promise<ServiceInstance[]> {
const services = await this.registry.discover(serviceName);
const healthyServices = services.filter(s => s.health === 'healthy');
if (healthyServices.length === 0) {
throw new Error(`No healthy instances found for service: ${serviceName}`);
}
return healthyServices;
}
async getServiceInstance(serviceName: string): Promise<ServiceInstance> {
const services = await this.discoverService(serviceName);
return this.loadBalancer.select(services);
}
async heartbeat(serviceId: string): Promise<void> {
await this.registry.heartbeat(serviceId);
}
private async startHealthChecking(): Promise<void> {
setInterval(async () => {
const allServices = await this.registry.getAllServices();
for (const service of allServices) {
try {
const isHealthy = await this.healthChecker.check(`http://${service.host}:${service.port}/health`);
await this.registry.updateHealth(service.id, isHealthy ? 'healthy' : 'unhealthy');
} catch (error) {
await this.registry.updateHealth(service.id, 'unhealthy');
}
}
}, this.healthChecker.interval);
}
async getServiceStats(): Promise<any> {
const services = await this.registry.getAllServices();
const stats = {
total: services.length,
healthy: services.filter(s => s.health === 'healthy').length,
unhealthy: services.filter(s => s.health === 'unhealthy').length,
unknown: services.filter(s => s.health === 'unknown').length,
byService: {} as Record<string, number>
};
services.forEach(service => {
stats.byService[service.name] = (stats.byService[service.name] || 0) + 1;
});
return stats;
}
}API Gateway
// src/gateway/api-gateway.ts
import { FoxFactory, Request, Response } from '@foxframework/core';
import { CircuitBreaker } from '@foxframework/core/microservices';
import { SecurityMiddlewareCore, AuthMiddleware } from '@foxframework/core/security';
import { MicroserviceRegistry } from '../services/registry.service';
import axios from 'axios';
export class ApiGateway {
private registry: MicroserviceRegistry;
private circuitBreakers: Map<string, CircuitBreaker>;
constructor() {
this.registry = new MicroserviceRegistry();
this.circuitBreakers = new Map();
}
private getCircuitBreaker(serviceName: string): CircuitBreaker {
if (!this.circuitBreakers.has(serviceName)) {
const breaker = new CircuitBreaker({
timeout: 5000,
errorThreshold: 5,
resetTimeout: 30000,
monitoringPeriod: 60000
});
this.circuitBreakers.set(serviceName, breaker);
}
return this.circuitBreakers.get(serviceName)!;
}
async proxyRequest(serviceName: string, req: Request, res: Response): Promise<void> {
try {
// Obtener instancia del servicio
const serviceInstance = await this.registry.getServiceInstance(serviceName);
const breaker = this.getCircuitBreaker(serviceName);
// Construir URL del servicio
const serviceUrl = `http://${serviceInstance.host}:${serviceInstance.port}${req.originalUrl}`;
// Preparar headers (sin headers internos del gateway)
const headers = { ...req.headers };
delete headers.host;
delete headers['x-forwarded-for'];
// Agregar headers de tracing
headers['x-gateway-request-id'] = req.id || Math.random().toString(36);
headers['x-gateway-service'] = serviceName;
headers['x-forwarded-for'] = req.ip;
// Hacer petición a través del circuit breaker
const response = await breaker.execute(async () => {
return await axios({
method: req.method.toLowerCase() as any,
url: serviceUrl,
headers,
data: req.body,
params: req.query,
timeout: 5000,
validateStatus: () => true // No lanzar error por códigos HTTP
});
});
// Enviar respuesta
res.status(response.status);
// Copiar headers de respuesta (excepto internos)
Object.entries(response.headers).forEach(([key, value]) => {
if (!key.startsWith('x-') && key !== 'content-encoding') {
res.set(key, value as string);
}
});
res.send(response.data);
} catch (error) {
console.error(`Gateway error for ${serviceName}:`, error);
if (error.name === 'CircuitBreakerOpen') {
res.status(503).json({
error: 'Service Unavailable',
message: `Service ${serviceName} is temporarily unavailable`,
details: 'Circuit breaker is open'
});
} else if (error.code === 'ECONNREFUSED') {
res.status(503).json({
error: 'Service Unavailable',
message: `Cannot connect to ${serviceName} service`
});
} else {
res.status(500).json({
error: 'Gateway Error',
message: 'An error occurred while proxying the request'
});
}
}
}
createServer(): any {
return FoxFactory.createServer({
port: 8080,
middleware: [
// Rate limiting global
SecurityMiddlewareCore.rateLimit({
windowMs: 15 * 60 * 1000,
maxRequests: 1000,
message: 'Too many requests from gateway'
}),
// CORS
SecurityMiddlewareCore.cors({
origin: process.env.ALLOWED_ORIGINS?.split(',') || ['*'],
credentials: true
}),
// Security headers
SecurityMiddlewareCore.securityHeaders(),
// Request logging
(req: Request, res: Response, next: any) => {
console.log(`🌐 Gateway: ${req.method} ${req.path} from ${req.ip}`);
next();
}
],
routes: [
// Health check del gateway
{
path: '/health',
method: 'get',
handler: (req: Request, res: Response) => {
res.json({
status: 'healthy',
timestamp: new Date().toISOString(),
uptime: process.uptime(),
gateway: 'fox-api-gateway'
});
}
},
// Service registry endpoints
{
path: '/registry/services',
method: 'get',
handler: async (req: Request, res: Response) => {
const stats = await this.registry.getServiceStats();
res.json(stats);
}
},
// User Service Routes (con autenticación)
{
path: '/api/users/*',
method: 'get',
handler: (req: Request, res: Response) => this.proxyRequest('user-service', req, res),
middleware: [
AuthMiddleware.optionalJwt({
secret: process.env.JWT_SECRET || 'your-secret-key',
algorithms: ['HS256']
})
]
},
{
path: '/api/users/*',
method: 'post',
handler: (req: Request, res: Response) => this.proxyRequest('user-service', req, res),
middleware: [
AuthMiddleware.jwt({
secret: process.env.JWT_SECRET || 'your-secret-key',
algorithms: ['HS256']
})
]
},
{
path: '/api/users/*',
method: 'put',
handler: (req: Request, res: Response) => this.proxyRequest('user-service', req, res),
middleware: [
AuthMiddleware.jwt({
secret: process.env.JWT_SECRET || 'your-secret-key',
algorithms: ['HS256']
})
]
},
{
path: '/api/users/*',
method: 'delete',
handler: (req: Request, res: Response) => this.proxyRequest('user-service', req, res),
middleware: [
AuthMiddleware.jwt({
secret: process.env.JWT_SECRET || 'your-secret-key',
algorithms: ['HS256']
})
]
},
// Product Service Routes
{
path: '/api/products/*',
method: 'get',
handler: (req: Request, res: Response) => this.proxyRequest('product-service', req, res)
},
{
path: '/api/products/*',
method: 'post',
handler: (req: Request, res: Response) => this.proxyRequest('product-service', req, res),
middleware: [
AuthMiddleware.jwt({
secret: process.env.JWT_SECRET || 'your-secret-key',
algorithms: ['HS256']
})
]
},
// Order Service Routes
{
path: '/api/orders/*',
method: 'get',
handler: (req: Request, res: Response) => this.proxyRequest('order-service', req, res),
middleware: [
AuthMiddleware.jwt({
secret: process.env.JWT_SECRET || 'your-secret-key',
algorithms: ['HS256']
})
]
},
{
path: '/api/orders/*',
method: 'post',
handler: (req: Request, res: Response) => this.proxyRequest('order-service', req, res),
middleware: [
AuthMiddleware.jwt({
secret: process.env.JWT_SECRET || 'your-secret-key',
algorithms: ['HS256']
})
]
},
// Notification Service Routes
{
path: '/api/notifications/*',
method: 'get',
handler: (req: Request, res: Response) => this.proxyRequest('notification-service', req, res),
middleware: [
AuthMiddleware.jwt({
secret: process.env.JWT_SECRET || 'your-secret-key',
algorithms: ['HS256']
})
]
},
{
path: '/api/notifications/*',
method: 'post',
handler: (req: Request, res: Response) => this.proxyRequest('notification-service', req, res),
middleware: [
AuthMiddleware.jwt({
secret: process.env.JWT_SECRET || 'your-secret-key',
algorithms: ['HS256']
})
]
}
]
});
}
}User Service
// services/user-service/src/server.ts
import { FoxFactory, Request, Response } from '@foxframework/core';
import { EventEmitter } from '@foxframework/core/events';
import { MicroserviceRegistry } from '../../shared/registry.service';
interface User {
id: number;
email: string;
firstName: string;
lastName: string;
createdAt: Date;
updatedAt: Date;
}
class UserService {
private users: User[] = [
{
id: 1,
email: 'admin@example.com',
firstName: 'Admin',
lastName: 'User',
createdAt: new Date('2024-01-01'),
updatedAt: new Date('2024-01-01')
}
];
private eventEmitter: EventEmitter;
private registry: MicroserviceRegistry;
private serviceId?: string;
constructor() {
this.eventEmitter = new EventEmitter();
this.registry = new MicroserviceRegistry();
}
async startService(): Promise<void> {
// Registrar el servicio
this.serviceId = await this.registry.registerService({
name: 'user-service',
host: 'localhost',
port: 3001,
version: '1.0.0',
metadata: {
description: 'User management service',
endpoints: ['/users', '/users/:id']
}
});
// Configurar heartbeat
setInterval(() => {
if (this.serviceId) {
this.registry.heartbeat(this.serviceId);
}
}, 10000);
// Configurar graceful shutdown
process.on('SIGTERM', () => this.shutdown());
process.on('SIGINT', () => this.shutdown());
}
async shutdown(): Promise<void> {
if (this.serviceId) {
await this.registry.unregisterService(this.serviceId);
}
process.exit(0);
}
// GET /users
getAllUsers = (req: Request, res: Response): void => {
const { page = 1, limit = 10 } = req.query;
const startIndex = (Number(page) - 1) * Number(limit);
const endIndex = startIndex + Number(limit);
const paginatedUsers = this.users.slice(startIndex, endIndex);
res.json({
data: paginatedUsers,
pagination: {
page: Number(page),
limit: Number(limit),
total: this.users.length,
totalPages: Math.ceil(this.users.length / Number(limit))
}
});
};
// GET /users/:id
getUserById = (req: Request, res: Response): void => {
const id = parseInt(req.params.id);
const user = this.users.find(u => u.id === id);
if (!user) {
return res.status(404).json({
error: 'User not found',
message: `User with id ${id} does not exist`
});
}
res.json({ data: user });
};
// POST /users
createUser = async (req: Request, res: Response): Promise<void> => {
const { email, firstName, lastName } = req.body;
// Validación
if (!email || !firstName || !lastName) {
return res.status(400).json({
error: 'Validation failed',
message: 'Email, firstName, and lastName are required'
});
}
// Verificar email único
const existingUser = this.users.find(u => u.email === email);
if (existingUser) {
return res.status(409).json({
error: 'Email already exists',
message: 'A user with this email already exists'
});
}
const newUser: User = {
id: this.users.length + 1,
email,
firstName,
lastName,
createdAt: new Date(),
updatedAt: new Date()
};
this.users.push(newUser);
// Emitir evento para otros servicios
this.eventEmitter.emit('user.created', {
userId: newUser.id,
email: newUser.email,
firstName: newUser.firstName,
timestamp: new Date()
});
res.status(201).json({
data: newUser,
message: 'User created successfully'
});
};
createServer() {
return FoxFactory.createServer({
port: 3001,
middleware: [
(req: Request, res: Response, next: any) => {
console.log(`👤 User Service: ${req.method} ${req.path}`);
next();
}
],
routes: [
// Health check
{
path: '/health',
method: 'get',
handler: (req: Request, res: Response) => {
res.json({
status: 'healthy',
service: 'user-service',
timestamp: new Date().toISOString(),
uptime: process.uptime()
});
}
},
// User routes
{ path: '/users', method: 'get', handler: this.getAllUsers },
{ path: '/users/:id', method: 'get', handler: this.getUserById },
{ path: '/users', method: 'post', handler: this.createUser }
]
});
}
}
// Iniciar el servicio
const userService = new UserService();
userService.startService();
const app = userService.createServer();
app.start().then(() => {
console.log('👤 User Service running on http://localhost:3001');
});Notification Service con SMTP
// services/notification-service/src/server.ts
import { FoxFactory, Request, Response } from '@foxframework/core';
import { EventEmitter } from '@foxframework/core/events';
import { MicroserviceRegistry } from '../../shared/registry.service';
import nodemailer from 'nodemailer';
interface Notification {
id: string;
userId: number;
type: 'email' | 'sms' | 'push';
subject?: string;
message: string;
status: 'pending' | 'sent' | 'failed';
createdAt: Date;
sentAt?: Date;
error?: string;
}
class NotificationService {
private notifications: Notification[] = [];
private emailTransporter: nodemailer.Transporter;
private eventEmitter: EventEmitter;
private registry: MicroserviceRegistry;
private serviceId?: string;
constructor() {
this.eventEmitter = new EventEmitter();
this.registry = new MicroserviceRegistry();
// Configurar transporter SMTP
this.emailTransporter = nodemailer.createTransporter({
host: process.env.SMTP_HOST || 'smtp.gmail.com',
port: parseInt(process.env.SMTP_PORT || '587'),
secure: false,
auth: {
user: process.env.SMTP_USER,
pass: process.env.SMTP_PASS,
},
});
// Suscribirse a eventos de otros servicios
this.setupEventListeners();
}
private setupEventListeners(): void {
// Escuchar eventos de creación de usuarios
this.eventEmitter.on('user.created', async (data: any) => {
await this.sendWelcomeEmail(data.userId, data.email, data.firstName);
});
// Escuchar eventos de pedidos
this.eventEmitter.on('order.created', async (data: any) => {
await this.sendOrderConfirmationEmail(data.userId, data.orderId, data.email);
});
}
async startService(): Promise<void> {
this.serviceId = await this.registry.registerService({
name: 'notification-service',
host: 'localhost',
port: 3004,
version: '1.0.0',
metadata: {
description: 'Notification service with email, SMS and push support',
capabilities: ['email', 'sms', 'push']
}
});
setInterval(() => {
if (this.serviceId) {
this.registry.heartbeat(this.serviceId);
}
}, 10000);
process.on('SIGTERM', () => this.shutdown());
process.on('SIGINT', () => this.shutdown());
}
async shutdown(): Promise<void> {
if (this.serviceId) {
await this.registry.unregisterService(this.serviceId);
}
process.exit(0);
}
async sendEmail(to: string, subject: string, html: string): Promise<void> {
try {
await this.emailTransporter.sendMail({
from: `"${process.env.APP_NAME}" <${process.env.SMTP_FROM}>`,
to,
subject,
html,
});
} catch (error) {
console.error('Email sending failed:', error);
throw error;
}
}
async sendWelcomeEmail(userId: number, email: string, firstName: string): Promise<void> {
const notification: Notification = {
id: `notif-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
userId,
type: 'email',
subject: 'Welcome to our platform!',
message: `Welcome ${firstName}! Your account has been created successfully.`,
status: 'pending',
createdAt: new Date()
};
this.notifications.push(notification);
try {
await this.sendEmail(
email,
'Welcome to our platform!',
`
<div style="max-width: 600px; margin: 0 auto; font-family: Arial, sans-serif;">
<h2>Welcome ${firstName}!</h2>
<p>Your account has been created successfully.</p>
<p>You can now start using our platform and explore all the features we have to offer.</p>
<div style="text-align: center; margin: 30px 0;">
<a href="${process.env.FRONTEND_URL}/dashboard"
style="background-color: #007bff; color: white; padding: 12px 24px;
text-decoration: none; border-radius: 4px; display: inline-block;">
Get Started
</a>
</div>
<p>If you have any questions, feel free to contact our support team.</p>
<p>Best regards,<br>The Team</p>
</div>
`
);
notification.status = 'sent';
notification.sentAt = new Date();
} catch (error) {
notification.status = 'failed';
notification.error = error instanceof Error ? error.message : 'Unknown error';
}
}
async sendOrderConfirmationEmail(userId: number, orderId: string, email: string): Promise<void> {
const notification: Notification = {
id: `notif-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
userId,
type: 'email',
subject: 'Order Confirmation',
message: `Your order ${orderId} has been confirmed.`,
status: 'pending',
createdAt: new Date()
};
this.notifications.push(notification);
try {
await this.sendEmail(
email,
'Order Confirmation',
`
<div style="max-width: 600px; margin: 0 auto; font-family: Arial, sans-serif;">
<h2>Order Confirmed!</h2>
<p>Thank you for your order. Your order #${orderId} has been confirmed and is being processed.</p>
<div style="background-color: #f8f9fa; padding: 20px; border-radius: 4px; margin: 20px 0;">
<h3>Order Details</h3>
<p><strong>Order ID:</strong> ${orderId}</p>
<p><strong>Status:</strong> Confirmed</p>
<p><strong>Estimated Delivery:</strong> 3-5 business days</p>
</div>
<div style="text-align: center; margin: 30px 0;">
<a href="${process.env.FRONTEND_URL}/orders/${orderId}"
style="background-color: #28a745; color: white; padding: 12px 24px;
text-decoration: none; border-radius: 4px; display: inline-block;">
Track Your Order
</a>
</div>
<p>We'll send you another email when your order ships.</p>
</div>
`
);
notification.status = 'sent';
notification.sentAt = new Date();
} catch (error) {
notification.status = 'failed';
notification.error = error instanceof Error ? error.message : 'Unknown error';
}
}
// POST /notifications
sendNotification = async (req: Request, res: Response): Promise<void> => {
const { userId, type, subject, message, email } = req.body;
if (!userId || !type || !message) {
return res.status(400).json({
error: 'Validation failed',
message: 'userId, type, and message are required'
});
}
const notification: Notification = {
id: `notif-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
userId,
type,
subject,
message,
status: 'pending',
createdAt: new Date()
};
this.notifications.push(notification);
try {
if (type === 'email' && email) {
await this.sendEmail(email, subject || 'Notification', message);
notification.status = 'sent';
notification.sentAt = new Date();
} else {
throw new Error(`Unsupported notification type: ${type}`);
}
res.status(201).json({
data: notification,
message: 'Notification sent successfully'
});
} catch (error) {
notification.status = 'failed';
notification.error = error instanceof Error ? error.message : 'Unknown error';
res.status(500).json({
error: 'Notification failed',
message: 'Failed to send notification',
details: notification.error
});
}
};
// GET /notifications
getNotifications = (req: Request, res: Response): void => {
const { userId, status } = req.query;
let filteredNotifications = this.notifications;
if (userId) {
filteredNotifications = filteredNotifications.filter(n => n.userId === parseInt(userId as string));
}
if (status) {
filteredNotifications = filteredNotifications.filter(n => n.status === status);
}
res.json({
data: filteredNotifications,
total: filteredNotifications.length
});
};
createServer() {
return FoxFactory.createServer({
port: 3004,
middleware: [
(req: Request, res: Response, next: any) => {
console.log(`📧 Notification Service: ${req.method} ${req.path}`);
next();
}
],
routes: [
// Health check
{
path: '/health',
method: 'get',
handler: (req: Request, res: Response) => {
res.json({
status: 'healthy',
service: 'notification-service',
timestamp: new Date().toISOString(),
uptime: process.uptime(),
emailConfig: {
host: process.env.SMTP_HOST,
port: process.env.SMTP_PORT,
user: process.env.SMTP_USER ? '***' : 'not configured'
}
});
}
},
// Notification routes
{ path: '/notifications', method: 'get', handler: this.getNotifications },
{ path: '/notifications', method: 'post', handler: this.sendNotification }
]
});
}
}
// Iniciar el servicio
const notificationService = new NotificationService();
notificationService.startService();
const app = notificationService.createServer();
app.start().then(() => {
console.log('📧 Notification Service running on http://localhost:3004');
});Inicialización Completa
// start-microservices.ts
import { ApiGateway } from './src/gateway/api-gateway';
async function startMicroservices() {
console.log('🚀 Starting Fox Framework Microservices...');
// Iniciar API Gateway
const gateway = new ApiGateway();
const gatewayApp = gateway.createServer();
await gatewayApp.start();
console.log('🌐 API Gateway running on http://localhost:8080');
console.log('\n📊 Service Architecture:');
console.log(' API Gateway : http://localhost:8080');
console.log(' User Service : http://localhost:3001');
console.log(' Product Service : http://localhost:3002');
console.log(' Notification Service: http://localhost:3004');
console.log('\n🔍 Available Endpoints:');
console.log(' GET /registry/services - Service registry stats');
console.log(' GET /api/users - User management');
console.log(' GET /api/products - Product catalog');
console.log(' GET /api/notifications - Notification history');
}
startMicroservices().catch(console.error);Docker Compose
# docker-compose.yml
version: '3.8'
services:
api-gateway:
build: ./
ports:
- "8080:8080"
environment:
- NODE_ENV=production
- JWT_SECRET=${JWT_SECRET}
depends_on:
- user-service
- notification-service
user-service:
build: ./services/user-service
ports:
- "3001:3001"
environment:
- NODE_ENV=production
notification-service:
build: ./services/notification-service
ports:
- "3004:3004"
environment:
- NODE_ENV=production
- SMTP_HOST=${SMTP_HOST}
- SMTP_USER=${SMTP_USER}
- SMTP_PASS=${SMTP_PASS}
consul:
image: hashicorp/consul:1.19
ports:
- "8500:8500"
command: agent -server -bootstrap -ui -client=0.0.0.0
prometheus:
image: prom/prometheus
ports:
- "9090:9090"
volumes:
- ./monitoring/prometheus.yml:/etc/prometheus/prometheus.ymlCaracterísticas Destacadas
- Service Registry: Descubrimiento automático de servicios con health checks
- API Gateway: Routing inteligente con circuit breakers y rate limiting
- Load Balancing: Algoritmos múltiples para distribución de carga
- Circuit Breakers: Protección contra fallos en cascada
- Event-Driven Communication: Comunicación asíncrona entre servicios
- SMTP Integration: Servicio de notificaciones con emails transaccionales
- Health Monitoring: Endpoints de salud en todos los servicios
- Graceful Shutdown: Desregistro automático de servicios al cerrar
- Distributed Tracing: Headers de trazabilidad entre servicios