TechLead
Leccion 23 de 30
6 min de lectura
Diseño de Sistemas

Diseño de Sistemas: Plataforma E-Commerce

Diseña una plataforma de comercio electrónico escalable cubriendo catálogo de productos, arquitectura de carrito, gestión de inventario, procesamiento de pedidos y escalado para tráfico pico.

Diseñando una Plataforma de E-Commerce

Las plataformas de comercio electrónico como Amazon, Shopify y eBay deben manejar catálogos masivos de productos, sesiones de compra concurrentes, rastreo de inventario en tiempo real y enormes picos de tráfico durante eventos como Black Friday. El diseño del sistema debe priorizar la disponibilidad, la consistencia de datos para inventario y pedidos, y la búsqueda de baja latencia.

Requisitos de Escala

  • 100 millones+ de productos en el catálogo
  • 50 millones de usuarios activos diarios
  • 10,000+ pedidos por segundo durante pico
  • 99.99% de disponibilidad para navegación y búsqueda
  • Consistencia fuerte para inventario y pagos
  • Carga de página sub-200ms para páginas de productos

Diseño del Catálogo de Productos

El catálogo de productos es la base de cualquier sistema de comercio electrónico. Debe soportar tipos de productos diversos con atributos variados, búsqueda rápida y navegación eficiente a través de categorías.

// Flexible product schema supporting varied attributes
interface Product {
  id: string;
  sellerId: string;
  title: string;
  slug: string;
  description: string;
  category: CategoryPath; // e.g., ["Electronics", "Phones", "Smartphones"]
  brand: string;
  basePrice: number;
  currency: string;
  images: ProductImage[];
  variants: ProductVariant[];
  attributes: Record<string, string | number | boolean>;
  status: "DRAFT" | "ACTIVE" | "ARCHIVED";
  createdAt: Date;
  updatedAt: Date;
}

interface ProductVariant {
  sku: string;
  attributes: Record<string, string>; // e.g., { color: "Red", size: "M" }
  price: number;
  inventoryCount: number;
  weight: number;
  dimensions: { length: number; width: number; height: number };
}

type CategoryPath = string[];
// Stored as: "Electronics > Phones > Smartphones"
// Enables efficient queries: find all products under "Electronics"

Para almacenamiento, usa PostgreSQL para los datos relacionales de productos y Elasticsearch para búsqueda de texto completo y filtrado por facetas. Mantenlos sincronizados con un pipeline CDC (Change Data Capture) o actualizaciones basadas en eventos.

Arquitectura del Carrito de Compras

El carrito de compras debe ser rápido, resistente y manejar acceso concurrente. Hay dos enfoques principales: carritos del lado del servidor y carritos del lado del cliente.

Enfoque Pros Contras
Del lado del servidor (Redis/BD) Persiste entre dispositivos, soporta analítica, permite recuperación de carrito abandonado Mayor carga del servidor, latencia en cada operación
Del lado del cliente (localStorage) Cero carga del servidor, operaciones instantáneas, funciona offline Se pierde al limpiar el navegador, sin sincronización entre dispositivos
Híbrido Lo mejor de ambos mundos, local para velocidad, servidor para persistencia Complejidad de lógica de sincronización, resolución de conflictos necesaria
interface CartItem {
  productId: string;
  variantSku: string;
  quantity: number;
  priceAtAdd: number;
  addedAt: Date;
}

interface ShoppingCart {
  userId: string;
  items: CartItem[];
  updatedAt: Date;
  expiresAt: Date;
}

class CartService {
  private redis: RedisClient;

  async addItem(userId: string, item: CartItem): Promise<ShoppingCart> {
    const cartKey = `cart:${userId}`;

    const available = await inventoryService.checkAvailability(
      item.variantSku,
      item.quantity
    );
    if (!available) throw new Error("Item out of stock");

    const cart = await this.getCart(userId);

    const existingIndex = cart.items.findIndex(
      (i) => i.variantSku === item.variantSku
    );
    if (existingIndex >= 0) {
      cart.items[existingIndex].quantity += item.quantity;
    } else {
      cart.items.push(item);
    }

    cart.updatedAt = new Date();
    cart.expiresAt = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000);

    await this.redis.setex(cartKey, 7 * 86400, JSON.stringify(cart));
    return cart;
  }
}

Gestión de Inventario

La gestión de inventario es uno de los aspectos más desafiantes del comercio electrónico. El sistema debe prevenir la sobreventa mientras maximiza la disponibilidad. Esto requiere control de concurrencia cuidadoso.

El Problema de la Sobreventa

Si 100 usuarios intentan comprar el último artículo simultáneamente, implementaciones ingenuas podrían dejar que los 100 tengan éxito. Debes usar operaciones atómicas o bloqueo pesimista para prevenir esto. El enfoque más común es reservar inventario al momento del checkout y liberarlo si el pedido no se completa dentro de un timeout.

class InventoryService {
  async reserveInventory(
    items: { sku: string; quantity: number }[],
    orderId: string,
    ttlMinutes: number = 15
  ): Promise<boolean> {
    const client = await db.pool.connect();
    try {
      await client.query("BEGIN");

      for (const item of items) {
        const result = await client.query(
          `UPDATE inventory
           SET available_count = available_count - $1,
               reserved_count = reserved_count + $1
           WHERE sku = $2
             AND available_count >= $1
           RETURNING available_count`,
          [item.quantity, item.sku]
        );

        if (result.rowCount === 0) {
          await client.query("ROLLBACK");
          await this.releaseReservation(orderId);
          return false;
        }

        await client.query(
          `INSERT INTO inventory_reservations (order_id, sku, quantity, expires_at)
           VALUES ($1, $2, $3, NOW() + INTERVAL '$4 minutes')`,
          [orderId, item.sku, item.quantity, ttlMinutes]
        );
      }

      await client.query("COMMIT");
      return true;
    } catch (error) {
      await client.query("ROLLBACK");
      throw error;
    } finally {
      client.release();
    }
  }

  async releaseExpiredReservations(): Promise<void> {
    const expired = await db.query(
      `SELECT * FROM inventory_reservations WHERE expires_at < NOW()`
    );

    for (const reservation of expired.rows) {
      await db.query(
        `UPDATE inventory
         SET available_count = available_count + $1,
             reserved_count = reserved_count - $1
         WHERE sku = $2`,
        [reservation.quantity, reservation.sku]
      );
      await db.query(
        `DELETE FROM inventory_reservations WHERE id = $1`,
        [reservation.id]
      );
    }
  }
}

Pipeline de Procesamiento de Pedidos

El procesamiento de pedidos es un pipeline asíncrono de múltiples pasos. Usar un enfoque dirigido por eventos asegura fiabilidad y permite que cada paso se reintente independientemente.

enum OrderStatus {
  CREATED = "CREATED",
  PAYMENT_PENDING = "PAYMENT_PENDING",
  PAYMENT_CONFIRMED = "PAYMENT_CONFIRMED",
  PREPARING = "PREPARING",
  SHIPPED = "SHIPPED",
  DELIVERED = "DELIVERED",
  CANCELLED = "CANCELLED",
  REFUNDED = "REFUNDED",
}

async function handleOrderCreated(order: Order): Promise<void> {
  // 1. Validate order
  await validateOrder(order);

  // 2. Reserve inventory
  const reserved = await inventoryService.reserveInventory(
    order.items.map((i) => ({ sku: i.sku, quantity: i.quantity })),
    order.id
  );
  if (!reserved) {
    await updateOrderStatus(order.id, OrderStatus.CANCELLED, "Out of stock");
    await notifyCustomer(order.customerId, "order_cancelled_stock");
    return;
  }

  // 3. Process payment
  await updateOrderStatus(order.id, OrderStatus.PAYMENT_PENDING);
  const paymentResult = await paymentService.charge(order);

  if (!paymentResult.success) {
    await inventoryService.releaseReservation(order.id);
    await updateOrderStatus(order.id, OrderStatus.CANCELLED, "Payment failed");
    return;
  }

  // 4. Confirm order
  await updateOrderStatus(order.id, OrderStatus.PAYMENT_CONFIRMED);
  await inventoryService.confirmReservation(order.id);

  // 5. Send to fulfillment
  await fulfillmentService.createShipment(order);
  await notifyCustomer(order.customerId, "order_confirmed");
}

Búsqueda y Recomendaciones

La búsqueda de productos y las recomendaciones son críticas para la conversión. La búsqueda debe soportar consultas de texto completo, filtrado por facetas (marca, rango de precio, calificaciones), autocompletado y tolerancia a errores tipográficos.

  • Motor de búsqueda: Elasticsearch u OpenSearch para búsqueda de texto completo con puntuación de relevancia
  • Autocompletado: Usar tokenizadores edge n-gram para coincidencia de prefijos mientras el usuario escribe
  • Búsqueda por facetas: Consultas de agregación en Elasticsearch para filtros como rangos de precio y categorías
  • Personalización: Reordenar resultados de búsqueda basándose en historial y preferencias del usuario
  • Recomendaciones: Filtrado colaborativo ("clientes que compraron X también compraron Y") y filtrado basado en contenido (atributos similares de producto)

Escalado para Alto Tráfico (Black Friday)

Los eventos pico de compras pueden ver 10-100x el tráfico normal. El sistema debe estar preparado con anticipación.

Estrategia Implementación
CDN para assets estáticos Cachear imágenes de productos, CSS, JS en CloudFront/Cloudflare edge
Réplicas de lectura Escalar consultas de catálogo de lectura intensiva entre múltiples réplicas de BD
Procesamiento de pedidos basado en colas Almacenar pedidos en buffer en una cola para que el backend procese a un ritmo sostenible
Auto-escalado Pre-calentar instancias antes del evento, auto-escalar basándose en CPU y conteo de solicitudes
Circuit breakers Degradar funciones no esenciales (recomendaciones, reseñas) bajo carga extrema
Rate limiting Proteger contra bots y abuso con límites de tasa por usuario

Resumen de Arquitectura

Una plataforma de e-commerce bien diseñada separa responsabilidades en servicios independientes (catálogo, carrito, inventario, pedidos, pagos, búsqueda), usa caché agresivamente para cargas de lectura intensiva, asegura consistencia fuerte solo donde se requiere (inventario y pagos), y se degrada elegantemente bajo carga extrema. La idea clave es que la navegación y búsqueda pueden tolerar consistencia eventual, mientras que las operaciones de inventario y pago deben ser fuertemente consistentes.

Continuar Aprendiendo