Ejemplos
Aplicación Completa

Aplicación Completa

Este ejemplo muestra cómo crear una aplicación web completa usando Fox Framework, integrando todas las características principales: autenticación JWT, base de datos, eventos, microservicios, SMTP, cache, y despliegue con Docker.

Arquitectura de la Aplicación

La aplicación es un E-commerce Platform completo que incluye:

  • API Gateway: Punto de entrada único para todos los servicios
  • User Service: Gestión de usuarios y autenticación
  • Product Service: Catálogo de productos
  • Order Service: Procesamiento de pedidos
  • Notification Service: Emails y notificaciones push
  • Payment Service: Procesamiento de pagos
  • Analytics Service: Métricas y reportes
  • Admin Dashboard: Panel de administración

Estructura del Proyecto

fox-ecommerce/
├── docker-compose.yml
├── .env
├── gateway/
│   ├── Dockerfile
│   ├── package.json
│   └── src/
│       ├── server.ts
│       ├── middleware/
│       └── routes/
├── services/
│   ├── user-service/
│   ├── product-service/
│   ├── order-service/
│   ├── notification-service/
│   ├── payment-service/
│   └── analytics-service/
├── shared/
│   ├── events/
│   ├── database/
│   ├── cache/
│   └── utils/
├── frontend/
│   ├── customer-app/
│   └── admin-dashboard/
└── infrastructure/
    ├── nginx/
    ├── postgres/
    ├── redis/
    └── monitoring/

API Gateway Principal

// gateway/src/server.ts
import { FoxFactory, Request, Response } from '@foxframework/core';
import { AuthMiddleware, SecurityMiddlewareCore } from '@foxframework/core/security';
import { CircuitBreaker, ServiceRegistry } from '@foxframework/core/microservices';
import { CacheMiddleware } from '@foxframework/core/cache';
import { LoggerFactory } from '@foxframework/core/logging';
import { MetricsCollector } from '@foxframework/core/metrics';
import axios from 'axios';
 
interface ServiceConfig {
  name: string;
  url: string;
  timeout: number;
  retries: number;
  circuitBreaker: {
    errorThreshold: number;
    resetTimeout: number;
  };
}
 
export class EcommerceGateway {
  private logger = LoggerFactory.create({ service: 'gateway' });
  private metrics = new MetricsCollector();
  private circuitBreakers = new Map<string, CircuitBreaker>();
  private serviceRegistry = new ServiceRegistry();
 
  private services: ServiceConfig[] = [
    {
      name: 'user-service',
      url: process.env.USER_SERVICE_URL || 'http://user-service:3001',
      timeout: 5000,
      retries: 3,
      circuitBreaker: { errorThreshold: 5, resetTimeout: 30000 }
    },
    {
      name: 'product-service',
      url: process.env.PRODUCT_SERVICE_URL || 'http://product-service:3002',
      timeout: 5000,
      retries: 3,
      circuitBreaker: { errorThreshold: 5, resetTimeout: 30000 }
    },
    {
      name: 'order-service',
      url: process.env.ORDER_SERVICE_URL || 'http://order-service:3003',
      timeout: 5000,
      retries: 3,
      circuitBreaker: { errorThreshold: 5, resetTimeout: 30000 }
    },
    {
      name: 'payment-service',
      url: process.env.PAYMENT_SERVICE_URL || 'http://payment-service:3004',
      timeout: 5000,
      retries: 3,
      circuitBreaker: { errorThreshold: 5, resetTimeout: 30000 }
    },
    {
      name: 'notification-service',
      url: process.env.NOTIFICATION_SERVICE_URL || 'http://notification-service:3005',
      timeout: 5000,
      retries: 3,
      circuitBreaker: { errorThreshold: 5, resetTimeout: 30000 }
    },
    {
      name: 'analytics-service',
      url: process.env.ANALYTICS_SERVICE_URL || 'http://analytics-service:3006',
      timeout: 5000,
      retries: 3,
      circuitBreaker: { errorThreshold: 5, resetTimeout: 30000 }
    }
  ];
 
  constructor() {
    this.initializeCircuitBreakers();
  }
 
  private initializeCircuitBreakers(): void {
    this.services.forEach(service => {
      const breaker = new CircuitBreaker({
        timeout: service.timeout,
        errorThreshold: service.circuitBreaker.errorThreshold,
        resetTimeout: service.circuitBreaker.resetTimeout,
        monitoringPeriod: 60000
      });
 
      this.circuitBreakers.set(service.name, breaker);
    });
  }
 
  private getServiceConfig(serviceName: string): ServiceConfig | undefined {
    return this.services.find(s => s.name === serviceName);
  }
 
  private async proxyRequest(serviceName: string, req: Request, res: Response): Promise<void> {
    const startTime = Date.now();
    const serviceConfig = this.getServiceConfig(serviceName);
    
    if (!serviceConfig) {
      return res.status(404).json({
        error: 'Service not found',
        message: `Service ${serviceName} is not configured`
      });
    }
 
    const circuitBreaker = this.circuitBreakers.get(serviceName);
    if (!circuitBreaker) {
      return res.status(500).json({
        error: 'Circuit breaker not found',
        message: `Circuit breaker for ${serviceName} is not configured`
      });
    }
 
    try {
      // Log de request
      this.logger.info('Gateway request', {
        service: serviceName,
        method: req.method,
        path: req.path,
        userAgent: req.get('User-Agent'),
        ip: req.ip
      });
 
      // Construir URL del servicio
      const serviceUrl = `${serviceConfig.url}${req.originalUrl.replace(`/${serviceName}`, '')}`;
 
      // Preparar headers
      const headers = { ...req.headers };
      delete headers.host;
      headers['x-gateway-request-id'] = req.id || `gw-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
      headers['x-forwarded-for'] = req.ip;
      headers['x-service-name'] = serviceName;
 
      // Ejecutar request a través del circuit breaker
      const response = await circuitBreaker.execute(async () => {
        return await axios({
          method: req.method.toLowerCase() as any,
          url: serviceUrl,
          headers,
          data: req.body,
          params: req.query,
          timeout: serviceConfig.timeout,
          validateStatus: () => true
        });
      });
 
      // Métricas
      const duration = Date.now() - startTime;
      this.metrics.recordHttpRequest({
        method: req.method,
        route: req.route?.path || req.path,
        statusCode: response.status,
        duration,
        service: serviceName
      });
 
      // Log de response
      this.logger.info('Gateway response', {
        service: serviceName,
        status: response.status,
        duration,
        size: response.headers['content-length']
      });
 
      // Enviar respuesta
      res.status(response.status);
      
      // Copiar headers relevantes
      Object.entries(response.headers).forEach(([key, value]) => {
        if (!key.startsWith('x-') && key !== 'content-encoding' && key !== 'transfer-encoding') {
          res.set(key, value as string);
        }
      });
 
      res.send(response.data);
 
    } catch (error) {
      const duration = Date.now() - startTime;
      
      this.logger.error('Gateway error', {
        service: serviceName,
        error: error instanceof Error ? error.message : 'Unknown error',
        duration
      });
 
      this.metrics.recordHttpRequest({
        method: req.method,
        route: req.route?.path || req.path,
        statusCode: 500,
        duration,
        service: serviceName
      });
 
      if (error.name === 'CircuitBreakerOpen') {
        res.status(503).json({
          error: 'Service Unavailable',
          message: `Service ${serviceName} is temporarily unavailable`,
          retryAfter: Math.ceil(serviceConfig.circuitBreaker.resetTimeout / 1000)
        });
      } else {
        res.status(500).json({
          error: 'Gateway Error',
          message: 'An error occurred while processing your request',
          requestId: headers['x-gateway-request-id']
        });
      }
    }
  }
 
  createServer() {
    return FoxFactory.createServer({
      port: process.env.GATEWAY_PORT || 8080,
      
      middleware: [
        // Request ID generation
        (req: Request, res: Response, next: any) => {
          req.id = `req-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
          res.set('X-Request-ID', req.id);
          next();
        },
 
        // CORS
        SecurityMiddlewareCore.cors({
          origin: process.env.ALLOWED_ORIGINS?.split(',') || ['http://localhost:3000'],
          credentials: true,
          methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS'],
          allowedHeaders: ['Content-Type', 'Authorization', 'X-Requested-With']
        }),
 
        // Security headers
        SecurityMiddlewareCore.securityHeaders({
          contentSecurityPolicy: "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'",
          hsts: { maxAge: 31536000, includeSubDomains: true }
        }),
 
        // Rate limiting global
        SecurityMiddlewareCore.rateLimit({
          windowMs: 15 * 60 * 1000, // 15 minutos
          maxRequests: 1000,
          message: 'Too many requests from this IP'
        }),
 
        // Request logging
        (req: Request, res: Response, next: any) => {
          this.logger.info('Incoming request', {
            requestId: req.id,
            method: req.method,
            url: req.url,
            userAgent: req.get('User-Agent'),
            ip: req.ip,
            timestamp: new Date().toISOString()
          });
          next();
        }
      ],
      
      routes: [
        // Health check del gateway
        {
          path: '/health',
          method: 'get',
          handler: (req: Request, res: Response) => {
            const health = {
              status: 'healthy',
              timestamp: new Date().toISOString(),
              uptime: process.uptime(),
              services: Object.fromEntries(
                this.services.map(service => [
                  service.name, 
                  {
                    url: service.url,
                    circuitBreaker: this.circuitBreakers.get(service.name)?.getState() || 'unknown'
                  }
                ])
              ),
              version: process.env.APP_VERSION || '1.0.0'
            };
            res.json(health);
          }
        },
 
        // Métricas para Prometheus
        {
          path: '/metrics',
          method: 'get',
          handler: async (req: Request, res: Response) => {
            const metrics = await this.metrics.getPrometheusMetrics();
            res.set('Content-Type', 'text/plain').send(metrics);
          }
        },
 
        // **Public Routes** (sin autenticación)
        
        // Products (solo lectura pública)
        {
          path: '/api/products',
          method: 'get',
          handler: (req: Request, res: Response) => this.proxyRequest('product-service', req, res),
          middleware: [
            CacheMiddleware.cache({
              ttl: 300, // 5 minutos
              key: (req) => `products:${JSON.stringify(req.query)}`
            })
          ]
        },
        {
          path: '/api/products/:id',
          method: 'get',
          handler: (req: Request, res: Response) => this.proxyRequest('product-service', req, res),
          middleware: [
            CacheMiddleware.cache({
              ttl: 600, // 10 minutos
              key: (req) => `product:${req.params.id}`
            })
          ]
        },
 
        // User registration and authentication
        {
          path: '/api/auth/register',
          method: 'post',
          handler: (req: Request, res: Response) => this.proxyRequest('user-service', req, res),
          middleware: [
            SecurityMiddlewareCore.rateLimit({
              windowMs: 15 * 60 * 1000,
              maxRequests: 5,
              message: 'Too many registration attempts'
            })
          ]
        },
        {
          path: '/api/auth/login',
          method: 'post',
          handler: (req: Request, res: Response) => this.proxyRequest('user-service', req, res),
          middleware: [
            SecurityMiddlewareCore.rateLimit({
              windowMs: 15 * 60 * 1000,
              maxRequests: 5,
              message: 'Too many login attempts'
            })
          ]
        },
        {
          path: '/api/auth/refresh',
          method: 'post',
          handler: (req: Request, res: Response) => this.proxyRequest('user-service', req, res)
        },
 
        // **Protected Routes** (requieren autenticación)
 
        // User management
        {
          path: '/api/users/*',
          method: 'get',
          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']
            })
          ]
        },
 
        // Order management
        {
          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']
            })
          ]
        },
 
        // Payment processing
        {
          path: '/api/payments/*',
          method: 'post',
          handler: (req: Request, res: Response) => this.proxyRequest('payment-service', req, res),
          middleware: [
            AuthMiddleware.jwt({
              secret: process.env.JWT_SECRET || 'your-secret-key',
              algorithms: ['HS256']
            })
          ]
        },
 
        // **Admin Routes** (requieren role de admin)
        
        // Product management
        {
          path: '/api/admin/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']
            }),
            // TODO: Add role-based authorization middleware
          ]
        },
        {
          path: '/api/admin/products/*',
          method: 'put',
          handler: (req: Request, res: Response) => this.proxyRequest('product-service', req, res),
          middleware: [
            AuthMiddleware.jwt({
              secret: process.env.JWT_SECRET || 'your-secret-key',
              algorithms: ['HS256']
            })
          ]
        },
        {
          path: '/api/admin/products/*',
          method: 'delete',
          handler: (req: Request, res: Response) => this.proxyRequest('product-service', req, res),
          middleware: [
            AuthMiddleware.jwt({
              secret: process.env.JWT_SECRET || 'your-secret-key',
              algorithms: ['HS256']
            })
          ]
        },
 
        // Analytics and reports
        {
          path: '/api/admin/analytics/*',
          method: 'get',
          handler: (req: Request, res: Response) => this.proxyRequest('analytics-service', req, res),
          middleware: [
            AuthMiddleware.jwt({
              secret: process.env.JWT_SECRET || 'your-secret-key',
              algorithms: ['HS256']
            }),
            CacheMiddleware.cache({
              ttl: 900, // 15 minutos para analytics
              key: (req) => `analytics:${req.path}:${JSON.stringify(req.query)}`
            })
          ]
        },
 
        // Notifications
        {
          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']
            })
          ]
        }
      ]
    });
  }
}
 
// Inicializar gateway
const gateway = new EcommerceGateway();
const app = gateway.createServer();
 
app.start().then(() => {
  console.log('🦊 E-commerce Gateway running on http://localhost:8080');
  console.log('🌐 Available services:');
  console.log('  👤 User Service     : http://user-service:3001');
  console.log('  📦 Product Service  : http://product-service:3002');
  console.log('  📋 Order Service    : http://order-service:3003');
  console.log('  💳 Payment Service  : http://payment-service:3004');
  console.log('  📧 Notification Service: http://notification-service:3005');
  console.log('  📊 Analytics Service: http://analytics-service:3006');
  console.log('\n📚 Public endpoints:');
  console.log('  GET  /api/products   - Browse products');
  console.log('  POST /api/auth/login - User login');
  console.log('  POST /api/auth/register - User registration');
  console.log('\n🔒 Protected endpoints:');
  console.log('  GET  /api/orders     - User orders');
  console.log('  POST /api/orders     - Create order');
  console.log('  POST /api/payments   - Process payment');
  console.log('\n🛡️  Admin endpoints:');
  console.log('  POST /api/admin/products - Create product');
  console.log('  GET  /api/admin/analytics - View analytics');
});
 
// Graceful shutdown
process.on('SIGTERM', () => {
  console.log('🔄 Gateway shutting down gracefully...');
  process.exit(0);
});

Order Service con Event Sourcing

// services/order-service/src/server.ts
import { FoxFactory, Request, Response } from '@foxframework/core';
import { DatabaseProvider, DatabaseFactory } from '@foxframework/core/database';
import { EventStore, DistributedEventBus } from '@foxframework/core/events';
import { EmailService } from '../../../shared/services/email.service';
import { PaymentService } from '../../../shared/services/payment.service';
 
interface OrderItem {
  productId: number;
  productName: string;
  quantity: number;
  unitPrice: number;
  total: number;
}
 
interface Order {
  id: string;
  orderNumber: string;
  userId: number;
  customerEmail: string;
  items: OrderItem[];
  subtotal: number;
  tax: number;
  shipping: number;
  total: number;
  status: 'pending' | 'confirmed' | 'paid' | 'shipped' | 'delivered' | 'cancelled';
  shippingAddress: any;
  billingAddress: any;
  paymentMethod?: string;
  createdAt: Date;
  updatedAt: Date;
}
 
export class OrderService {
  private db: DatabaseProvider;
  private eventStore: EventStore;
  private eventBus: DistributedEventBus;
  private emailService: EmailService;
  private paymentService: PaymentService;
  private orders: Map<string, Order> = new Map();
 
  constructor() {
    this.initializeServices();
  }
 
  private async initializeServices(): Promise<void> {
    // Database connection
    this.db = await DatabaseFactory.create({
      provider: 'postgresql',
      connection: {
        host: process.env.DB_HOST || 'postgres',
        port: parseInt(process.env.DB_PORT || '5432'),
        database: process.env.DB_NAME || 'ecommerce_orders',
        username: process.env.DB_USER || 'fox_user',
        password: process.env.DB_PASS || 'fox_password'
      }
    });
 
    // Event store and bus
    this.eventStore = new EventStore({
      provider: 'postgresql',
      connection: this.db
    });
 
    this.eventBus = new DistributedEventBus({
      provider: 'redis',
      connection: {
        host: process.env.REDIS_HOST || 'redis',
        port: parseInt(process.env.REDIS_PORT || '6379')
      }
    });
 
    // Services
    this.emailService = new EmailService();
    this.paymentService = new PaymentService();
 
    // Setup event subscriptions
    await this.setupEventSubscriptions();
  }
 
  private async setupEventSubscriptions(): Promise<void> {
    // Listen for payment events
    await this.eventBus.subscribe('PaymentProcessed', this.handlePaymentProcessed.bind(this));
    await this.eventBus.subscribe('PaymentFailed', this.handlePaymentFailed.bind(this));
    
    // Listen for inventory events
    await this.eventBus.subscribe('InventoryReserved', this.handleInventoryReserved.bind(this));
    await this.eventBus.subscribe('InventoryReservationFailed', this.handleInventoryReservationFailed.bind(this));
  }
 
  // POST /orders
  createOrder = async (req: Request, res: Response): Promise<void> => {
    try {
      const { items, shippingAddress, billingAddress, paymentMethod } = req.body;
      const userId = req.user?.id;
      const userEmail = req.user?.email;
 
      if (!userId || !userEmail) {
        return res.status(401).json({
          error: 'Authentication required',
          message: 'User must be authenticated to create orders'
        });
      }
 
      // Validate items
      if (!items || !Array.isArray(items) || items.length === 0) {
        return res.status(400).json({
          error: 'Validation failed',
          message: 'Order must contain at least one item'
        });
      }
 
      // Generate order ID and number
      const orderId = `order-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
      const orderNumber = `ORD-${Date.now().toString().slice(-8)}`;
 
      // Fetch product details and calculate totals
      let subtotal = 0;
      const enrichedItems: OrderItem[] = [];
 
      for (const item of items) {
        // In a real application, fetch product details from product service
        const productDetails = await this.fetchProductDetails(item.productId);
        
        if (!productDetails) {
          return res.status(400).json({
            error: 'Invalid product',
            message: `Product ${item.productId} not found`
          });
        }
 
        if (productDetails.stock < item.quantity) {
          return res.status(400).json({
            error: 'Insufficient stock',
            message: `Product ${productDetails.name} has insufficient stock`
          });
        }
 
        const itemTotal = productDetails.price * item.quantity;
        subtotal += itemTotal;
 
        enrichedItems.push({
          productId: item.productId,
          productName: productDetails.name,
          quantity: item.quantity,
          unitPrice: productDetails.price,
          total: itemTotal
        });
      }
 
      // Calculate tax and shipping
      const tax = subtotal * 0.08; // 8% tax
      const shipping = subtotal > 100 ? 0 : 15; // Free shipping over $100
      const total = subtotal + tax + shipping;
 
      // Create order
      const order: Order = {
        id: orderId,
        orderNumber,
        userId,
        customerEmail: userEmail,
        items: enrichedItems,
        subtotal,
        tax,
        shipping,
        total,
        status: 'pending',
        shippingAddress,
        billingAddress: billingAddress || shippingAddress,
        paymentMethod,
        createdAt: new Date(),
        updatedAt: new Date()
      };
 
      // Store order
      this.orders.set(orderId, order);
 
      // Emit order created event
      await this.eventBus.publish({
        eventId: `evt-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
        eventType: 'OrderCreated',
        aggregateId: orderId,
        aggregateType: 'Order',
        payload: {
          orderId,
          orderNumber,
          userId,
          customerEmail: userEmail,
          items: enrichedItems,
          total,
          status: 'pending'
        },
        metadata: {
          version: 1,
          timestamp: new Date(),
          source: 'order-service'
        }
      });
 
      // Start order processing workflow
      await this.processOrderWorkflow(orderId);
 
      res.status(201).json({
        data: order,
        message: 'Order created successfully'
      });
 
    } catch (error) {
      console.error('Create order error:', error);
      res.status(500).json({
        error: 'Order creation failed',
        message: error instanceof Error ? error.message : 'Unknown error'
      });
    }
  };
 
  // GET /orders/user/:userId
  getUserOrders = async (req: Request, res: Response): Promise<void> => {
    try {
      const userId = parseInt(req.params.userId);
      const authenticatedUserId = req.user?.id;
 
      // Users can only see their own orders (unless admin)
      if (userId !== authenticatedUserId && req.user?.role !== 'admin') {
        return res.status(403).json({
          error: 'Access denied',
          message: 'You can only view your own orders'
        });
      }
 
      const userOrders = Array.from(this.orders.values())
        .filter(order => order.userId === userId)
        .sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime());
 
      res.json({
        data: userOrders,
        total: userOrders.length
      });
 
    } catch (error) {
      console.error('Get user orders error:', error);
      res.status(500).json({
        error: 'Failed to fetch orders',
        message: 'An error occurred while fetching orders'
      });
    }
  };
 
  // GET /orders/:id
  getOrder = async (req: Request, res: Response): Promise<void> => {
    try {
      const orderId = req.params.id;
      const order = this.orders.get(orderId);
 
      if (!order) {
        return res.status(404).json({
          error: 'Order not found',
          message: `Order ${orderId} not found`
        });
      }
 
      // Check access permissions
      if (order.userId !== req.user?.id && req.user?.role !== 'admin') {
        return res.status(403).json({
          error: 'Access denied',
          message: 'You can only view your own orders'
        });
      }
 
      res.json({ data: order });
 
    } catch (error) {
      console.error('Get order error:', error);
      res.status(500).json({
        error: 'Failed to fetch order',
        message: 'An error occurred while fetching the order'
      });
    }
  };
 
  // PUT /orders/:id/cancel
  cancelOrder = async (req: Request, res: Response): Promise<void> => {
    try {
      const orderId = req.params.id;
      const order = this.orders.get(orderId);
 
      if (!order) {
        return res.status(404).json({
          error: 'Order not found',
          message: `Order ${orderId} not found`
        });
      }
 
      // Check permissions
      if (order.userId !== req.user?.id && req.user?.role !== 'admin') {
        return res.status(403).json({
          error: 'Access denied',
          message: 'You can only cancel your own orders'
        });
      }
 
      // Check if order can be cancelled
      if (['shipped', 'delivered', 'cancelled'].includes(order.status)) {
        return res.status(400).json({
          error: 'Cannot cancel order',
          message: `Order cannot be cancelled in ${order.status} status`
        });
      }
 
      // Update order status
      order.status = 'cancelled';
      order.updatedAt = new Date();
 
      // Emit order cancelled event
      await this.eventBus.publish({
        eventId: `evt-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
        eventType: 'OrderCancelled',
        aggregateId: orderId,
        aggregateType: 'Order',
        payload: {
          orderId,
          orderNumber: order.orderNumber,
          userId: order.userId,
          customerEmail: order.customerEmail,
          reason: req.body.reason || 'Customer request',
          refundAmount: order.total
        },
        metadata: {
          version: 2,
          timestamp: new Date(),
          source: 'order-service',
          userId: req.user?.id
        }
      });
 
      res.json({
        data: order,
        message: 'Order cancelled successfully'
      });
 
    } catch (error) {
      console.error('Cancel order error:', error);
      res.status(500).json({
        error: 'Order cancellation failed',
        message: error instanceof Error ? error.message : 'Unknown error'
      });
    }
  };
 
  private async processOrderWorkflow(orderId: string): Promise<void> {
    const order = this.orders.get(orderId);
    if (!order) return;
 
    try {
      // Step 1: Reserve inventory
      await this.eventBus.publish({
        eventId: `evt-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
        eventType: 'InventoryReservationRequested',
        aggregateId: orderId,
        aggregateType: 'Order',
        payload: {
          orderId,
          items: order.items.map(item => ({
            productId: item.productId,
            quantity: item.quantity
          }))
        },
        metadata: {
          version: 1,
          timestamp: new Date(),
          source: 'order-service'
        }
      });
 
    } catch (error) {
      console.error('Order workflow error:', error);
      await this.handleOrderError(orderId, 'Workflow processing failed');
    }
  }
 
  private async handleInventoryReserved(event: any): Promise<void> {
    const orderId = event.aggregateId;
    const order = this.orders.get(orderId);
    
    if (order && order.status === 'pending') {
      // Inventory reserved, now process payment
      await this.processPayment(orderId);
    }
  }
 
  private async handleInventoryReservationFailed(event: any): Promise<void> {
    const orderId = event.aggregateId;
    await this.handleOrderError(orderId, 'Inventory reservation failed');
  }
 
  private async processPayment(orderId: string): Promise<void> {
    const order = this.orders.get(orderId);
    if (!order) return;
 
    try {
      // Process payment through payment service
      await this.eventBus.publish({
        eventId: `evt-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
        eventType: 'PaymentRequested',
        aggregateId: orderId,
        aggregateType: 'Order',
        payload: {
          orderId,
          amount: order.total,
          currency: 'USD',
          paymentMethod: order.paymentMethod,
          customerEmail: order.customerEmail,
          billingAddress: order.billingAddress
        },
        metadata: {
          version: 1,
          timestamp: new Date(),
          source: 'order-service'
        }
      });
 
    } catch (error) {
      console.error('Payment processing error:', error);
      await this.handleOrderError(orderId, 'Payment processing failed');
    }
  }
 
  private async handlePaymentProcessed(event: any): Promise<void> {
    const orderId = event.payload.orderId;
    const order = this.orders.get(orderId);
    
    if (order) {
      order.status = 'paid';
      order.updatedAt = new Date();
 
      // Send order confirmation email
      await this.emailService.sendOrderConfirmation(
        order.customerEmail,
        order.orderNumber,
        order
      );
 
      // Emit order confirmed event
      await this.eventBus.publish({
        eventId: `evt-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
        eventType: 'OrderConfirmed',
        aggregateId: orderId,
        aggregateType: 'Order',
        payload: {
          orderId,
          orderNumber: order.orderNumber,
          userId: order.userId,
          customerEmail: order.customerEmail,
          total: order.total,
          items: order.items
        },
        metadata: {
          version: 3,
          timestamp: new Date(),
          source: 'order-service'
        }
      });
    }
  }
 
  private async handlePaymentFailed(event: any): Promise<void> {
    const orderId = event.payload.orderId;
    await this.handleOrderError(orderId, `Payment failed: ${event.payload.reason}`);
  }
 
  private async handleOrderError(orderId: string, reason: string): Promise<void> {
    const order = this.orders.get(orderId);
    if (order) {
      order.status = 'cancelled';
      order.updatedAt = new Date();
 
      // Send error notification
      await this.emailService.sendOrderError(
        order.customerEmail,
        order.orderNumber,
        reason
      );
 
      // Emit order failed event
      await this.eventBus.publish({
        eventId: `evt-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
        eventType: 'OrderFailed',
        aggregateId: orderId,
        aggregateType: 'Order',
        payload: {
          orderId,
          orderNumber: order.orderNumber,
          userId: order.userId,
          customerEmail: order.customerEmail,
          reason
        },
        metadata: {
          version: 2,
          timestamp: new Date(),
          source: 'order-service'
        }
      });
    }
  }
 
  private async fetchProductDetails(productId: number): Promise<any> {
    // In a real application, this would call the product service
    // For demo purposes, return mock data
    const mockProducts = {
      1: { id: 1, name: 'Laptop Pro', price: 1299.99, stock: 10 },
      2: { id: 2, name: 'Wireless Mouse', price: 29.99, stock: 50 },
      3: { id: 3, name: 'USB-C Cable', price: 19.99, stock: 100 }
    };
 
    return mockProducts[productId as keyof typeof mockProducts] || null;
  }
 
  createServer() {
    return FoxFactory.createServer({
      port: process.env.ORDER_SERVICE_PORT || 3003,
      
      routes: [
        // Health check
        {
          path: '/health',
          method: 'get',
          handler: (req: Request, res: Response) => {
            res.json({
              status: 'healthy',
              service: 'order-service',
              timestamp: new Date().toISOString(),
              ordersCount: this.orders.size
            });
          }
        },
 
        // Order endpoints
        { path: '/orders', method: 'post', handler: this.createOrder },
        { path: '/orders/user/:userId', method: 'get', handler: this.getUserOrders },
        { path: '/orders/:id', method: 'get', handler: this.getOrder },
        { path: '/orders/:id/cancel', method: 'put', handler: this.cancelOrder }
      ]
    });
  }
}
 
// Initialize service
const orderService = new OrderService();
const app = orderService.createServer();
 
app.start().then(() => {
  console.log('📋 Order Service running on http://localhost:3003');
  console.log('📚 Available endpoints:');
  console.log('  POST /orders            - Create new order');
  console.log('  GET  /orders/user/:id   - Get user orders');
  console.log('  GET  /orders/:id        - Get order details');
  console.log('  PUT  /orders/:id/cancel - Cancel order');
});

Docker Compose Completo

# docker-compose.yml
version: '3.8'
 
services:
  # API Gateway
  gateway:
    build: ./gateway
    ports:
      - "8080:8080"
    environment:
      - NODE_ENV=production
      - GATEWAY_PORT=8080
      - JWT_SECRET=${JWT_SECRET}
      - USER_SERVICE_URL=http://user-service:3001
      - PRODUCT_SERVICE_URL=http://product-service:3002
      - ORDER_SERVICE_URL=http://order-service:3003
      - PAYMENT_SERVICE_URL=http://payment-service:3004
      - NOTIFICATION_SERVICE_URL=http://notification-service:3005
      - ANALYTICS_SERVICE_URL=http://analytics-service:3006
      - REDIS_HOST=redis
    depends_on:
      - redis
      - user-service
      - product-service
      - order-service
    networks:
      - ecommerce
 
  # User Service
  user-service:
    build: ./services/user-service
    environment:
      - NODE_ENV=production
      - DB_HOST=postgres
      - DB_NAME=ecommerce_users
      - DB_USER=${DB_USER}
      - DB_PASS=${DB_PASS}
      - REDIS_HOST=redis
      - JWT_SECRET=${JWT_SECRET}
      - SMTP_HOST=${SMTP_HOST}
      - SMTP_USER=${SMTP_USER}
      - SMTP_PASS=${SMTP_PASS}
    depends_on:
      - postgres
      - redis
    networks:
      - ecommerce
 
  # Product Service
  product-service:
    build: ./services/product-service
    environment:
      - NODE_ENV=production
      - DB_HOST=postgres
      - DB_NAME=ecommerce_products
      - DB_USER=${DB_USER}
      - DB_PASS=${DB_PASS}
      - REDIS_HOST=redis
    depends_on:
      - postgres
      - redis
    networks:
      - ecommerce
 
  # Order Service
  order-service:
    build: ./services/order-service
    environment:
      - NODE_ENV=production
      - DB_HOST=postgres
      - DB_NAME=ecommerce_orders
      - DB_USER=${DB_USER}
      - DB_PASS=${DB_PASS}
      - REDIS_HOST=redis
      - SMTP_HOST=${SMTP_HOST}
      - SMTP_USER=${SMTP_USER}
      - SMTP_PASS=${SMTP_PASS}
    depends_on:
      - postgres
      - redis
    networks:
      - ecommerce
 
  # Payment Service
  payment-service:
    build: ./services/payment-service
    environment:
      - NODE_ENV=production
      - DB_HOST=postgres
      - DB_NAME=ecommerce_payments
      - DB_USER=${DB_USER}
      - DB_PASS=${DB_PASS}
      - REDIS_HOST=redis
      - STRIPE_SECRET_KEY=${STRIPE_SECRET_KEY}
      - PAYPAL_CLIENT_ID=${PAYPAL_CLIENT_ID}
      - PAYPAL_SECRET=${PAYPAL_SECRET}
    depends_on:
      - postgres
      - redis
    networks:
      - ecommerce
 
  # Notification Service
  notification-service:
    build: ./services/notification-service
    environment:
      - NODE_ENV=production
      - REDIS_HOST=redis
      - SMTP_HOST=${SMTP_HOST}
      - SMTP_USER=${SMTP_USER}
      - SMTP_PASS=${SMTP_PASS}
      - TWILIO_SID=${TWILIO_SID}
      - TWILIO_TOKEN=${TWILIO_TOKEN}
      - FIREBASE_KEY=${FIREBASE_KEY}
    depends_on:
      - redis
    networks:
      - ecommerce
 
  # Analytics Service
  analytics-service:
    build: ./services/analytics-service
    environment:
      - NODE_ENV=production
      - DB_HOST=postgres
      - DB_NAME=ecommerce_analytics
      - DB_USER=${DB_USER}
      - DB_PASS=${DB_PASS}
      - REDIS_HOST=redis
    depends_on:
      - postgres
      - redis
    networks:
      - ecommerce
 
  # PostgreSQL Database
  postgres:
    image: postgres:15-alpine
    environment:
      - POSTGRES_USER=${DB_USER}
      - POSTGRES_PASSWORD=${DB_PASS}
      - POSTGRES_MULTIPLE_DATABASES=ecommerce_users,ecommerce_products,ecommerce_orders,ecommerce_payments,ecommerce_analytics
    ports:
      - "5432:5432"
    volumes:
      - postgres_data:/var/lib/postgresql/data
      - ./infrastructure/postgres/init-multiple-databases.sh:/docker-entrypoint-initdb.d/init-multiple-databases.sh
    networks:
      - ecommerce
 
  # Redis Cache & Message Broker
  redis:
    image: redis:7-alpine
    ports:
      - "6379:6379"
    volumes:
      - redis_data:/data
    command: redis-server --appendonly yes
    networks:
      - ecommerce
 
  # Nginx Load Balancer
  nginx:
    image: nginx:alpine
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./infrastructure/nginx/nginx.conf:/etc/nginx/nginx.conf
      - ./infrastructure/nginx/ssl:/etc/ssl/certs
    depends_on:
      - gateway
    networks:
      - ecommerce
 
  # Monitoring Stack
  prometheus:
    image: prom/prometheus
    ports:
      - "9090:9090"
    volumes:
      - ./infrastructure/monitoring/prometheus.yml:/etc/prometheus/prometheus.yml
      - prometheus_data:/prometheus
    networks:
      - ecommerce
 
  grafana:
    image: grafana/grafana
    ports:
      - "3000:3000"
    environment:
      - GF_SECURITY_ADMIN_PASSWORD=${GRAFANA_PASSWORD}
    volumes:
      - grafana_data:/var/lib/grafana
    networks:
      - ecommerce
 
  # Elasticsearch for logging
  elasticsearch:
    image: docker.elastic.co/elasticsearch/elasticsearch:8.9.0
    environment:
      - discovery.type=single-node
      - "ES_JAVA_OPTS=-Xms512m -Xmx512m"
      - xpack.security.enabled=false
    ports:
      - "9200:9200"
    volumes:
      - elasticsearch_data:/usr/share/elasticsearch/data
    networks:
      - ecommerce
 
  kibana:
    image: docker.elastic.co/kibana/kibana:8.9.0
    ports:
      - "5601:5601"
    environment:
      - ELASTICSEARCH_HOSTS=http://elasticsearch:9200
    depends_on:
      - elasticsearch
    networks:
      - ecommerce
 
networks:
  ecommerce:
    driver: bridge
 
volumes:
  postgres_data:
  redis_data:
  prometheus_data:
  grafana_data:
  elasticsearch_data:

Frontend Customer App (React)

// frontend/customer-app/src/services/api.service.ts
export class EcommerceApiService {
  private baseUrl: string;
  private token?: string;
 
  constructor(baseUrl: string = 'http://localhost:8080/api') {
    this.baseUrl = baseUrl;
    this.loadTokenFromStorage();
  }
 
  private loadTokenFromStorage(): void {
    this.token = localStorage.getItem('accessToken') || undefined;
  }
 
  private async request<T>(endpoint: string, options: RequestInit = {}): Promise<T> {
    const url = `${this.baseUrl}${endpoint}`;
    const headers: HeadersInit = {
      'Content-Type': 'application/json',
      ...options.headers,
    };
 
    if (this.token) {
      headers.Authorization = `Bearer ${this.token}`;
    }
 
    const response = await fetch(url, {
      ...options,
      headers,
    });
 
    if (!response.ok) {
      const error = await response.json();
      throw new Error(error.message || 'API request failed');
    }
 
    return response.json();
  }
 
  // Authentication
  async login(email: string, password: string): Promise<any> {
    const result = await this.request<any>('/auth/login', {
      method: 'POST',
      body: JSON.stringify({ email, password }),
    });
 
    if (result.data?.accessToken) {
      this.token = result.data.accessToken;
      localStorage.setItem('accessToken', this.token);
    }
 
    return result;
  }
 
  async register(userData: {
    email: string;
    password: string;
    firstName: string;
    lastName: string;
  }): Promise<any> {
    return this.request<any>('/auth/register', {
      method: 'POST',
      body: JSON.stringify(userData),
    });
  }
 
  // Products
  async getProducts(filters?: any): Promise<any> {
    const params = new URLSearchParams(filters);
    return this.request<any>(`/products?${params}`);
  }
 
  async getProduct(id: number): Promise<any> {
    return this.request<any>(`/products/${id}`);
  }
 
  // Orders
  async createOrder(orderData: any): Promise<any> {
    return this.request<any>('/orders', {
      method: 'POST',
      body: JSON.stringify(orderData),
    });
  }
 
  async getMyOrders(): Promise<any> {
    return this.request<any>('/orders/user/me');
  }
 
  async getOrder(orderId: string): Promise<any> {
    return this.request<any>(`/orders/${orderId}`);
  }
 
  async cancelOrder(orderId: string, reason?: string): Promise<any> {
    return this.request<any>(`/orders/${orderId}/cancel`, {
      method: 'PUT',
      body: JSON.stringify({ reason }),
    });
  }
 
  // Payments
  async processPayment(paymentData: any): Promise<any> {
    return this.request<any>('/payments', {
      method: 'POST',
      body: JSON.stringify(paymentData),
    });
  }
}
 
// Usage in React component
import React, { useState, useEffect } from 'react';
import { EcommerceApiService } from '../services/api.service';
 
const ProductList: React.FC = () => {
  const [products, setProducts] = useState([]);
  const [loading, setLoading] = useState(true);
  const apiService = new EcommerceApiService();
 
  useEffect(() => {
    loadProducts();
  }, []);
 
  const loadProducts = async () => {
    try {
      const response = await apiService.getProducts();
      setProducts(response.data);
    } catch (error) {
      console.error('Failed to load products:', error);
    } finally {
      setLoading(false);
    }
  };
 
  const handleAddToCart = async (productId: number) => {
    try {
      // Add to cart logic here
      console.log(`Added product ${productId} to cart`);
    } catch (error) {
      console.error('Failed to add to cart:', error);
    }
  };
 
  if (loading) return <div>Loading products...</div>;
 
  return (
    <div className="product-grid">
      {products.map((product: any) => (
        <div key={product.id} className="product-card">
          <img src={product.imageUrl} alt={product.name} />
          <h3>{product.name}</h3>
          <p>{product.description}</p>
          <div className="price">${product.price}</div>
          <button onClick={() => handleAddToCart(product.id)}>
            Add to Cart
          </button>
        </div>
      ))}
    </div>
  );
};

Scripts de Deployment

#!/bin/bash
# scripts/deploy-full-stack.sh
 
set -euo pipefail
 
echo "🚀 Deploying Fox E-commerce Platform"
 
# Variables
ENVIRONMENT=${1:-development}
VERSION=${2:-latest}
 
echo "📋 Environment: $ENVIRONMENT"
echo "📋 Version: $VERSION"
 
# Build all services
echo "🔨 Building all services..."
docker-compose build
 
# Start infrastructure first
echo "🏗️  Starting infrastructure..."
docker-compose up -d postgres redis elasticsearch
 
# Wait for databases to be ready
echo "⏳ Waiting for databases..."
sleep 30
 
# Start services
echo "🚀 Starting microservices..."
docker-compose up -d user-service product-service order-service payment-service notification-service analytics-service
 
# Start gateway
echo "🌐 Starting API Gateway..."
docker-compose up -d gateway
 
# Start frontend services
echo "🎨 Starting frontend..."
docker-compose up -d nginx
 
# Start monitoring
echo "📊 Starting monitoring stack..."
docker-compose up -d prometheus grafana kibana
 
# Health check
echo "🏥 Performing health checks..."
sleep 60
 
services=("gateway:8080" "user-service:3001" "product-service:3002" "order-service:3003")
for service in "${services[@]}"; do
  IFS=':' read -r name port <<< "$service"
  if curl -f "http://localhost:$port/health" > /dev/null 2>&1; then
    echo "✅ $name is healthy"
  else
    echo "❌ $name health check failed"
  fi
done
 
echo "🎉 Deployment completed!"
echo ""
echo "📋 Available services:"
echo "  🌐 API Gateway: http://localhost:8080"
echo "  🎨 Frontend: http://localhost:80"
echo "  📊 Grafana: http://localhost:3000"
echo "  🔍 Kibana: http://localhost:5601"
echo "  📈 Prometheus: http://localhost:9090"
echo ""
echo "📚 API Documentation: http://localhost:8080/docs"
echo "🏥 Health Status: http://localhost:8080/health"

Variables de Entorno

# .env
# Database
DB_USER=fox_user
DB_PASS=super_secure_password
 
# JWT
JWT_SECRET=super-secure-jwt-secret-key-at-least-32-characters
JWT_REFRESH_SECRET=another-super-secure-refresh-key
 
# SMTP (SendGrid example)
SMTP_HOST=smtp.sendgrid.net
SMTP_PORT=587
SMTP_USER=apikey
SMTP_PASS=your-sendgrid-api-key
 
# Payment Providers
STRIPE_SECRET_KEY=sk_test_your_stripe_secret_key
PAYPAL_CLIENT_ID=your_paypal_client_id
PAYPAL_SECRET=your_paypal_secret
 
# SMS (Twilio)
TWILIO_SID=your_twilio_account_sid
TWILIO_TOKEN=your_twilio_auth_token
 
# Push Notifications (Firebase)
FIREBASE_KEY=your_firebase_server_key
 
# Monitoring
GRAFANA_PASSWORD=admin123
SENTRY_DSN=https://your-sentry-dsn
 
# Frontend
FRONTEND_URL=http://localhost:3000
ADMIN_URL=http://localhost:3001
 
# API Rate Limiting
RATE_LIMIT_WINDOW_MS=900000
RATE_LIMIT_MAX_REQUESTS=100

Características Destacadas de la Aplicación Completa

  • 🏗️ Arquitectura de Microservicios: Servicios independientes y escalables
  • 🔐 Autenticación JWT Completa: Login, registro, refresh tokens
  • 📊 Event Sourcing & CQRS: Historial completo de eventos y separación de lecturas/escrituras
  • 💳 Procesamiento de Pagos: Integración con Stripe y PayPal
  • 📧 Sistema de Notificaciones: Emails transaccionales, SMS y push notifications
  • 🛒 Flujo de E-commerce Completo: Productos, carrito, checkout, pagos
  • 📈 Analytics & Monitoring: Métricas en tiempo real con Prometheus y Grafana
  • 🔍 Logging Centralizado: ELK Stack para análisis de logs
  • 🐳 Containerización Completa: Docker Compose para todo el stack
  • 🚀 Auto-scaling: Configuración lista para Kubernetes
  • 🛡️ Seguridad Integral: Rate limiting, CORS, security headers
  • 💾 Persistencia Multi-Provider: PostgreSQL, Redis, Elasticsearch
  • 🔄 Circuit Breakers: Resistencia a fallos entre servicios
  • 📱 Frontend Moderno: React con TypeScript y API client
  • 👨‍💼 Admin Dashboard: Panel de administración completo

Esta aplicación completa demuestra cómo Fox Framework puede manejar aplicaciones empresariales complejas con todas las características modernas esperadas en un sistema de production-ready.