Introduccion a la Arquitectura de Microservicios - Principios de Diseno y Patrones de Implementacion

20 min de lectura | 2025.12.02

La arquitectura de microservicios es un enfoque de diseno que divide aplicaciones grandes en servicios pequenos e independientes. Empresas como Netflix, Amazon y Uber la han adoptado, convirtiendose en la corriente principal del desarrollo cloud-native moderno. Este articulo explica sistematicamente desde los conceptos basicos hasta los patrones de implementacion de microservicios.

Comparacion entre Monolito y Microservicios

Arquitectura Monolitica

flowchart TB
    subgraph Mono["Arquitectura Monolitica - Una sola aplicacion"]
        subgraph Modules["Modulos de funcionalidad"]
            M1["Gestion de Usuarios"]
            M2["Gestion de Productos"]
            M3["Gestion de Pedidos"]
            M4["Procesamiento de Pagos"]
        end
        DB["Base de Datos Compartida"]
    end

    Modules --> DB
    Note["Una sola unidad de despliegue"]

Arquitectura de Microservicios

flowchart TB
    Gateway["API Gateway / Service Mesh"]

    subgraph UserSvc["Servicio de Usuarios"]
        US["Servicio de<br/>Usuarios"]
        UDB["Users DB"]
        US --> UDB
    end

    subgraph ProductSvc["Servicio de Productos"]
        PS["Servicio de<br/>Productos"]
        PDB["Products DB"]
        PS --> PDB
    end

    subgraph OrderSvc["Servicio de Pedidos"]
        OS["Servicio de<br/>Pedidos"]
        ODB["Orders DB"]
        OS --> ODB
    end

    subgraph PaymentSvc["Servicio de Pagos"]
        PaS["Servicio de<br/>Pagos"]
        PaDB["Payments DB"]
        PaS --> PaDB
    end

    Gateway --> UserSvc
    Gateway --> ProductSvc
    Gateway --> OrderSvc
    Gateway --> PaymentSvc

Tabla Comparativa

AspectoMonolitoMicroservicios
DespliegueTodo juntoIndependiente por servicio
EscaladoTodo el sistemaSolo los servicios necesarios
Stack tecnologicoDebe ser uniformeEleccion por servicio
Impacto de fallosSe propaga a todoLimitado al servicio afectado
Equipo de desarrolloTodos conocen todoEquipos dedicados por servicio
ComplejidadConcentrada en el codigoDistribuida en infraestructura/operaciones

Principios de Diseno de Microservicios

1. Principio de Responsabilidad Unica (Single Responsibility)

Cada servicio se enfoca en una sola funcion de negocio.

// Mal ejemplo: un servicio con multiples responsabilidades
class UserOrderService {
  createUser() { /* ... */ }
  updateUser() { /* ... */ }
  createOrder() { /* ... */ }      // Dominio diferente
  processPayment() { /* ... */ }   // Dominio diferente
}

// Buen ejemplo: separar servicios por responsabilidad
// user-service
class UserService {
  createUser() { /* ... */ }
  updateUser() { /* ... */ }
  getUserById() { /* ... */ }
}

// order-service
class OrderService {
  createOrder() { /* ... */ }
  getOrdersByUser() { /* ... */ }
}

2. Independencia de Datos (Database per Service)

Cada servicio tiene su propio almacen de datos y no accede directamente a la base de datos de otros servicios.

flowchart TB
    subgraph Anti["Anti-patron"]
        A1["Service A"]
        B1["Service B"]
        SharedDB["DB Compartida"]
        A1 -->|Acceso directo| SharedDB
        B1 -->|Acceso directo| SharedDB
    end

    subgraph Good["Patron Recomendado"]
        A2["Service A"]
        B2["Service B"]
        DBA["DB A"]
        DBB["DB B"]
        A2 --> DBA
        B2 --> DBB
        A2 <-->|Via API| B2
    end

    style Anti fill:#fee,stroke:#f00
    style Good fill:#efe,stroke:#0f0

3. Bajo Acoplamiento (Loose Coupling)

Minimizar las dependencias entre servicios, comunicandose solo a traves de interfaces.

// Ejemplo de order-service comunicandose con user-service
interface UserClient {
  getUserById(userId: string): Promise<User>;
}

class OrderService {
  constructor(private userClient: UserClient) {}

  async createOrder(userId: string, items: OrderItem[]): Promise<Order> {
    // Comunicacion con otro servicio a traves de interfaz
    const user = await this.userClient.getUserById(userId);

    if (!user.isActive) {
      throw new Error('User is not active');
    }

    return this.orderRepository.create({
      userId,
      items,
      createdAt: new Date(),
    });
  }
}

4. Alta Cohesion (High Cohesion)

Las funcionalidades relacionadas se agrupan en el mismo servicio.

flowchart TB
    subgraph ECommerce["Dominio E-Commerce"]
        subgraph UserCtx["Contexto de Usuario"]
            U1["User"]
            U2["Profile"]
            U3["Address"]
            U4["Authentication"]
        end

        subgraph OrderCtx["Contexto de Pedidos"]
            O1["Order"]
            O2["OrderItem"]
            O3["Payment"]
            O4["Shipping"]
        end

        subgraph InvCtx["Contexto de Inventario"]
            I1["Product"]
            I2["Stock"]
            I3["Warehouse"]
            I4["Supplier"]
        end
    end

Patrones de Comunicacion entre Servicios

1. Comunicacion Sincrona (REST / gRPC)

Se usa cuando se necesita respuesta inmediata.

// Comunicacion sincrona con REST API
class ProductClient {
  private baseUrl = 'http://product-service:8080';

  async getProduct(productId: string): Promise<Product> {
    const response = await fetch(`${this.baseUrl}/products/${productId}`, {
      headers: {
        'Content-Type': 'application/json',
        'X-Request-ID': generateRequestId(),  // Para tracing
      },
      signal: AbortSignal.timeout(5000),  // Configuracion de timeout
    });

    if (!response.ok) {
      throw new ProductServiceError(response.status);
    }

    return response.json();
  }
}
// Comunicacion sincrona con gRPC (protocol buffers)
syntax = "proto3";

service ProductService {
  rpc GetProduct(GetProductRequest) returns (Product);
  rpc ListProducts(ListProductsRequest) returns (stream Product);
}

message GetProductRequest {
  string product_id = 1;
}

message Product {
  string id = 1;
  string name = 2;
  int32 price = 3;
  int32 stock = 4;
}

2. Comunicacion Asincrona (Colas de Mensajes)

Se usa cuando se tolera consistencia eventual o se necesita desacoplar el procesamiento.

// Arquitectura orientada a eventos
interface OrderCreatedEvent {
  eventType: 'ORDER_CREATED';
  orderId: string;
  userId: string;
  items: OrderItem[];
  totalAmount: number;
  timestamp: Date;
}

// order-service: Publicar evento
class OrderService {
  async createOrder(order: CreateOrderDto): Promise<Order> {
    const created = await this.orderRepository.create(order);

    // Publicar evento (otros servicios suscritos)
    await this.eventBus.publish<OrderCreatedEvent>({
      eventType: 'ORDER_CREATED',
      orderId: created.id,
      userId: created.userId,
      items: created.items,
      totalAmount: created.totalAmount,
      timestamp: new Date(),
    });

    return created;
  }
}

// inventory-service: Suscribirse al evento
class InventoryEventHandler {
  @Subscribe('ORDER_CREATED')
  async handleOrderCreated(event: OrderCreatedEvent): Promise<void> {
    for (const item of event.items) {
      await this.inventoryService.decrementStock(item.productId, item.quantity);
    }
  }
}

Criterios de Seleccion de Patrones de Comunicacion

PatronCaso de usoCaracteristicas
RESTOperaciones CRUD, APIs simplesAmpliamente adoptado, facil de depurar
gRPCAlto rendimiento, tipado fuerteRapido, soporte de streaming
Colas de MensajesProcesamiento asincrono, event-drivenBajo acoplamiento, escalabilidad
GraphQLObtencion de datos dirigida por clienteConsultas flexibles, evita over-fetching

Patron API Gateway

Proporciona un punto de entrada unico entre clientes y microservicios.

flowchart TB
    Client["Client"] --> Gateway

    subgraph Gateway["API Gateway"]
        G1["Autenticacion/Autorizacion"]
        G2["Rate Limiting"]
        G3["Enrutamiento de Requests"]
        G4["Agregacion de Respuestas"]
        G5["Conversion de Protocolos"]
    end

    Gateway --> US["User Service"]
    Gateway --> OS["Order Service"]
    Gateway --> PS["Product Service"]
// Ejemplo de configuracion de enrutamiento en API Gateway (Express + http-proxy-middleware)
import express from 'express';
import { createProxyMiddleware } from 'http-proxy-middleware';

const app = express();

// Middleware de autenticacion
app.use(authMiddleware);

// Enrutamiento por servicio
app.use('/api/users', createProxyMiddleware({
  target: 'http://user-service:8080',
  changeOrigin: true,
  pathRewrite: { '^/api/users': '' },
}));

app.use('/api/orders', createProxyMiddleware({
  target: 'http://order-service:8080',
  changeOrigin: true,
  pathRewrite: { '^/api/orders': '' },
}));

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

Patrones de Resiliencia

1. Circuit Breaker

Corta temporalmente las llamadas a servicios con fallos.

enum CircuitState {
  CLOSED,     // Operacion normal
  OPEN,       // Cortado
  HALF_OPEN,  // Verificando recuperacion
}

class CircuitBreaker {
  private state = CircuitState.CLOSED;
  private failureCount = 0;
  private lastFailureTime: Date | null = null;

  private readonly failureThreshold = 5;
  private readonly resetTimeout = 30000; // 30 segundos

  async call<T>(fn: () => Promise<T>): Promise<T> {
    if (this.state === CircuitState.OPEN) {
      if (this.shouldAttemptReset()) {
        this.state = CircuitState.HALF_OPEN;
      } else {
        throw new CircuitBreakerOpenError();
      }
    }

    try {
      const result = await fn();
      this.onSuccess();
      return result;
    } catch (error) {
      this.onFailure();
      throw error;
    }
  }

  private onSuccess(): void {
    this.failureCount = 0;
    this.state = CircuitState.CLOSED;
  }

  private onFailure(): void {
    this.failureCount++;
    this.lastFailureTime = new Date();

    if (this.failureCount >= this.failureThreshold) {
      this.state = CircuitState.OPEN;
    }
  }

  private shouldAttemptReset(): boolean {
    return Date.now() - (this.lastFailureTime?.getTime() ?? 0) > this.resetTimeout;
  }
}

2. Patron de Reintento

Reintentar con backoff exponencial ante fallos temporales.

async function withRetry<T>(
  fn: () => Promise<T>,
  options: {
    maxRetries: number;
    baseDelay: number;
    maxDelay: number;
  }
): Promise<T> {
  let lastError: Error;

  for (let attempt = 0; attempt <= options.maxRetries; attempt++) {
    try {
      return await fn();
    } catch (error) {
      lastError = error as Error;

      if (attempt === options.maxRetries) break;

      // Backoff exponencial + jitter
      const delay = Math.min(
        options.baseDelay * Math.pow(2, attempt) + Math.random() * 1000,
        options.maxDelay
      );

      await new Promise(resolve => setTimeout(resolve, delay));
    }
  }

  throw lastError!;
}

// Ejemplo de uso
const user = await withRetry(
  () => userClient.getUserById(userId),
  { maxRetries: 3, baseDelay: 1000, maxDelay: 10000 }
);

3. Patron Bulkhead

Aislar recursos para limitar el alcance del impacto de fallos.

// Separacion de pools de threads/conexiones
const pools = {
  userService: new ConnectionPool({ maxConnections: 10 }),
  orderService: new ConnectionPool({ maxConnections: 20 }),
  paymentService: new ConnectionPool({ maxConnections: 5 }),
};

// Aunque un servicio este sobrecargado, prevenir impacto en otros servicios

Transacciones Distribuidas

Patron Saga

Implementar transacciones que abarcan multiples servicios como una serie de transacciones locales.

sequenceDiagram
    participant OS as Order Service
    participant IS as Inventory Service
    participant PS as Payment Service

    Note over OS,PS: Flujo Normal
    OS->>IS: 1. Create Order
    IS->>PS: 2. Reserve Stock
    PS->>PS: 3. Process Payment
    PS-->>IS: Success
    IS-->>OS: 4. All Success

    Note over OS,PS: Flujo de Compensacion (fallo en pago)
    PS--xPS: Payment Failed
    PS-->>IS: Rollback Stock
    IS-->>OS: Cancel Order
// Orquestador Saga
class OrderSaga {
  async execute(orderData: CreateOrderData): Promise<Order> {
    const sagaLog: SagaStep[] = [];

    try {
      // Step 1: Crear pedido
      const order = await this.orderService.create(orderData);
      sagaLog.push({ service: 'order', action: 'create', data: order });

      // Step 2: Reservar inventario
      await this.inventoryService.reserve(order.items);
      sagaLog.push({ service: 'inventory', action: 'reserve', data: order.items });

      // Step 3: Procesar pago
      await this.paymentService.process(order.id, order.totalAmount);
      sagaLog.push({ service: 'payment', action: 'process', data: order.id });

      // Step 4: Confirmar pedido
      await this.orderService.confirm(order.id);

      return order;

    } catch (error) {
      // Transaccion de compensacion (ejecutar en orden inverso)
      await this.compensate(sagaLog);
      throw error;
    }
  }

  private async compensate(sagaLog: SagaStep[]): Promise<void> {
    for (const step of sagaLog.reverse()) {
      switch (step.service) {
        case 'inventory':
          await this.inventoryService.release(step.data);
          break;
        case 'order':
          await this.orderService.cancel(step.data.id);
          break;
      }
    }
  }
}

Observabilidad

Los 3 Pilares

flowchart TB
    subgraph Observability["Observabilidad - Los 3 Pilares"]
        subgraph Logs["Logs"]
            L1["Registro de eventos<br/>de la aplicacion"]
            L2["ELK Stack / Loki"]
        end

        subgraph Metrics["Metricas"]
            M1["Estado del sistema<br/>expresado numericamente"]
            M2["Prometheus / Grafana"]
        end

        subgraph Traces["Trazas"]
            T1["Seguimiento del flujo<br/>de requests"]
            T2["Jaeger / Zipkin"]
        end
    end

Tracing Distribuido

// Tracing distribuido con OpenTelemetry
import { trace, context, SpanKind } from '@opentelemetry/api';

const tracer = trace.getTracer('order-service');

async function createOrder(req: Request): Promise<Order> {
  return tracer.startActiveSpan(
    'createOrder',
    { kind: SpanKind.SERVER },
    async (span) => {
      try {
        span.setAttribute('user.id', req.userId);

        // Span hijo: validacion de usuario
        const user = await tracer.startActiveSpan('validateUser', async (childSpan) => {
          const result = await userClient.getUser(req.userId);
          childSpan.end();
          return result;
        });

        // Span hijo: guardar pedido
        const order = await tracer.startActiveSpan('saveOrder', async (childSpan) => {
          const result = await orderRepository.save(req.orderData);
          childSpan.setAttribute('order.id', result.id);
          childSpan.end();
          return result;
        });

        span.setStatus({ code: SpanStatusCode.OK });
        return order;

      } catch (error) {
        span.setStatus({ code: SpanStatusCode.ERROR, message: error.message });
        throw error;
      } finally {
        span.end();
      }
    }
  );
}

Criterios de Decision para Adoptar Microservicios

Casos donde se Recomienda

  • Equipos de 10+ personas que necesitan desarrollo independiente
  • Diferentes partes con diferentes requisitos de escalado
  • Se requiere diversidad de stack tecnologico
  • El aislamiento de fallos es importante

Casos a Evitar

  • Equipos pequenos (alrededor de 3-5 personas)
  • Etapa inicial con comprension insuficiente del dominio
  • Capacidad operacional insuficiente (monitoreo, CI/CD)
  • Aplicaciones CRUD simples

Importante: En muchos casos se recomienda el enfoque de “comenzar con monolito y dividir segun sea necesario”.

Resumen

La arquitectura de microservicios, cuando se implementa correctamente, ofrece grandes beneficios, pero tambien conlleva complejidad.

Principios de Diseno

  1. Responsabilidad unica: 1 servicio = 1 funcion de negocio
  2. Independencia de datos: DB separada por servicio
  3. Bajo acoplamiento: Comunicacion a traves de interfaces
  4. Alta cohesion: Funciones relacionadas en el mismo servicio

Patrones Esenciales

  • API Gateway: Punto de entrada unico
  • Circuit Breaker: Prevencion de propagacion de fallos
  • Saga: Gestion de transacciones distribuidas
  • Tracing Distribuido: Seguimiento de requests

La clave del exito en microservicios esta en definir limites apropiados y construir una infraestructura operacional robusta.

Enlaces de Referencia

← Volver a la lista