Patrones de diseño


Clasificación general

Creational Patterns

Patrones que facilitan la creación de objetos sin acoplar el código a clases concretas.

  • Factory
    • [Patrón FACTORY ✅ (Ejemplo en Typescript) - YouTube](https://youtu.be/JBV_ZOxBWpo)
    • Define una interfaz común para la creación de objetos, delegando a las subclases la decisión de qué clase instanciar.
    • Evita el uso directo de new en el código cliente, moviendo la lógica de creación a un método especializado.
    • Permite tener métodos de creación distintos en las clases hijas, adaptándose al contexto.
  • Abstract Factory
    • Permite crear familias de objetos relacionados sin especificar sus clases concretas.
    • Se usa para mantener la consistencia entre objetos que deben coexistir (por ejemplo, componentes de una misma interfaz gráfica).
  • Builder
    • Separa la construcción compleja de un objeto de su representación final.
    • Útil cuando un objeto tiene muchos parámetros opcionales o configuraciones.
  • Prototype
    • Crea nuevos objetos copiando instancias existentes (clonación).
    • Ideal para casos donde la creación desde cero es costosa (por ejemplo, estructuras preconfiguradas o pesadas).

Structural Patterns

Patrones que facilitan la composición y organización de clases y objetos para formar estructuras flexibles y escalables.

  • Adapter
    • Convierte la interfaz de una clase existente en otra que el cliente espera.
    • Ejemplo: adaptar diferentes clientes HTTP con una interfaz unificada.
  • Bridge
    • Desacopla una abstracción de su implementación, permitiendo que ambas evolucionen de forma independiente.
  • Composite
    • Permite tratar objetos individuales y compuestos de forma uniforme (por ejemplo, jerarquías de menús o nodos).
  • Decorator
    • Añade dinámicamente responsabilidades o comportamientos a un objeto sin modificar su estructura original.
  • Facade
    • Proporciona una interfaz simplificada a un conjunto complejo de clases o subsistemas.
    • Ejemplo: un punto de acceso único a múltiples APIs.
  • Flyweight
    • Optimiza el uso de memoria compartiendo instancias de objetos similares.
    • Separa el estado intrínseco (compartido) del extrínseco (único).
  • Proxy
    • Proporciona un objeto sustituto o intermediario que controla el acceso a otro.
    • Se usa para control de acceso, carga diferida, logging, o conexiones remotas.

Behavioral Patterns

Patrones que se centran en la comunicación y responsabilidad entre objetos.

  • Strategy
    • Define una familia de algoritmos, encapsula cada uno y los hace intercambiables.
    • Permite variar el comportamiento del programa sin modificar el código cliente.
  • Observer
    • [Patrón OBSERVER 🔎 ✅ TYPESCRIPT - YouTube](https://youtu.be/pwMoZKZQBhc)
    • Establece una relación uno-a-muchos entre objetos.
    • Componentes:
      • Subject: mantiene una lista de observadores.
      • Observer: define la interfaz para recibir actualizaciones.
      • Notify: método que comunica los cambios.
    • Ejemplo: sistemas de eventos, interfaces reactivas, o data-binding.

Patrones arquitectónicos

  • BFF (Backend For Frontend)
    • [Patrón BFF: Backend For Frontend Patrones de Arquitectura y Diseño - YouTube](https://www.youtube.com/watch?v=UcGaicwNKVA)
    • Crea un backend específico para cada tipo de frontend (web, móvil, etc.).
    • Permite optimizar la respuesta y estructura de datos para las necesidades de cada cliente.
    • Mejora la mantenibilidad y desacopla la lógica de negocio del cliente.

MVC, MVP, MVVM, MVVM-C y VIPER

  • MVC (Model-View-Controller)
    • Divide la aplicación en tres componentes: Modelo (datos), Vista (interfaz) y Controlador (lógica).
    • Favorece la separación de responsabilidades y la escalabilidad.
  • MVP (Model-View-Presenter)
    • Variante de MVC donde el Presenter actúa como mediador directo entre vista y modelo.
    • Facilita testeo unitario al aislar la lógica de presentación.
  • MVVM (Model-View-ViewModel)
    • Introduce el ViewModel, que expone datos y comandos a la vista a través de binding.
    • Común en frameworks como Angular, React (con Hooks) o SwiftUI.
  • MVVM-C (Model-View-ViewModel-Coordinator)
    • Añade el Coordinator para manejar la navegación y el flujo entre pantallas.
    • Mejora la modularidad y la reutilización.
  • VIPER
    • View, Interactor, Presenter, Entity, Router.
    • Arquitectura usada en iOS que promueve la separación estricta de responsabilidades.
    • El Interactor maneja la lógica de negocio, el Router la navegación, y el Presenter comunica vista y lógica.

Patrón Criteria

  • Define una interfaz de criterios para aplicar filtros sobre colecciones de objetos.
  • Se relaciona con principios SOLID, especialmente Open/Closed.
  • Ideal para carga en memoria con filtrado dinámico.
  • Estructura:
    • Clase abstracta o interfaz con métodos and, or, etc.
    • Modelado de filtros reutilizables.
  • Ventajas:
    • Simplifica consultas en memoria o bases de datos con bajo volumen de datos.
    • Puede actuar como un DTO (Data Transfer Object).
    • Permite construir un DSL (Domain Specific Language) para filtros.
  • Implementación:
    • Clase con filtros definidos.
    • Convertidores para transformar filtros en consultas reales (SQL, ORM, etc.).
    • Soporte para paginación, ordenación y combinaciones lógicas de criterios.

Patrones de concurrencia

  • Thread Pool
    • Gestiona un conjunto de hilos reutilizables para ejecutar tareas en paralelo sin crear nuevos hilos constantemente.
  • Future
    • Representa el resultado de una tarea asíncrona que se completará en el futuro.
  • Reactor
    • Maneja múltiples flujos de eventos concurrentes de forma no bloqueante.
    • Utilizado en servidores de alto rendimiento y sistemas reactivos.

Código de ejemplo

Factory Pattern (TypeScript)

interface Product {
	operation(): string;
}

class ConcreteProductA implements Product {
	operation(): string {
		return "Resultado del producto A";
	}
}

class ConcreteProductB implements Product {
	operation(): string {
		return "Resultado del producto B";
	}
}

abstract class Creator {
	abstract factoryMethod(): Product;

	someOperation(): string {
		const product = this.factoryMethod();
		return `Creador: el mismo código ha trabajado con ${product.operation()}`;
	}
}

class ConcreteCreatorA extends Creator {
	factoryMethod(): Product {
		return new ConcreteProductA();
	}
}

class ConcreteCreatorB extends Creator {
	factoryMethod(): Product {
		return new ConcreteProductB();
	}
}

function clientCode(creator: Creator) {
	console.log("Cliente: No conozco la clase del creador, pero aún funciona.");
	console.log(creator.someOperation());
}

clientCode(new ConcreteCreatorA());
clientCode(new ConcreteCreatorB());

`


Observer Pattern (TypeScript)

interface Observer {
	update(subject: Subject): void;
}

class Subject {
	private observers: Observer[] = [];
	private state: number = 0;

	public attach(observer: Observer): void {
		this.observers.push(observer);
	}

	public detach(observer: Observer): void {
		this.observers = this.observers.filter(o => o !== observer);
	}

	public notify(): void {
		for (const observer of this.observers) {
			observer.update(this);
		}
	}

	public setState(state: number): void {
		this.state = state;
		this.notify();
	}

	public getState(): number {
		return this.state;
	}
}

class ConcreteObserver implements Observer {
	public update(subject: Subject): void {
		console.log(`Observador notificado. Nuevo estado: ${subject.getState()}`);
	}
}

// Ejemplo de uso
const subject = new Subject();
const observer1 = new ConcreteObserver();
const observer2 = new ConcreteObserver();

subject.attach(observer1);
subject.attach(observer2);

subject.setState(5);

Patrones de diseño — Expansión y conceptos avanzados

Patrones de diseño avanzados

Dependency Injection (DI)

  • Propósito: desacoplar los componentes evitando dependencias directas entre clases.
  • Se logra inyectando las dependencias desde fuera (por constructor, setters o contenedores).
  • Ventajas:
    • Facilita testeo unitario y mocking.
    • Mejora la mantenibilidad y adherencia a Inversión de Dependencias (principio SOLID).
  • Ejemplo de uso: frameworks como Angular, Spring o NestJS.

Inversion of Control (IoC)

  • Principio que cede el control de la creación y gestión de objetos a un contenedor o framework.
  • Relación con DI: la inyección de dependencias es una implementación concreta de IoC.
  • Beneficios:
    • Desacopla la aplicación del ciclo de vida de sus componentes.
    • Facilita la configuración declarativa (por ejemplo, mediante archivos YAML o JSON).

Event Sourcing

  • Los cambios en el estado del sistema se almacenan como una secuencia de eventos inmutables.
  • En lugar de guardar solo el estado final, se conserva todo el historial.
  • Ventajas:
    • Auditoría completa y trazabilidad de acciones.
    • Posibilidad de reconstruir el estado en cualquier momento.
  • Usos comunes:
    • Sistemas financieros, blockchain, CQRS.
  • Desafíos:
    • Complejidad en la rehidratación de estado.
    • Necesidad de manejar versiones de eventos.

CQRS (Command Query Responsibility Segregation)

  • Separa las operaciones de lectura (Query) y escritura (Command) en modelos distintos.
  • Objetivo: optimizar rendimiento, escalabilidad y claridad conceptual.
  • Ventajas:
    • Permite distintas estrategias de almacenamiento para lectura y escritura.
    • Escalable en microservicios.
  • Relación con Event Sourcing:
    • A menudo se combinan, donde los comandos generan eventos y las consultas leen proyecciones derivadas de ellos.
  • websockets
  • registro de eventos

Repository Pattern

  • Abstracción que actúa como intermediario entre la capa de dominio y la capa de datos.
  • Propósito: encapsular la lógica de acceso a datos y mantener una interfaz limpia.
  • Ventajas:
    • Simplifica pruebas unitarias.
    • Oculta la complejidad de ORMs o SQL.
  • Ejemplo:
interface UserRepository {
	findByEmail(email: string): User | null;
	save(user: User): void;
}

Unit of Work

  • Coordina las operaciones sobre múltiples repositorios como una única transacción.
  • Objetivo: garantizar consistencia y evitar estados intermedios corruptos.
  • Se usa en combinación con Repository Pattern y ORMs (como Hibernate o TypeORM).

Patrones estructurales avanzados

Dependency Inversion con Facade

  • Combina principios SOLID con el patrón Facade para unificar interfaces externas.
  • El Facade actúa como punto de entrada único, mientras que el DI Container administra la inyección de dependencias.
  • Ejemplo: en sistemas grandes, la fachada puede encapsular APIs, bases de datos y servicios externos.

Microkernel (Plugin Architecture)

  • Núcleo mínimo que ofrece la infraestructura base, mientras los módulos o plugins añaden funcionalidades.
  • Ventajas:
    • Extensible y modular.
    • Facilita el mantenimiento y personalización.
  • Ejemplo: sistemas de IDEs, navegadores, o plataformas de videojuegos.

Proxy avanzado: Lazy Loading y Caching

  • Lazy Loading: retrasa la carga de recursos hasta que son necesarios.
  • Caching Proxy: almacena respuestas de un objeto costoso para acelerar las subsiguientes peticiones.
  • Ejemplo: ORM que retrasa la carga de relaciones (lazy relationships).

Patrones de comportamiento avanzados

Command Pattern

  • Encapsula una petición como un objeto, permitiendo deshacer operaciones, registrar o encolar comandos.
  • Componentes:
    • Command: interfaz común con execute().
    • Invoker: ejecuta comandos.
    • Receiver: contiene la lógica de negocio real.
  • Ejemplo de uso: implementaciones de undo/redo, colas de tareas.

State Pattern

  • Permite que un objeto altere su comportamiento cuando cambia su estado interno.
  • Diferencia con Strategy: en State, los estados pueden transicionar entre sí; en Strategy, las estrategias son seleccionadas externamente.
  • Ejemplo: una máquina de estados para el ciclo de vida de una orden o sesión.

Chain of Responsibility

  • Permite pasar una solicitud a través de una cadena de manejadores hasta que uno la procese.
  • Ventajas:
    • Desacopla el emisor del receptor.
    • Permite añadir nuevos manejadores sin alterar el código existente.
  • Ejemplo: middleware en frameworks web (Express, Django, etc.).

Mediator Pattern

  • Centraliza la comunicación entre múltiples objetos (colegas), evitando dependencias directas.
  • Aplicación: sistemas de UI, chatrooms, o flujos coordinados.
  • Ejemplo: un componente UI notifica al Mediator en lugar de comunicarse con otros directamente.

Patrones arquitectónicos complementarios

Event-Driven Architecture (EDA)

  • Arquitectura basada en eventos asíncronos que disparan reacciones en diferentes componentes.
  • Elementos:
    • Emisores (producers)
    • Consumidores (listeners)
    • Bus de eventos
  • Ventajas:
    • Escalabilidad y desacoplamiento.
    • Ideal para sistemas distribuidos y microservicios.

Layered Architecture

  • Divide el sistema en capas con responsabilidades claras:
    • Presentación
    • Aplicación
    • Dominio
    • Infraestructura
  • Beneficios:
    • Aislamiento entre lógica de negocio y detalles técnicos.
    • Combinable con otros patrones (Repository, Service, Facade).

Hexagonal Architecture (Ports and Adapters)

  • También llamada Arquitectura Limpia o Clean Architecture.
  • Los componentes externos se conectan mediante puertos (interfaces) y adaptadores (implementaciones).
  • Ventajas:
    • Pruebas unitarias simples (mockeo de adaptadores).
    • Independencia de frameworks o bases de datos.
  • Relación: extensión conceptual de los patrones Adapter y Dependency Inversion.

Patrones de concurrencia y reactividad

Producer-Consumer

  • Divide el trabajo entre productores (generan datos/tareas) y consumidores (procesan).
  • Sincronización mediante colas o buffers.
  • Ejemplo: sistemas de mensajería o streaming (Kafka, RabbitMQ).

Actor Model

  • Los actores son unidades independientes con su propio estado y buzón de mensajes.
  • Ventajas:
    • Elimina condiciones de carrera.
    • Escalabilidad horizontal.
  • Usado en: Akka, Erlang, Elixir.

Scheduler Pattern

  • Coordina tareas periódicas o planificadas.
  • Se usa en combinación con colas o cron jobs.
  • Ejemplo: actualización de cachés o limpieza de logs.

Patrones emergentes y modernos

Saga Pattern

  • Maneja transacciones distribuidas en microservicios.
  • Divide una operación global en múltiples pasos locales, cada uno con su compensación.
  • Tipos:
    • Choreography: cada servicio reacciona a eventos.
    • Orchestration: un coordinador central dirige el flujo.
  • Uso común: sistemas de pagos o reservas.

Circuit Breaker

  • Previene fallos en cascada cuando un servicio externo falla repetidamente.
  • Estados:
    • Closed: peticiones normales.
    • Open: bloquea peticiones tras múltiples fallos.
    • Half-open: permite intentos de recuperación.
  • Implementado en librerías como Resilience4j, Hystrix.

Bulkhead Pattern

  • Aísla componentes para evitar que el fallo de uno afecte a otros.
  • Inspirado en los compartimentos estancos de los barcos.
  • Ejemplo: separar hilos o conexiones por servicio.

Recursos y vínculos útiles

  • Refactoring Guru - Design Patterns
  • Arquitectura Limpia
  • Microservicios y patrones de resiliencia
  • Event Driven Systems
  • CQRS y Event Sourcing
  • SOLID y principios de diseño

Patrones de diseño — Ejemplos de código


🧱 PATRONES CREACIONALES

Factory Pattern (TypeScript)

interface Product {
  getDescription(): string;
}

class ConcreteProductA implements Product {
  getDescription() {
    return "Soy el producto A";
  }
}

class ConcreteProductB implements Product {
  getDescription() {
    return "Soy el producto B";
  }
}

abstract class Creator {
  abstract createProduct(): Product;

  deliver(): void {
    const product = this.createProduct();
    console.log("Entregando producto:", product.getDescription());
  }
}

class ConcreteCreatorA extends Creator {
  createProduct(): Product {
    return new ConcreteProductA();
  }
}

class ConcreteCreatorB extends Creator {
  createProduct(): Product {
    return new ConcreteProductB();
  }
}

// Uso realista
const creators: Creator[] = [new ConcreteCreatorA(), new ConcreteCreatorB()];
creators.forEach(c => c.deliver());

`


Abstract Factory (Java)

interface Button {
  void render();
}

interface Checkbox {
  void render();
}

interface GUIFactory {
  Button createButton();
  Checkbox createCheckbox();
}

class MacButton implements Button {
  public void render() { System.out.println("Render Mac Button"); }
}

class WindowsButton implements Button {
  public void render() { System.out.println("Render Windows Button"); }
}

class MacFactory implements GUIFactory {
  public Button createButton() { return new MacButton(); }
  public Checkbox createCheckbox() { return () -> System.out.println("Render Mac Checkbox"); }
}

class WindowsFactory implements GUIFactory {
  public Button createButton() { return new WindowsButton(); }
  public Checkbox createCheckbox() { return () -> System.out.println("Render Windows Checkbox"); }
}

// Cliente
public class App {
  public static void main(String[] args) {
    GUIFactory factory = System.getProperty("os.name").contains("Mac")
        ? new MacFactory()
        : new WindowsFactory();
    Button button = factory.createButton();
    button.render();
  }
}

Builder Pattern (Python)

class House:
    def __init__(self):
        self.foundation = None
        self.walls = []
        self.roof = None

    def __repr__(self):
        return f"House(foundation={self.foundation}, walls={self.walls}, roof={self.roof})"

class HouseBuilder:
    def __init__(self):
        self.house = House()

    def build_foundation(self, material):
        self.house.foundation = material
        return self

    def build_walls(self, walls):
        self.house.walls.extend(walls)
        return self

    def build_roof(self, roof_type):
        self.house.roof = roof_type
        return self

    def get_result(self):
        return self.house

# Uso realista
builder = HouseBuilder()
house = (builder
    .build_foundation("Hormigón armado")
    .build_walls(["Ladrillo", "Aislante térmico"])
    .build_roof("Tejas cerámicas")
    .get_result())

print(house)

Prototype Pattern (JavaScript)

class Document {
  constructor(title, content) {
    this.title = title;
    this.content = content;
  }

  clone() {
    return new Document(this.title, { ...this.content });
  }
}

// Uso realista
const doc1 = new Document("Manual", { pages: 10, lang: "es" });
const doc2 = doc1.clone();
doc2.content.pages = 12;

console.log(doc1, doc2);

🧩 PATRONES ESTRUCTURALES

Adapter Pattern (TypeScript)

class OldPaymentGateway {
  makePayment(amount: number) {
    console.log(`Pagando €${amount} con API antigua`);
  }
}

interface ModernGateway {
  pay(amount: number): void;
}

class PaymentAdapter implements ModernGateway {
  constructor(private oldGateway: OldPaymentGateway) {}

  pay(amount: number): void {
    this.oldGateway.makePayment(amount);
  }
}

// Uso realista
const oldSystem = new OldPaymentGateway();
const newSystem: ModernGateway = new PaymentAdapter(oldSystem);
newSystem.pay(250);

Decorator Pattern (Python)

class Notifier:
    def send(self, message):
        print(f"Enviando notificación base: {message}")

class EmailDecorator(Notifier):
    def __init__(self, wrappee):
        self.wrappee = wrappee

    def send(self, message):
        self.wrappee.send(message)
        print(f"Enviando correo: {message}")

class SlackDecorator(Notifier):
    def __init__(self, wrappee):
        self.wrappee = wrappee

    def send(self, message):
        self.wrappee.send(message)
        print(f"Enviando mensaje Slack: {message}")

# Uso realista
notifier = SlackDecorator(EmailDecorator(Notifier()))
notifier.send("Actualización del sistema completada ✅")

Proxy Pattern (Python)

class ExpensiveService:
    def request(self):
        print("Ejecutando operación costosa...")

class ProxyService:
    def __init__(self):
        self._real_service = None

    def request(self):
        if not self._real_service:
            print("Inicializando servicio real...")
            self._real_service = ExpensiveService()
        print("Llamando a servicio real a través del proxy...")
        self._real_service.request()

# Uso
service = ProxyService()
service.request()
service.request()

⚙️ PATRONES DE COMPORTAMIENTO

Strategy Pattern (TypeScript)

interface SortingStrategy {
  sort(data: number[]): number[];
}

class QuickSort implements SortingStrategy {
  sort(data: number[]): number[] {
    console.log("Usando QuickSort");
    return data.sort((a, b) => a - b);
  }
}

class BubbleSort implements SortingStrategy {
  sort(data: number[]): number[] {
    console.log("Usando BubbleSort");
    for (let i = 0; i < data.length; i++)
      for (let j = 0; j < data.length - i - 1; j++)
        if (data[j] > data[j + 1])
          [data[j], data[j + 1]] = [data[j + 1], data[j]];
    return data;
  }
}

class Context {
  constructor(private strategy: SortingStrategy) {}
  execute(data: number[]) {
    return this.strategy.sort(data);
  }
}

const dataset = [5, 2, 9, 1];
new Context(new QuickSort()).execute(dataset);
new Context(new BubbleSort()).execute(dataset);

Observer Pattern (TypeScript)

interface Observer {
  update(stock: string, price: number): void;
}

class StockMarket {
  private observers: Observer[] = [];
  private prices = new Map<string, number>();

  attach(observer: Observer) {
    this.observers.push(observer);
  }

  setPrice(stock: string, price: number) {
    this.prices.set(stock, price);
    this.notify(stock, price);
  }

  notify(stock: string, price: number) {
    for (const obs of this.observers) {
      obs.update(stock, price);
    }
  }
}

class Trader implements Observer {
  constructor(private name: string) {}
  update(stock: string, price: number): void {
    console.log(`${this.name} notificado: ${stock} = €${price}`);
  }
}

// Uso realista
const market = new StockMarket();
const trader1 = new Trader("Eduardo");
const trader2 = new Trader("Sofía");

market.attach(trader1);
market.attach(trader2);

market.setPrice("AAPL", 175.5);

Command Pattern (Python)

class Light:
    def on(self):
        print("💡 Luz encendida")

    def off(self):
        print("💡 Luz apagada")

class Command:
    def execute(self): pass

class TurnOnCommand(Command):
    def __init__(self, light):
        self.light = light
    def execute(self):
        self.light.on()

class TurnOffCommand(Command):
    def __init__(self, light):
        self.light = light
    def execute(self):
        self.light.off()

class RemoteControl:
    def __init__(self):
        self.commands = {}
    def set_command(self, name, command):
        self.commands[name] = command
    def press(self, name):
        if name in self.commands:
            self.commands[name].execute()

# Uso realista
light = Light()
remote = RemoteControl()
remote.set_command("on", TurnOnCommand(light))
remote.set_command("off", TurnOffCommand(light))

remote.press("on")
remote.press("off")

State Pattern (JavaScript)

class Context {
  constructor(state) {
    this.transitionTo(state);
  }

  transitionTo(state) {
    this.state = state;
    this.state.context = this;
  }

  request() {
    this.state.handle();
  }
}

class State {
  handle() {}
}

class ConcreteStateA extends State {
  handle() {
    console.log("Estado A manejando la solicitud. → Cambiando a B");
    this.context.transitionTo(new ConcreteStateB());
  }
}

class ConcreteStateB extends State {
  handle() {
    console.log("Estado B manejando la solicitud. → Cambiando a A");
    this.context.transitionTo(new ConcreteStateA());
  }
}

// Uso
const context = new Context(new ConcreteStateA());
context.request();
context.request();

🏗️ PATRONES ARQUITECTÓNICOS

Repository Pattern (TypeScript)

interface User {
  id: number;
  name: string;
  email: string;
}

class UserRepository {
  private users: User[] = [];

  save(user: User): void {
    this.users.push(user);
  }

  findByEmail(email: string): User | undefined {
    return this.users.find(u => u.email === email);
  }

  all(): User[] {
    return [...this.users];
  }
}

// Uso
const repo = new UserRepository();
repo.save({ id: 1, name: "Eduardo", email: "edu@domain.com" });
console.log(repo.findByEmail("edu@domain.com"));

Event Sourcing + CQRS (Python simplificado)

class Event:
    def __init__(self, type, data):
        self.type = type
        self.data = data

class Account:
    def __init__(self):
        self.balance = 0
        self.events = []

    def deposit(self, amount):
        self.events.append(Event("Deposit", amount))
        self.balance += amount

    def withdraw(self, amount):
        self.events.append(Event("Withdraw", amount))
        self.balance -= amount

    def replay(self, events):
        for e in events:
            if e.type == "Deposit":
                self.balance += e.data
            elif e.type == "Withdraw":
                self.balance -= e.data

# Uso realista
acc = Account()
acc.deposit(100)
acc.withdraw(30)
print("Saldo actual:", acc.balance)

⚡ PATRONES DE CONCURRENCIA

Thread Pool (Python)

import concurrent.futures
import time

def worker(n):
    time.sleep(1)
    return f"Tarea {n} completada"

with concurrent.futures.ThreadPoolExecutor(max_workers=3) as executor:
    results = executor.map(worker, range(5))
    for r in results:
        print(r)

Reactor Pattern (Node.js)

const net = require("net");

const server = net.createServer(socket => {
  socket.on("data", data => {
    console.log("Recibido:", data.toString());
    socket.write("Respuesta del servidor");
  });
});

server.listen(4000, () => console.log("Servidor Reactor en puerto 4000"));

🔗 Recursos relacionados

  • Patrones de diseño - Design patterns de refactoring guru
  • Arquitectura Limpia
  • Microservicios y patrones de resiliencia
  • Event Driven Systems
  • CQRS y Event Sourcing
  • SOLID y principios de diseño

Patrones de diseño — Tests y escenarios de integración


✅ Escenario 1 — Factory + Strategy + Observer (Sistema de pagos modular)

Contexto

Un sistema de pagos:

  • Factory decide qué pasarela de pago crear (Stripe, PayPal, etc.).
  • Strategy define cómo calcular las comisiones según tipo de pago.
  • Observer notifica a los clientes cuando un pago cambia de estado.

Código del sistema (TypeScript)

// 1. OBSERVER ---------------------------------------------------
interface Observer {
  update(event: string, data: any): void;
}

class NotificationService implements Observer {
  constructor(private userEmail: string) {}

  update(event: string, data: any) {
    console.log(`[NOTIFY ${this.userEmail}] Evento: ${event}`, data);
  }
}

class PaymentEventBus {
  private observers: Observer[] = [];

  subscribe(observer: Observer) {
    this.observers.push(observer);
  }

  publish(event: string, data: any) {
    this.observers.forEach(o => o.update(event, data));
  }
}


// 2. STRATEGY ---------------------------------------------------
interface FeeStrategy {
  calculate(amount: number): number;
}

class StandardFee implements FeeStrategy {
  calculate(amount: number): number {
    return amount * 0.03; // 3%
  }
}

class PremiumFee implements FeeStrategy {
  calculate(amount: number): number {
    return amount * 0.015; // 1.5%
  }
}


// 3. FACTORY -----------------------------------------------------
interface PaymentGateway {
  pay(amount: number): Promise<string>;
}

class StripeGateway implements PaymentGateway {
  async pay(amount: number): Promise<string> {
    return `Stripe OK: ${amount}`;
  }
}

class PayPalGateway implements PaymentGateway {
  async pay(amount: number): Promise<string> {
    return `PayPal OK: ${amount}`;
  }
}

class PaymentGatewayFactory {
  static create(gateway: string): PaymentGateway {
    if (gateway === "stripe") return new StripeGateway();
    if (gateway === "paypal") return new PayPalGateway();
    throw new Error("Unknown Gateway");
  }
}


// 4. FUSIÓN DE PATRONES: PaymentService ---------------------------
class PaymentService {
  constructor(
    private strategy: FeeStrategy,
    private eventBus: PaymentEventBus
  ) {}

  async process(amount: number, gatewayName: string) {
    const commission = this.strategy.calculate(amount);
    const total = amount + commission;

    const gateway = PaymentGatewayFactory.create(gatewayName);

    const result = await gateway.pay(total);

    this.eventBus.publish("payment_completed", {
      amount,
      commission,
      total,
      gateway: gatewayName,
      result
    });

    return result;
  }
}

`

Test realista

// TEST DE INTEGRACIÓN COMPLETA -------------------------

const eventBus = new PaymentEventBus();
eventBus.subscribe(new NotificationService("eduardo@domain.com"));
eventBus.subscribe(new NotificationService("sofia@domain.com"));

const standardPayment = new PaymentService(new StandardFee(), eventBus);
const premiumPayment = new PaymentService(new PremiumFee(), eventBus);

(async () => {
  console.log(await standardPayment.process(100, "stripe"));
  console.log(await premiumPayment.process(200, "paypal"));
})();

✅ Escenario 2 — Builder + Repository + Unit of Work (Sistema de pedidos)

Contexto

Un sistema de pedidos:

  • Builder crea pedidos complejos.
  • Repository Pattern gestiona persistencia.
  • Unit of Work asegura consistencia transaccional.

Código (Python)

# 1. BUILDER ------------------------------------------------------
class Order:
    def __init__(self):
        self.items = []
        self.coupon = None
        self.address = None

    def __repr__(self):
        return f"Order(items={self.items}, coupon={self.coupon}, address={self.address})"


class OrderBuilder:
    def __init__(self):
        self.order = Order()

    def add_item(self, name, price):
        self.order.items.append((name, price))
        return self

    def set_coupon(self, coupon):
        self.order.coupon = coupon
        return self

    def deliver_to(self, address):
        self.order.address = address
        return self

    def build(self):
        return self.order


# 2. REPOSITORY ----------------------------------------------------
class OrderRepository:
    def __init__(self):
        self._store = {}

    def save(self, order):
        order_id = len(self._store) + 1
        self._store[order_id] = order
        return order_id


# 3. UNIT OF WORK --------------------------------------------------
class UnitOfWork:
    def __init__(self, order_repo):
        self.order_repo = order_repo
        self._pending_orders = []

    def register_new(self, order):
        self._pending_orders.append(order)

    def commit(self):
        ids = []
        for order in self._pending_orders:
            ids.append(self.order_repo.save(order))
        self._pending_orders.clear()
        return ids

Test realista

repo = OrderRepository()
uow = UnitOfWork(repo)

order = (OrderBuilder()
         .add_item("Teclado", 50)
         .add_item("Monitor", 250)
         .set_coupon("SUMMER20")
         .deliver_to("Calle Gran Vía 123")
         .build())

uow.register_new(order)
ids = uow.commit()

print("Pedidos guardados:", ids)

✅ Escenario 3 — Adapter + Facade + Strategy (Sistema de envío logístico)

Contexto

  • Adapter adapta APIs externas de empresas de envío.
  • Strategy calcula coste del envío.
  • Facade crea una interfaz única para clientes.

Código (TypeScript)

// 1. ADAPTERS -----------------------------------------------------
class DHLApi {
  sendPackage(pkg: any) {
    return `DHL enviado → ${pkg.id}`;
  }
}

class FedexApi {
  deliver(pkg: any) {
    return `FEDEX enviado → ${pkg.id}`;
  }
}

interface ShippingProvider {
  ship(pkg: any): string;
}

class DHLAdapter implements ShippingProvider {
  constructor(private api = new DHLApi()) {}
  ship(pkg: any): string {
    return this.api.sendPackage(pkg);
  }
}

class FedexAdapter implements ShippingProvider {
  constructor(private api = new FedexApi()) {}
  ship(pkg: any): string {
    return this.api.deliver(pkg);
  }
}


// 2. STRATEGY ------------------------------------------------------
interface ShippingCostStrategy {
  calculate(weight: number): number;
}

class StandardShipping implements ShippingCostStrategy {
  calculate(weight: number) {
    return weight * 1.2;
  }
}

class AirShipping implements ShippingCostStrategy {
  calculate(weight: number) {
    return weight * 3.5;
  }
}


// 3. FACADE --------------------------------------------------------
class ShippingFacade {
  constructor(
    private provider: ShippingProvider,
    private costStrategy: ShippingCostStrategy
  ) {}

  processShipment(pkg: any) {
    const cost = this.costStrategy.calculate(pkg.weight);
    const response = this.provider.ship(pkg);

    return { ...pkg, cost, response };
  }
}

Test realista

const pkg = { id: "PKG-777", weight: 5 };

const facade1 = new ShippingFacade(
  new DHLAdapter(),
  new StandardShipping()
);

console.log(facade1.processShipment(pkg));

const facade2 = new ShippingFacade(
  new FedexAdapter(),
  new AirShipping()
);

console.log(facade2.processShipment(pkg));

✅ Escenario 4 — Chain of Responsibility + Command + Observer (Procesador de operaciones financieras)

Código (Python)

# 1. OBSERVER ------------------------------------------------------
class EventBus:
    def __init__(self):
        self.listeners = []

    def subscribe(self, cb):
        self.listeners.append(cb)

    def publish(self, event, data):
        for cb in self.listeners:
            cb(event, data)


# 2. COMMAND --------------------------------------------------------
class Command:
    def execute(self, ctx): pass

class ValidateFunds(Command):
    def execute(self, ctx):
        if ctx["balance"] < ctx["amount"]:
            raise Exception("Fondos insuficientes")
        return ctx

class ApplyTransaction(Command):
    def execute(self, ctx):
        ctx["balance"] -= ctx["amount"]
        return ctx


# 3. CHAIN OF RESPONSIBILITY ----------------------------------------
class Handler:
    def __init__(self):
        self.next = None

    def set_next(self, handler):
        self.next = handler
        return handler

    def handle(self, ctx):
        ctx = self.process(ctx)
        if self.next:
            return self.next.handle(ctx)
        return ctx

    def process(self, ctx): pass


class ValidateHandler(Handler):
    def __init__(self, command):
        super().__init__()
        self.command = command

    def process(self, ctx):
        return self.command.execute(ctx)


class ApplyHandler(Handler):
    def __init__(self, command):
        super().__init__()
        self.command = command

    def process(self, ctx):
        return self.command.execute(ctx)

Test realista

eventbus = EventBus()
eventbus.subscribe(lambda e, d: print("[LOG EVENT]", e, d))

ctx = {"amount": 40, "balance": 100}

pipeline = ValidateHandler(ValidateFunds())
pipeline.set_next(ApplyHandler(ApplyTransaction()))

result = pipeline.handle(ctx)

eventbus.publish("transaction_completed", result)

print(result)

✅ Escenario 5 — Microkernel + Plugin Architecture

Código (JavaScript)

class Kernel {
  constructor() {
    this.plugins = [];
  }

  register(plugin) {
    this.plugins.push(plugin);
  }

  execute(event, payload) {
    for (const plugin of this.plugins) {
      if (plugin.supports(event)) {
        plugin.run(payload);
      }
    }
  }
}

// Plugins
class LoggingPlugin {
  supports(event) { return true; }
  run(payload) { console.log("[LOG]", payload); }
}

class MetricsPlugin {
  supports(event) { return event === "request"; }
  run(payload) { console.log("[METRICS] +1 request", payload.url); }
}

// Test
const kernel = new Kernel();
kernel.register(new LoggingPlugin());
kernel.register(new MetricsPlugin());

kernel.execute("request", { url: "/home" });

Patrones de diseño — Integraciones avanzadas

  • Patrones de diseño
  • Arquitectura de Software
  • DDD Domain Driven Design
  • CQRS
  • Event Sourcing
  • MVVM
  • Saga Pattern
  • Circuit Breaker Pattern
  • Repository Pattern

🧠 Escenario 1 — CQRS + Event Sourcing + Factory + Strategy

Contexto

Un sistema de pedidos distribuidos:

  • CQRS: separa lectura/escritura con modelos diferentes.
  • Event Sourcing: guarda los cambios como eventos en vez del estado final.
  • Factory: crea comandos y eventos.
  • Strategy: define cómo aplicar descuentos según tipo de cliente.

Código (Python)

from abc import ABC, abstractmethod
from datetime import datetime
import uuid

# 1. EVENT SOURCING -------------------------------------------------
class Event(ABC):
    @abstractmethod
    def apply(self, aggregate): pass


class OrderCreated(Event):
    def __init__(self, order_id, customer_type, amount):
        self.order_id = order_id
        self.customer_type = customer_type
        self.amount = amount

    def apply(self, aggregate):
        aggregate.id = self.order_id
        aggregate.customer_type = self.customer_type
        aggregate.amount = self.amount
        aggregate.status = "CREATED"
        aggregate.events.append(self)


class OrderPaid(Event):
    def __init__(self, order_id):
        self.order_id = order_id

    def apply(self, aggregate):
        aggregate.status = "PAID"
        aggregate.events.append(self)


# 2. STRATEGY -------------------------------------------------------
class DiscountStrategy(ABC):
    @abstractmethod
    def calculate(self, amount): pass


class RegularDiscount(DiscountStrategy):
    def calculate(self, amount): return amount * 0.95


class VIPDiscount(DiscountStrategy):
    def calculate(self, amount): return amount * 0.85


# 3. FACTORY --------------------------------------------------------
class EventFactory:
    @staticmethod
    def create_event(event_type, **kwargs):
        mapping = {
            "OrderCreated": OrderCreated,
            "OrderPaid": OrderPaid
        }
        return mapping[event_type](**kwargs)


# 4. CQRS — COMMAND SIDE -------------------------------------------
class OrderAggregate:
    def __init__(self):
        self.id = None
        self.customer_type = None
        self.amount = 0
        self.status = "NEW"
        self.events = []

    def create_order(self, customer_type, amount, discount_strategy):
        order_id = str(uuid.uuid4())
        final_amount = discount_strategy.calculate(amount)
        event = EventFactory.create_event("OrderCreated",
                                          order_id=order_id,
                                          customer_type=customer_type,
                                          amount=final_amount)
        event.apply(self)

    def pay_order(self):
        event = EventFactory.create_event("OrderPaid", order_id=self.id)
        event.apply(self)


# 5. CQRS — QUERY SIDE ---------------------------------------------
class OrderReadModel:
    def __init__(self):
        self.orders = {}

    def project(self, event):
        if isinstance(event, OrderCreated):
            self.orders[event.order_id] = {
                "type": event.customer_type,
                "amount": event.amount,
                "status": "CREATED"
            }
        elif isinstance(event, OrderPaid):
            self.orders[event.order_id]["status"] = "PAID"

`

Test de integración

order = OrderAggregate()
strategy = VIPDiscount()
order.create_order("vip", 200, strategy)
order.pay_order()

read_model = OrderReadModel()
for e in order.events:
    read_model.project(e)

print(read_model.orders)

⚙️ Escenario 2 — Decorator + Proxy + Adapter (Sistema de caché y logging para API externa)

Contexto

  • Adapter: conecta a un servicio HTTP externo.
  • Proxy: controla el acceso (cachea y limita).
  • Decorator: añade logging sin modificar el código base.

Código (TypeScript)

// 1. ADAPTER -------------------------------------------------------
interface HttpService {
  get(url: string): Promise<any>;
}

class ExternalApiAdapter implements HttpService {
  async get(url: string) {
    console.log("Real HTTP request:", url);
    return { data: "Response from " + url };
  }
}


// 2. PROXY ---------------------------------------------------------
class CachedApiProxy implements HttpService {
  private cache = new Map<string, any>();

  constructor(private api: HttpService) {}

  async get(url: string) {
    if (this.cache.has(url)) {
      console.log("Returning from cache:", url);
      return this.cache.get(url);
    }
    const res = await this.api.get(url);
    this.cache.set(url, res);
    return res;
  }
}


// 3. DECORATOR -----------------------------------------------------
class LoggingDecorator implements HttpService {
  constructor(private api: HttpService) {}

  async get(url: string) {
    console.log(`[LOG] Fetching: ${url}`);
    const res = await this.api.get(url);
    console.log(`[LOG] Done: ${url}`);
    return res;
  }
}

Test realista

const baseApi = new ExternalApiAdapter();
const cachedApi = new CachedApiProxy(baseApi);
const loggedApi = new LoggingDecorator(cachedApi);

(async () => {
  await loggedApi.get("https://service/api/users");
  await loggedApi.get("https://service/api/users"); // viene del cache
})();

🧩 Escenario 3 — MVVM + Observer + Command (Aplicación de tareas Reactiva)

Contexto

  • MVVM: separa lógica (ViewModel), vista y modelo.
  • Observer: sincroniza la vista con el modelo.
  • Command: encapsula acciones (añadir tarea, marcar completada).

Código (TypeScript)

// 1. OBSERVER ------------------------------------------------------
type ObserverFn = () => void;

class Observable<T> {
  private observers: ObserverFn[] = [];
  constructor(public value: T) {}

  subscribe(fn: ObserverFn) { this.observers.push(fn); }
  set(newValue: T) {
    this.value = newValue;
    this.observers.forEach(fn => fn());
  }
}


// 2. MODEL ----------------------------------------------------------
interface Task {
  id: number;
  title: string;
  done: boolean;
}


// 3. VIEWMODEL ------------------------------------------------------
class TaskViewModel {
  tasks = new Observable<Task[]>([]);
  private nextId = 1;

  addTask(title: string) {
    const newTask = { id: this.nextId++, title, done: false };
    this.tasks.set([...this.tasks.value, newTask]);
  }

  toggleTask(id: number) {
    this.tasks.set(
      this.tasks.value.map(t =>
        t.id === id ? { ...t, done: !t.done } : t
      )
    );
  }
}


// 4. COMMANDS -------------------------------------------------------
class AddTaskCommand {
  constructor(private vm: TaskViewModel, private title: string) {}
  execute() { this.vm.addTask(this.title); }
}

class ToggleTaskCommand {
  constructor(private vm: TaskViewModel, private id: number) {}
  execute() { this.vm.toggleTask(this.id); }
}

Test realista (simulación)

const vm = new TaskViewModel();
vm.tasks.subscribe(() => console.log("UI Update:", vm.tasks.value));

const add1 = new AddTaskCommand(vm, "Aprender patrones de diseño");
add1.execute();

const add2 = new AddTaskCommand(vm, "Implementar MVVM");
add2.execute();

const toggle = new ToggleTaskCommand(vm, 1);
toggle.execute();

🔁 Escenario 4 — Saga + Circuit Breaker + Repository (Orquestación distribuida)

Contexto

Simula un flujo de reserva de viaje:

  • Saga: coordina pasos distribuidos (vuelo, hotel, pago).
  • Circuit Breaker: evita repetir llamadas fallidas.
  • Repository: persiste el estado de las sagas.

Código (Python)

import random, time

# 1. CIRCUIT BREAKER -----------------------------------------------
class CircuitBreaker:
    def __init__(self, failure_threshold=3, reset_time=5):
        self.failure_count = 0
        self.failure_threshold = failure_threshold
        self.reset_time = reset_time
        self.last_failure = 0
        self.state = "CLOSED"

    def call(self, func, *args, **kwargs):
        if self.state == "OPEN" and time.time() - self.last_failure < self.reset_time:
            raise Exception("Circuit is open — skipping call")
        try:
            result = func(*args, **kwargs)
            self.failure_count = 0
            self.state = "CLOSED"
            return result
        except Exception as e:
            self.failure_count += 1
            self.last_failure = time.time()
            if self.failure_count >= self.failure_threshold:
                self.state = "OPEN"
            raise e


# 2. REPOSITORY -----------------------------------------------------
class SagaRepository:
    def __init__(self):
        self.data = {}

    def save(self, saga_id, state):
        self.data[saga_id] = state

    def get(self, saga_id):
        return self.data.get(saga_id, {})

# 3. SAGA ------------------------------------------------------------
class TravelBookingSaga:
    def __init__(self, repo, circuit):
        self.repo = repo
        self.circuit = circuit
        self.id = str(uuid.uuid4())
        self.state = "STARTED"

    def book_flight(self):
        if random.random() < 0.2:
            raise Exception("Flight booking failed")
        return "FLIGHT_OK"

    def book_hotel(self):
        if random.random() < 0.3:
            raise Exception("Hotel booking failed")
        return "HOTEL_OK"

    def charge_payment(self):
        if random.random() < 0.2:
            raise Exception("Payment failed")
        return "PAYMENT_OK"

    def execute(self):
        try:
            flight = self.circuit.call(self.book_flight)
            hotel = self.circuit.call(self.book_hotel)
            payment = self.circuit.call(self.charge_payment)
            self.state = "COMPLETED"
        except Exception as e:
            self.state = "FAILED"
            print("Rollback triggered:", e)
        finally:
            self.repo.save(self.id, {"state": self.state})

Test realista

repo = SagaRepository()
circuit = CircuitBreaker(failure_threshold=2, reset_time=3)

for i in range(5):
    saga = TravelBookingSaga(repo, circuit)
    saga.execute()
    print(f"Saga {saga.id}{saga.state}")

🧩 Notas adicionales

  • Cada escenario representa un caso real de integración de patrones en arquitecturas complejas.
  • Se pueden combinar aún más capas:

    • CQRS + Event Sourcing + Saga
    • Circuit Breaker + Retry Pattern
    • Repository + Unit of Work + Specification

🏢 Enterprise Patterns – Integraciones Distribuidas

  • Arquitectura de Software
  • Patrones de diseño
  • Microservicios
  • Event Driven Architecture
  • CQRS
  • Saga Pattern
  • Repository Pattern
  • Circuit Breaker Pattern
  • Adapter Pattern
  • API Gateway
  • Event Bus
  • Message Broker
  • Domain Driven Design

🌐 Escenario 1 — API Gateway + Proxy + Adapter (Orquestación de microservicios REST)

Contexto

Un API Gateway centraliza las peticiones desde el cliente y delega las llamadas a distintos microservicios.

  • Proxy Pattern: intercepta y redirige solicitudes.
  • Adapter Pattern: adapta respuestas heterogéneas.
  • Facade Pattern: expone una interfaz única y consistente al cliente.

Código (TypeScript)

// Simulación de microservicios
class UserService {
  async getUser(id: string) {
    return { id, name: "Eduardo", email: "edu@domain.com" };
  }
}

class OrdersService {
  async getOrders(userId: string) {
    return [
      { id: 1, total: 100 },
      { id: 2, total: 250 }
    ];
  }
}


// ADAPTER: Unifica formato de respuesta
class ResponseAdapter {
  adapt(user: any, orders: any[]) {
    return {
      userId: user.id,
      name: user.name,
      email: user.email,
      ordersCount: orders.length,
      totalSpent: orders.reduce((acc, o) => acc + o.total, 0)
    };
  }
}


// PROXY + FACADE (Gateway)
class ApiGateway {
  private userService = new UserService();
  private ordersService = new OrdersService();
  private adapter = new ResponseAdapter();

  async handleRequest(userId: string) {
    const [user, orders] = await Promise.all([
      this.userService.getUser(userId),
      this.ordersService.getOrders(userId)
    ]);

    return this.adapter.adapt(user, orders);
  }
}

`

Test realista

(async () => {
  const gateway = new ApiGateway();
  console.log(await gateway.handleRequest("user-123"));
})();

🧩 Escenario 2 — CQRS + Event Bus + Saga Orchestrator

Contexto

Una arquitectura distribuida en la que:

  • CQRS separa los modelos de lectura y escritura.
  • Event Bus publica y escucha eventos entre microservicios.
  • Saga coordina la ejecución distribuida de pedidos.

Código (Python)

import uuid

# EVENT BUS --------------------------------------------------------
class EventBus:
    def __init__(self):
        self.subscribers = {}

    def subscribe(self, event_type, handler):
        self.subscribers.setdefault(event_type, []).append(handler)

    def publish(self, event_type, data):
        for handler in self.subscribers.get(event_type, []):
            handler(data)


# COMMAND SIDE ------------------------------------------------------
class OrderCommandService:
    def __init__(self, event_bus):
        self.event_bus = event_bus

    def create_order(self, customer_id, amount):
        order_id = str(uuid.uuid4())
        print(f"[Command] Order created {order_id}")
        self.event_bus.publish("OrderCreated", {"id": order_id, "amount": amount})
        return order_id


# QUERY SIDE --------------------------------------------------------
class OrderReadModel:
    def __init__(self):
        self.data = {}

    def project_order_created(self, event):
        self.data[event["id"]] = {"amount": event["amount"], "status": "PENDING"}

    def project_payment_done(self, event):
        self.data[event["id"]]["status"] = "PAID"


# SAGA ORCHESTRATOR -------------------------------------------------
class PaymentService:
    def __init__(self, event_bus):
        self.event_bus = event_bus

    def handle_order_created(self, event):
        print("[Saga] Processing payment for order:", event["id"])
        self.event_bus.publish("PaymentDone", event)


# REGISTRO DE EVENTOS -----------------------------------------------
event_bus = EventBus()
read_model = OrderReadModel()
payment_service = PaymentService(event_bus)

event_bus.subscribe("OrderCreated", payment_service.handle_order_created)
event_bus.subscribe("OrderCreated", read_model.project_order_created)
event_bus.subscribe("PaymentDone", read_model.project_payment_done)

Test de flujo completo

command_service = OrderCommandService(event_bus)
order_id = command_service.create_order("cust-001", 300)

print("[READ MODEL]", read_model.data)

🧱 Escenario 3 — Repository + Unit of Work + Circuit Breaker + Retry

Contexto

Un servicio que guarda pedidos en base de datos remota.

  • Repository Pattern: abstrae el acceso a datos.
  • Unit of Work: agrupa transacciones.
  • Circuit Breaker + Retry: manejan errores de red con resiliencia.

Código (Python)

import time, random

# CIRCUIT BREAKER --------------------------------------------------
class CircuitBreaker:
    def __init__(self, failure_threshold=3, reset_timeout=5):
        self.failures = 0
        self.failure_threshold = failure_threshold
        self.reset_timeout = reset_timeout
        self.last_failure = 0
        self.open = False

    def call(self, func, *args, **kwargs):
        if self.open and (time.time() - self.last_failure < self.reset_timeout):
            raise Exception("Circuit is OPEN — skipping call")
        try:
            result = func(*args, **kwargs)
            self.failures = 0
            self.open = False
            return result
        except Exception as e:
            self.failures += 1
            self.last_failure = time.time()
            if self.failures >= self.failure_threshold:
                self.open = True
            raise e


# REPOSITORY --------------------------------------------------------
class OrderRepository:
    def __init__(self):
        self._store = {}

    def save(self, order):
        if random.random() < 0.3:
            raise Exception("DB connection failed")
        self._store[order["id"]] = order
        print("[Repo] Order saved:", order)
        return True


# UNIT OF WORK ------------------------------------------------------
class UnitOfWork:
    def __init__(self, repo, circuit):
        self.repo = repo
        self.circuit = circuit
        self.pending = []

    def register(self, order):
        self.pending.append(order)

    def commit(self):
        for order in self.pending:
            retries = 3
            while retries:
                try:
                    self.circuit.call(self.repo.save, order)
                    break
                except Exception as e:
                    retries -= 1
                    print("Retrying...", retries, e)
            else:
                print("Failed permanently:", order["id"])
        self.pending.clear()

Test realista

repo = OrderRepository()
breaker = CircuitBreaker()
uow = UnitOfWork(repo, breaker)

for i in range(5):
    order = {"id": str(uuid.uuid4()), "amount": random.randint(50, 300)}
    uow.register(order)

uow.commit()

🧠 Escenario 4 — Event Store + CQRS + Kafka Mock

Contexto

Un sistema event-driven donde:

  • Todos los cambios se registran en un Event Store.
  • Un Event Consumer replica estados para queries (CQRS).
  • Se simula un Kafka broker básico para comunicación asíncrona.

Código (Python)

from collections import defaultdict

# KAFKA MOCK --------------------------------------------------------
class KafkaBroker:
    def __init__(self):
        self.topics = defaultdict(list)

    def publish(self, topic, event):
        for handler in self.topics[topic]:
            handler(event)

    def subscribe(self, topic, handler):
        self.topics[topic].append(handler)


# EVENT STORE -------------------------------------------------------
class EventStore:
    def __init__(self):
        self.events = []

    def append(self, event):
        print("[EventStore] Recorded event:", event)
        self.events.append(event)


# COMMAND SIDE ------------------------------------------------------
class ProductCommandService:
    def __init__(self, store, broker):
        self.store = store
        self.broker = broker

    def create_product(self, name, price):
        event = {"type": "ProductCreated", "name": name, "price": price}
        self.store.append(event)
        self.broker.publish("products", event)


# QUERY SIDE --------------------------------------------------------
class ProductQueryModel:
    def __init__(self):
        self.products = []

    def on_product_created(self, event):
        self.products.append({"name": event["name"], "price": event["price"]})

Test de integración

broker = KafkaBroker()
store = EventStore()
query = ProductQueryModel()
command = ProductCommandService(store, broker)

broker.subscribe("products", query.on_product_created)

command.create_product("Laptop", 1200)
command.create_product("Tablet", 700)

print("Productos (Query Model):", query.products)

🧩 Extensiones sugeridas

  • Implementar un Event Sourcing real con snapshots
  • Incluir un Message Bus persistente (RabbitMQ, NATS o Redis Streams)
  • Agregar patrones de resiliencia: Retry, Timeout, Fallback
  • Simular integración entre microservicios con AsyncAPI

📚 Referencias útiles

  • Patrones de Microservicios - Chris Richardson
  • Enterprise Integration Patterns (Hohpe & Woolf)
  • Event Driven Architecture
  • Domain Driven Design
  • CQRS y Event Sourcing - Greg Young

Distributed Transactions & Message Patterns — Tests distribuidos (incompleta?)

  • Microservicios
  • Saga Pattern
  • Event Driven Architecture
  • Distributed Transactions
  • Outbox Pattern
  • Dead Letter Queue

Objetivo

Nota con tests distribuidos que simulan comunicación entre microservicios mediante colas internas (mock), implementando sagas, compensaciones, outbox, idempotencia, dead-letter queues y retries. Todo ejecutable con pytest + pytest-asyncio (simulado con asyncio).

Estructura de la nota

  • Escenario A — Orquestación de Saga (Orchestrator)
  • Escenario B — Coreografía de Saga (Choreography)
  • Escenario C — Outbox + Poller (garantía de publicación atomizada)
  • Escenario D — Dead Letter Queue y Retries

Cada escenario incluye:

  • Implementación de componentes (broker mock, repositorios en memoria)
  • Servicios simulados (Order, Payment, Inventory, Notification)
  • Tests de integración usando pytest-asyncio

Dependencias de prueba

  • Python 3.8+
  • pytest
  • pytest-asyncio

Instalación sugerida (virtualenv):

pip install pytest pytest-asyncio

Escenario A — Orquestación de Saga (Orchestrator)

Descripción

Un OrderService crea un pedido y pide al Saga Orchestrator que coordine:

  • Reservar inventario (InventoryService)
  • Cobrar al cliente (PaymentService)
  • Confirmar pedido y notificar (NotificationService)

Si cualquiera de los pasos falla, el Orchestrator ejecuta compensaciones en orden inverso (p.ej. liberar inventario, reembolsar).

Código (tests/escenario_a.py)

import asyncio
import uuid
import pytest

# Mock Message Broker (async)
class AsyncEventBus:
	def __init__(self):
		self.subscribers = {}

	async def publish(self, topic, event):
		for handler in list(self.subscribers.get(topic, [])):
			# schedule handlers concurrently but don't wait here
			asyncio.create_task(handler(event))

	def subscribe(self, topic, handler):
		self.subscribers.setdefault(topic, []).append(handler)


# In-memory saga repository
class SagaRepository:
	def __init__(self):
		self.store = {}

	def save(self, saga_id, state):
		self.store[saga_id] = state

	def get(self, saga_id):
		return self.store.get(saga_id)


# Services
class InventoryService:
	async def reserve(self, order_id, sku, qty):
		# Simulate latency
		await asyncio.sleep(0.05)
		if sku == "SKU-FAIL":
			raise Exception("Inventory reservation failed")
		return {"status": "reserved"}

	async def release(self, order_id, sku, qty):
		await asyncio.sleep(0.02)
		return {"status": "released"}


class PaymentService:
	async def charge(self, order_id, amount):
		await asyncio.sleep(0.05)
		if amount > 1000:
			raise Exception("Payment declined")
		return {"status": "charged"}

	async def refund(self, order_id, amount):
		await asyncio.sleep(0.02)
		return {"status": "refunded"}


class NotificationService:
	async def notify(self, user_id, message):
		await asyncio.sleep(0.01)
		return True


# Orchestrator
class OrderOrchestrator:
	def __init__(self, bus, repo, inventory, payment, notification):
		self.bus = bus
		self.repo = repo
		self.inventory = inventory
		self.payment = payment
		self.notification = notification

	async def start(self, order):
		saga_id = str(uuid.uuid4())
		self.repo.save(saga_id, {"state": "STARTED", "order": order})
		try:
			# Step 1: reserve inventory
			await self.inventory.reserve(order["id"], order["sku"], order["qty"])
			self.repo.save(saga_id, {"state": "INVENTORY_RESERVED"})

			# Step 2: charge payment
			await self.payment.charge(order["id"], order["amount"])
			self.repo.save(saga_id, {"state": "PAYMENT_CHARGED"})

			# Step 3: confirm
			self.repo.save(saga_id, {"state": "COMPLETED"})
			await self.notification.notify(order["user_id"], "Order completed")
			return {"saga_id": saga_id, "status": "COMPLETED"}
		except Exception as e:
			# Compensation in reverse order
			state = self.repo.get(saga_id)
			if state and state.get("state") == "PAYMENT_CHARGED":
				await self.payment.refund(order["id"], order["amount"])
			if state and state.get("state") in ("PAYMENT_CHARGED", "INVENTORY_RESERVED"):
				await self.inventory.release(order["id"], order["sku"], order["qty"])
			self.repo.save(saga_id, {"state": "COMPENSATED", "error": str(e)})
			await self.notification.notify(order["user_id"], f"Order failed: {e}")
			return {"saga_id": saga_id, "status": "FAILED", "error": str(e)}


# Tests
@pytest.mark.asyncio
async def test_orchestrator_success():
	bus = AsyncEventBus()
	repo = SagaRepository()
	inv = InventoryService()
	pay = PaymentService()
	notif = NotificationService()
	orch = OrderOrchestrator(bus, repo, inv, pay, notif)

	order = {"id": "ORD-1", "user_id": "U1", "sku": "SKU-1", "qty": 1, "amount": 100}
	res = await orch.start(order)
	assert res["status"] == "COMPLETED"
	assert repo.get(res["saga_id"])  # persisted state


@pytest.mark.asyncio
async def test_orchestrator_inventory_failure_triggers_compensation():
	bus = AsyncEventBus()
	repo = SagaRepository()
	inv = InventoryService()
	pay = PaymentService()
	notif = NotificationService()
	orch = OrderOrchestrator(bus, repo, inv, pay, notif)

	order = {"id": "ORD-2", "user_id": "U2", "sku": "SKU-FAIL", "qty": 1, "amount": 50}
	res = await orch.start(order)
	assert res["status"] == "FAILED"
	state = repo.get(res["saga_id"])
	assert state["state"] == "COMPENSATED"

Escenario B — Coreografía de Saga (Choreography)

Descripción

En lugar de un Orchestrator central, cada servicio reacciona a eventos y publica nuevos eventos. El flujo emerge de la interacción (choreography). Incluye idempotencia y deduplicación simple.

Código (tests/escenario_b.py)

import asyncio
import uuid
import pytest

class AsyncBroker:
	def __init__(self):
		self.topics = {}

	async def publish(self, topic, event):
		for h in list(self.topics.get(topic, [])):
			asyncio.create_task(h(event))

	def subscribe(self, topic, handler):
		self.topics.setdefault(topic, []).append(handler)


# Simple dedup store
class DedupStore:
	def __init__(self):
		self.processed = set()

	def seen(self, id):
		if id in self.processed:
			return True
		self.processed.add(id)
		return False


# Services reacting to events
class OrderService:
	def __init__(self, broker):
		self.broker = broker

	async def create_order(self, payload):
		# Persist order locally (omitted) and emit event
		await self.broker.publish("OrderCreated", payload)


class InventoryService:
	def __init__(self, broker, dedup):
		self.broker = broker
		self.dedup = dedup
		self.broker.subscribe("OrderCreated", self.on_order_created)

	async def on_order_created(self, event):
		if self.dedup.seen(event["id"]):
			return
		# reserve
		if event.get("sku") == "SKU-FAIL":
			await self.broker.publish("InventoryFailed", event)
		else:
			await self.broker.publish("InventoryReserved", event)


class PaymentService:
	def __init__(self, broker, dedup):
		self.broker = broker
		self.dedup = dedup
		self.broker.subscribe("InventoryReserved", self.on_inventory_reserved)

	async def on_inventory_reserved(self, event):
		if self.dedup.seen(event["id"]):
			return
		if event.get("amount", 0) > 1000:
			await self.broker.publish("PaymentFailed", event)
		else:
			await self.broker.publish("PaymentDone", event)


class NotificationService:
	def __init__(self, broker):
		self.broker = broker
		self.events = []
		self.broker.subscribe("PaymentDone", self.on_payment_done)
		self.broker.subscribe("PaymentFailed", self.on_payment_failed)

	async def on_payment_done(self, event):
		self.events.append((event["id"], "COMPLETED"))

	async def on_payment_failed(self, event):
		self.events.append((event["id"], "FAILED"))


# Tests
@pytest.mark.asyncio
async def test_choreography_success():
	broker = AsyncBroker()

dedup = DedupStore()
	order_svc = OrderService(broker)
	inv = InventoryService(broker, dedup)
	pay = PaymentService(broker, dedup)
	notif = NotificationService(broker)

	order = {"id": "C-1", "sku": "SKU-1", "amount": 100}
	await order_svc.create_order(order)
	# small sleep to let tasks run
	await asyncio.sleep(0.2)
	assert ("C-1", "COMPLETED") in notif.events


@pytest.mark.asyncio
async def test_choreography_inventory_failure():
	broker = AsyncBroker()

dedup = DedupStore()
	order_svc = OrderService(broker)
	inv = InventoryService(broker, dedup)
	pay = PaymentService(broker, dedup)
	notif = NotificationService(broker)

	order = {"id": "C-2", "sku": "SKU-FAIL", "amount": 50}
	await order_svc.create_order(order)
	await asyncio.sleep(0.2)
	assert ("C-2", "FAILED") in notif.events

`

Escenario C — Outbox Pattern + Poller (garantía atómica entre DB y broker)

Descripción

El Outbox Pattern asegura que la escritura de estado y la emisión de eventos se comporten de forma atómica: la aplicación escribe el estado y el evento en la misma transacción (outbox table). Un poller lee la tabla outbox y publica los eventos al broker.

Código (tests/escenario_c.py)

import asyncio
import uuid
import pytest

# Simulated DB + Outbox
class InMemoryDB:
	def __init__(self):
		self.orders = {}
		self.outbox = []

	def save_order_and_outbox(self, order, event):
		# atomic by design in this mock
		self.orders[order["id"]] = order
		self.outbox.append(event)

	def fetch_outbox(self):
		items = list(self.outbox)
		return items

	def remove_outbox(self, event):
		self.outbox.remove(event)


class Poller:
	def __init__(self, db, broker):
		self.db = db
		self.broker = broker
		self.running = False

	async def run_once(self):
		items = self.db.fetch_outbox()
		for e in items:
			await self.broker.publish(e["topic"], e["payload"])
			self.db.remove_outbox(e)


class OrderServiceOutbox:
	def __init__(self, db):
		self.db = db

	def create_order(self, user_id, sku, amount):
		order = {"id": str(uuid.uuid4()), "user": user_id, "sku": sku, "amount": amount}
		event = {"topic": "OrderCreated", "payload": order}
		self.db.save_order_and_outbox(order, event)
		return order


class FakeBroker:
	def __init__(self):
		self.received = []

	async def publish(self, topic, payload):
		self.received.append((topic, payload))


# Tests
@pytest.mark.asyncio
async def test_outbox_poller_publishes():
	db = InMemoryDB()
	broker = FakeBroker()
	service = OrderServiceOutbox(db)
	poller = Poller(db, broker)

	order = service.create_order("U1", "SKU-1", 120)
	# Nothing published yet
	assert broker.received == []

	# Run poller
	await poller.run_once()
	assert broker.received[0][0] == "OrderCreated"
	assert broker.received[0][1]["id"] == order["id"]
	# Outbox emptied
	assert db.fetch_outbox() == []

`

Escenario D — Dead Letter Queue (DLQ) y Retries

Descripción

Mensajes que fallan repetidamente acaban en una DLQ para inspección manual o re-procesado con lógica específica. Implementamos un broker que soporta reintentos configurables y DLQ.

Código (tests/escenario_d.py)

import asyncio
import pytest

class BrokerWithDLQ:
	def __init__(self, retries=3):
		self.handlers = {}
		self.retries = retries
		self.dlq = []

	async def publish(self, topic, event):
		for h in list(self.handlers.get(topic, [])):
			asyncio.create_task(self._invoke_with_retries(h, event))

	async def _invoke_with_retries(self, handler, event):
		attempts = 0
		while attempts < self.retries:
			try:
				await handler(event)
				return
			except Exception:
				attempts += 1
				await asyncio.sleep(0.01)
		# push to DLQ
		self.dlq.append((handler, event))

	def subscribe(self, topic, handler):
		self.handlers.setdefault(topic, []).append(handler)


# Handler that fails deterministically
async def flaky_handler(event):
	raise Exception("boom")


@pytest.mark.asyncio
async def test_dlq_after_retries():
	broker = BrokerWithDLQ(retries=2)
	broker.subscribe("T", flaky_handler)
	await broker.publish("T", {"x": 1})
	# wait for tasks
	await asyncio.sleep(0.1)
	assert len(broker.dlq) == 1


# Handler that succeeds on second attempt (idempotent)
state = {"count": 0}
async def sometimes_success(event):
	state["count"] += 1
	if state["count"] < 2:
		raise Exception("temporary")
	return True


@pytest.mark.asyncio
async def test_retry_succeeds():
	broker = BrokerWithDLQ(retries=3)
	broker.subscribe("S", sometimes_success)
	await broker.publish("S", {"x": 1})
	await asyncio.sleep(0.1)
	assert len(broker.dlq) == 0

`

Buenas prácticas incluidas en los tests

  • Idempotencia: deduplicadores y keys en eventos.
  • Outbox Pattern para evitar pérdidas entre DB y Broker.
  • Retries con backoff (simplificado) y DLQ.
  • Compensaciones reversibles para sagas orquestadas.
  • Pruebas asíncronas con pytest-asyncio para simular concurrencia real.

Extensiones sugeridas

  • Añadir simulación de latencias de red y particionamientos (chaos testing).
  • Integrar con brokers reales (RabbitMQ, Kafka) y usar Docker Compose para pruebas de integración.
  • Añadir métricas y observabilidad (traces, logs estructurados, prometheus mock).
  • Generar diagramas de secuencia para cada test utilizando PlantUML.

Referencias

  • Enterprise Integration Patterns (Hohpe & Woolf)
  • Saga Pattern
  • Outbox Pattern
  • Event Driven Architecture