Principios SOLID

Los principios SOLID son cinco principios de diseño orientado a objetos que buscan crear software más comprensible, flexible y mantenible.

  • SRP, principio de responsabilidad unica
  • OCP principio open close
  • LSP, Liskov substitution principle
  • ISP Interface segregation principle
  • DIP Dependency inyection principle (Acoplamiento)
  • Computer Science
  • CLEAN
  • Computer Science CLEAN SOLID- los 5 principios que te ayudarán a desarrollar software de calidad

SRP - Principio de Responsabilidad Única

Una clase debe tener una sola razón para cambiar, meaning it should have only one job or responsibility.

  • Beneficios: Código más fácil de entender, mantener y probar
  • Ejemplo: Separar una clase que maneja datos y también genera reportes en dos clases distintas
  • Consecuencias de violarlo: Cambios en una funcionalidad afectan múltiples partes del sistema
  • Detección: Una clase con muchos métodos no relacionados o que cambia por diferentes razones

OCP - Principio Abierto/Cerrado

Las entidades de software deben estar abiertas para extensión pero cerradas para modificación.

  • Extensión sin modificación: Añadir nuevo comportamiento mediante herencia, composición o interfaces
  • Patrones aplicables: Strategy, Decorator, Factory
  • Ventajas: Reduce el riesgo de introducir bugs en código existente
  • Ejemplo: Usar interfaces para permitir nuevos tipos de pago sin modificar la clase principal de procesamiento

LSP - Principio de Sustitución de Liskov

Los objetos de un programa deberían ser reemplazables por instancias de sus subtipos sin alterar el correcto funcionamiento del programa.

  • Relación “es-un”: Debe ser semanticamente correcta
  • Precondiciones y postcondiciones: Las subclases no deben fortalecer precondiciones ni debilitar postcondiciones
  • Violaciones comunes:
    • Lanzar excepciones inesperadas
    • Devolver tipos de datos diferentes
    • Tener requisitos de inicialización diferentes
  • Ejemplo: Si Pato hereda de Ave, debe poder usar todos los métodos de Ave correctamente

ISP - Principio de Segregación de Interfaces

Ningún cliente debería verse forzado a depender de métodos que no usa.

  • Interfaces específicas: Mejor muchas interfaces específicas que una general
  • Detección de violaciones: Clientes que implementan métodos vacíos o lanzan “NotImplementedException”
  • Beneficios: Reduce el acoplamiento y las dependencias no necesarias
  • Ejemplo: Separar una interfaz IMultifuncional en IImpresora, IEscáner, IFax

DIP - Principio de Inversión de Dependencias

Depende de abstracciones, no de implementaciones concretas.

  • Módulos de alto nivel: No deben depender de módulos de bajo nivel
  • Abstracciones: No deben depender de detalles, los detalles deben depender de abstracciones
  • Patrones relacionados: Inyección de dependencias, Service Locator
  • Acoplamiento: Reduce el acoplamiento fuerte entre componentes
  • Implementación: Usar interfaces o clases abstractas como puntos de dependencia
  • Ventajas: Código más testeable, mantenible y flexible

Relación entre Principios

  • SRP y ISP: Ambos buscan la cohesión y separación de responsabilidades
  • DIP y OCP: Facilitan la extensibilidad del sistema
  • LSP: Garantiza que la herencia y polimorfismo funcionen correctamente
  • Conjunto: Crean arquitecturas desacopladas, mantenibles y escalables

Beneficios de Aplicar SOLID

  • Mantenibilidad: Código más fácil de modificar y extender
  • Testabilidad: Componentes aislados más fáciles de probar
  • Reusabilidad: Componentes con responsabilidades bien definidas
  • Reducción de acoplamiento: Dependencias claras y gestionadas
  • Resiliencia: Menos efectos colaterales al hacer cambios

Ejemplos Prácticos de Principios SOLID

SRP - Ejemplo Práctico

❌ Violación del SRP

class User {
  constructor(private name: string, private email: string) {}
  getUserInfo(): string {
    return `${this.name} <${this.email}>`;
  }
  saveToDatabase(): void {
    // Lógica para guardar en base de datos
    console.log(`Guardando usuario ${this.name} en la base de datos...`);
  }
  sendEmail(subject: string, message: string): void {
    // Lógica para enviar email
    console.log(`Enviando email a ${this.email}: ${subject}`);
  }
}

✅ Aplicando SRP

class User {
  constructor(private name: string, private email: string) {}
  getUserInfo(): string {
    return `${this.name} <${this.email}>`;
  }
}

class UserRepository {
  saveUser(user: User): void {
    console.log(`Guardando usuario en la base de datos...`);
  }
}

class EmailService {
  sendEmail(to: string, subject: string, message: string): void {
    console.log(`Enviando email a ${to}: ${subject}`);
  }
}

OCP - Ejemplo Práctico

❌ Violación del OCP

class AreaCalculator {
  calculateArea(shape: any): number {
    if (shape.type === 'circle') {
      return Math.PI * shape.radius * shape.radius;
    } else if (shape.type === 'rectangle') {
      return shape.width * shape.height;
    }
    // Para añadir un triángulo, tendría que MODIFICAR esta clase
    throw new Error('Tipo de forma no soportada');
  }
}

✅ Aplicando OCP

interface Shape {
  area(): number;
}

class Circle implements Shape {
  constructor(private radius: number) {}
  area(): number {
    return Math.PI * this.radius * this.radius;
  }
}

class Rectangle implements Shape {
  constructor(private width: number, private height: number) {}
  area(): number {
    return this.width * this.height;
  }
}

class Triangle implements Shape {
  constructor(private base: number, private height: number) {}
  area(): number {
    return (this.base * this.height) / 2;
  }
}

class AreaCalculator {
  calculateArea(shape: Shape): number {
    return shape.area();
  }
}

LSP - Ejemplo Práctico

❌ Violación del LSP

class Bird {
  fly(): void {
    console.log("Volando...");
  }
}

class Duck extends Bird {
  // OK - los patos pueden volar
}

class Penguin extends Bird {
  fly(): void {
    throw new Error("Los pingüinos no pueden volar!");
  }
}

// Uso que viola LSP
function makeBirdFly(bird: Bird): void {
  bird.fly(); // ❌ Esto fallará con Penguin
}

✅ Aplicando LSP

class Bird {
  // Comportamiento común a todas las aves
}

interface FlyingBird {
  fly(): void;
}

class Duck extends Bird implements FlyingBird {
  fly(): void {
    console.log("Pato volando...");
  }
  swim(): void {
    console.log("Pato nadando...");
  }
}

class Penguin extends Bird {
  swim(): void {
    console.log("Pingüino nadando...");
  }
}

// Uso correcto
function makeBirdFly(bird: FlyingBird): void {
  bird.fly(); // ✅ Solo acepta aves que pueden volar
}

ISP - Ejemplo Práctico

❌ Violación del ISP

interface Worker {
  work(): void;
  eat(): void;
  sleep(): void;
}

class Robot implements Worker {
  work(): void {
    console.log("Robot trabajando...");
  }
  eat(): void {
    throw new Error("Los robots no comen!");
  }
  sleep(): void {
    throw new Error("Los robots no duermen!");
  }
}

class Human implements Worker {
  work(): void {
    console.log("Humano trabajando...");
  }
  eat(): void {
    console.log("Humano comiendo...");
  }
  sleep(): void {
    console.log("Humano durmiendo...");
  }
}

✅ Aplicando ISP

interface Workable {
  work(): void;
}

interface Eatable {
  eat(): void;
}

interface Sleepable {
  sleep(): void;
}

class Robot implements Workable {
  work(): void {
    console.log("Robot trabajando...");
  }
}

class Human implements Workable, Eatable, Sleepable {
  work(): void {
    console.log("Humano trabajando...");
  }
  eat(): void {
    console.log("Humano comiendo...");
  }
  sleep(): void {
    console.log("Humano durmiendo...");
  }
}

DIP - Ejemplo Práctico

❌ Violación del DIP

class MySQLDatabase {
  connect(): void {
    console.log("Conectando a MySQL...");
  }
  query(sql: string): any {
    console.log(`Ejecutando query en MySQL: ${sql}`);
    return { results: [] };
  }
}

class UserService {
  private database: MySQLDatabase;
  constructor() {
    this.database = new MySQLDatabase(); // ❌ Dependencia concreta
    this.database.connect();
  }
  getUsers(): any {
    return this.database.query("SELECT * FROM users");
  }
}

✅ Aplicando DIP

interface Database {
  connect(): void;
  query(sql: string): any;
}

class MySQLDatabase implements Database {
  connect(): void {
    console.log("Conectando a MySQL...");
  }
  query(sql: string): any {
    console.log(`Ejecutando query en MySQL: ${sql}`);
    return { results: [] };
  }
}

class PostgreSQLDatabase implements Database {
  connect(): void {
    console.log("Conectando a PostgreSQL...");
  }
  query(sql: string): any {
    console.log(`Ejecutando query en PostgreSQL: ${sql}`);
    return { results: [] };
  }
}

class UserService {
  constructor(private database: Database) { // ✅ Dependencia de abstracción
    this.database.connect();
  }
  getUsers(): any {
    return this.database.query("SELECT * FROM users");
  }
}

// Uso
const mySQLDb = new MySQLDatabase();
const userService = new UserService(mySQLDb);

const postgreSQLDb = new PostgreSQLDatabase();
const userService2 = new UserService(postgreSQLDb);

Ejemplo Integrado - Sistema de Notificaciones

❌ Sin SOLID

class NotificationService {
  sendNotification(user: any, message: string, type: string): void {
    if (type === 'email') {
      // Lógica compleja para enviar email
      console.log(`Enviando email a ${user.email}: ${message}`);
    } else if (type === 'sms') {
      // Lógica compleja para enviar SMS
      console.log(`Enviando SMS a ${user.phone}: ${message}`);
    } else if (type === 'push') {
      // Lógica compleja para notificación push
      console.log(`Enviando push a ${user.deviceId}: ${message}`);
    }
    // Registrar en base de datos
    console.log(`Registrando notificación en BD...`);
  }
}

✅ Con SOLID Aplicado

// SRP + ISP: Interfaces segregadas
interface NotificationChannel {
  send(to: string, message: string): void;
}

interface NotificationRepository {
  save(notification: any): void;
}

// OCP: Fácil de extender
class EmailChannel implements NotificationChannel {
  send(to: string, message: string): void {
    console.log(`Enviando email a ${to}: ${message}`);
  }
}

class SMSChannel implements NotificationChannel {
  send(to: string, message: string): void {
    console.log(`Enviando SMS a ${to}: ${message}`);
  }
}

class PushChannel implements NotificationChannel {
  send(to: string, message: string): void {
    console.log(`Enviando push a ${to}: ${message}`);
  }
}

// DIP: Depende de abstracciones
class NotificationService {
  constructor(
    private channels: NotificationChannel[],
    private repository: NotificationRepository
  ) {}
  sendNotification(user: any, message: string): void {
    this.channels.forEach(channel => {
      channel.send(user.contact, message);
    });
    this.repository.save({
      userId: user.id,
      message: message,
      timestamp: new Date()
    });
  }
}

// Uso
const emailChannel = new EmailChannel();
const smsChannel = new SMSChannel();
const repository = new DatabaseNotificationRepository();

const notificationService = new NotificationService(
  [emailChannel, smsChannel],
  repository
);

Patrones de Detección de Violaciones

Señales de que necesitas aplicar SOLID:

SRP:

  • Una clase cambia por múltiples razones
  • Tiene muchos métodos no relacionados
  • Es difícil de testear

OCP:

  • Modificas código existente para añadir funcionalidad
  • Muchos if/else o switch para tipos

LSP:

  • instanceof checks frecuentes
  • Métodos que lanzan “NotImplemented”
  • Subclases que no usan todos los métodos padre

ISP:

  • Interfaces con muchos métodos
  • Clases que implementan métodos vacíos
  • Dependencias forzadas

DIP:

  • new keyword en constructores de servicios
  • Dificultad para hacer testing
  • Acoplamiento fuerte entre módulos

Patrones de Diseño que Complementan SOLID

Patrones Creacionales

Factory Method

interface PaymentProcessor {
    process(amount: number): void;
}

class CreditCardProcessor implements PaymentProcessor {
    process(amount: number): void {
        console.log(`Processing credit card payment: $${amount}`);
    }
}

class PayPalProcessor implements PaymentProcessor {
    process(amount: number): void {
        console.log(`Processing PayPal payment: $${amount}`);
    }
}

abstract class PaymentProcessorFactory {
    abstract createProcessor(): PaymentProcessor;
    processPayment(amount: number): void {
        const processor = this.createProcessor();
        processor.process(amount);
    }
}

class CreditCardFactory extends PaymentProcessorFactory {
    createProcessor(): PaymentProcessor {
        return new CreditCardProcessor();
    }
}

Builder Pattern

class QueryBuilder {
    private table: string = '';
    private fields: string[] = [];
    private conditions: string[] = [];
    select(fields: string[]): QueryBuilder {
        this.fields = fields;
        return this;
    }
    from(table: string): QueryBuilder {
        this.table = table;
        return this;
    }
    where(condition: string): QueryBuilder {
        this.conditions.push(condition);
        return this;
    }
    build(): string {
        return `SELECT ${this.fields.join(', ')} FROM ${this.table} WHERE ${this.conditions.join(' AND ')}`;
    }
}

Patrones Estructurales

Adapter Pattern

// Servicio legacy que no sigue nuestra interfaz
class LegacyWeatherService {
    fetchWeatherData(city: string): any {
        return { temperature: 72, condition: 'sunny' };
    }
}

// Interfaz moderna que queremos usar
interface WeatherService {
    getWeather(city: string): WeatherData;
}

interface WeatherData {
    temp: number;
    description: string;
}

// Adapter que convierte la interfaz legacy a la moderna
class WeatherServiceAdapter implements WeatherService {
    constructor(private legacyService: LegacyWeatherService) {}
    getWeather(city: string): WeatherData {
        const legacyData = this.legacyService.fetchWeatherData(city);
        return {
            temp: legacyData.temperature,
            description: legacyData.condition
        };
    }
}

Composite Pattern

interface FileSystemComponent {
    getName(): string;
    getSize(): number;
    display(indent: string): void;
}

class File implements FileSystemComponent {
    constructor(private name: string, private size: number) {}
    getName(): string { return this.name; }
    getSize(): number { return this.size; }
    display(indent: string): void {
        console.log(`${indent}File: ${this.name} (${this.size} bytes)`);
    }
}

class Directory implements FileSystemComponent {
    private children: FileSystemComponent[] = [];
    constructor(private name: string) {}
    getName(): string { return this.name; }
    getSize(): number {
        return this.children.reduce((total, child) => total + child.getSize(), 0);
    }
    add(component: FileSystemComponent): void {
        this.children.push(component);
    }
    display(indent: string): void {
        console.log(`${indent}Directory: ${this.name}`);
        this.children.forEach(child => child.display(indent + '  '));
    }
}

Patrones de Comportamiento

Strategy Pattern

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

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

class QuickSort implements SortingStrategy {
    sort(data: number[]): number[] {
        console.log("Sorting using quick sort");
        return this.quickSort([...data]);
    }
    private quickSort(data: number[]): number[] {
        if (data.length <= 1) return data;
        const pivot = data[0];
        const left = data.slice(1).filter(x => x <= pivot);
        const right = data.slice(1).filter(x => x > pivot);
        return [...this.quickSort(left), pivot, ...this.quickSort(right)];
    }
}

class Sorter {
    constructor(private strategy: SortingStrategy) {}
    setStrategy(strategy: SortingStrategy): void {
        this.strategy = strategy;
    }
    sort(data: number[]): number[] {
        return this.strategy.sort(data);
    }
}

Observer Pattern

interface Observer {
    update(data: any): void;
}

interface Subject {
    attach(observer: Observer): void;
    detach(observer: Observer): void;
    notify(): void;
}

class WeatherStation implements Subject {
    private observers: Observer[] = [];
    private temperature: number = 0;
    attach(observer: Observer): void {
        this.observers.push(observer);
    }
    detach(observer: Observer): void {
        const index = this.observers.indexOf(observer);
        if (index > -1) {
            this.observers.splice(index, 1);
        }
    }
    notify(): void {
        this.observers.forEach(observer => 
            observer.update(this.temperature)
        );
    }
    setTemperature(temp: number): void {
        this.temperature = temp;
        this.notify();
    }
}

class TemperatureDisplay implements Observer {
    update(temperature: number): void {
        console.log(`Temperature Display: ${temperature}°C`);
    }
}

class Logger implements Observer {
    update(temperature: number): void {
        console.log(`Logger: Temperature changed to ${temperature}°C`);
    }
}

Principios Relacionados con SOLID

Principio DRY (Don’t Repeat Yourself)

// ❌ Violación DRY
class UserValidator {
    validateEmail(email: string): boolean {
        const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
        return emailRegex.test(email);
    }
}

class NewsletterValidator {
    validateEmail(email: string): boolean {
        const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
        return emailRegex.test(email);
    }
}

// ✅ Aplicando DRY
class EmailValidator {
    static validate(email: string): boolean {
        const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
        return emailRegex.test(email);
    }
}

class UserValidator {
    validateEmail(email: string): boolean {
        return EmailValidator.validate(email);
    }
}

Principio KISS (Keep It Simple, Stupid)

// ❌ Complejo innecesariamente
class ComplexCalculator {
    calculate(input: number[]): number {
        return input.reduce((acc, curr, idx, arr) => {
            const weighted = curr * (idx + 1);
            const normalized = weighted / arr.length;
            return acc + Math.pow(normalized, 2);
        }, 0);
    }
}

// ✅ Simple y claro
class SimpleCalculator {
    calculateWeightedAverage(numbers: number[]): number {
        let total = 0;
        for (let i = 0; i < numbers.length; i++) {
            total += numbers[i] * (i + 1);
        }
        return total / numbers.length;
    }
}

Ley de Demeter (Principio del Menor Conocimiento)

// ❌ Violación de la Ley de Demeter
class Customer {
    constructor(private wallet: Wallet) {}
    getWallet(): Wallet {
        return this.wallet;
    }
}

class Wallet {
    constructor(private money: number) {}
    getMoney(): number {
        return this.money;
    }
}

// Uso problemático
const customer = new Customer(new Wallet(100));
const money = customer.getWallet().getMoney(); // ❌ Conoce demasiado

// ✅ Cumpliendo la Ley de Demeter
class Customer {
    constructor(private wallet: Wallet) {}
    getAvailableMoney(): number {
        return this.wallet.getMoney();
    }
    makePayment(amount: number): boolean {
        return this.wallet.withdraw(amount);
    }
}

// Uso correcto
const money = customer.getAvailableMoney(); // ✅ Solo conoce lo necesario

Arquitectura Hexagonal (Puertos y Adaptadores)

Estructura Básica

// Domain Core (sin dependencias externas)
interface UserRepository {
    findById(id: string): Promise<User>;
    save(user: User): Promise<void>;
}

class User {
    constructor(
        public id: string,
        public name: string,
        public email: string
    ) {}
    changeEmail(newEmail: string): void {
        // Lógica de dominio
        this.email = newEmail;
    }
}

// Application Layer
class ChangeEmailUseCase {
    constructor(private userRepository: UserRepository) {}
    async execute(userId: string, newEmail: string): Promise<void> {
        const user = await this.userRepository.findById(userId);
        user.changeEmail(newEmail);
        await this.userRepository.save(user);
    }
}

// Infrastructure Layer (Adaptadores)
class MongoDBUserRepository implements UserRepository {
    async findById(id: string): Promise<User> {
        // Implementación específica de MongoDB
        return new User(id, "John", "john@example.com");
    }
    async save(user: User): Promise<void> {
        // Guardar en MongoDB
    }
}

class PostgreSQLUserRepository implements UserRepository {
    async findById(id: string): Promise<User> {
        // Implementación específica de PostgreSQL
        return new User(id, "John", "john@example.com");
    }
    async save(user: User): Promise<void> {
        // Guardar en PostgreSQL
    }
}

Testing con SOLID

Testabilidad Mejorada

// Servicio fácil de testear gracias a DIP
class OrderService {
    constructor(
        private paymentProcessor: PaymentProcessor,
        private inventoryService: InventoryService,
        private notificationService: NotificationService
    ) {}
    async processOrder(order: Order): Promise<void> {
        // Lógica de negocio fácil de testear con mocks
    }
}

// Tests
describe('OrderService', () => {
    it('should process order successfully', async () => {
        const mockPaymentProcessor = {
            process: jest.fn().mockResolvedValue(true)
        };
        const mockInventory = {
            reserve: jest.fn().mockResolvedValue(true)
        };
        const mockNotification = {
            send: jest.fn()
        };
        const service = new OrderService(
            mockPaymentProcessor,
            mockInventory,
            mockNotification
        );
        await service.processOrder(testOrder);
        expect(mockPaymentProcessor.process).toHaveBeenCalled();
    });
});

Métricas de Calidad de Código

Indicadores de Buen Diseño

Acoplamiento:

  • Número de dependencias por clase
  • Profundidad del árbol de herencia
  • Dependencias cíclicas

Cohesión:

  • Métodos relacionados trabajando juntos
  • Responsabilidades bien definidas
  • Lógica de negocio agrupada apropiadamente

Complejidad:

  • Complejidad ciclomática por método
  • Número de parámetros por método
  • Longitud de métodos y clases

Herramientas de Análisis

  • SonarQube: Análisis estático de código
  • ESLint: Reglas de calidad para JavaScript/TypeScript
  • Jest: Coverage y métricas de testing
  • Code Climate: Análisis continuo de calidad

Refactoring hacia SOLID

Técnicas Comunes de Refactoring

Extracción de Interfaces:

// Antes
class ReportGenerator {
    generatePDF(): void { /* ... */ }
    generateExcel(): void { /* ... */ }
    sendEmail(): void { /* ... */ }
}

// Después
interface ReportGenerator {
    generate(): void;
}

interface EmailSender {
    send(): void;
}

Inversión de Dependencias:

// Antes
class UserService {
    private database = new MySQLDatabase();
}

// Después
class UserService {
    constructor(private database: Database) {}
}

Composición sobre Herencia:

// Antes
class Bird {
    fly(): void { /* ... */ }
}

class Penguin extends Bird {
    fly(): void { throw new Error(); } // ❌
}

// Después
class Bird {
    private flyingBehavior: FlyingBehavior;
    fly(): void {
        this.flyingBehavior.fly();
    }
}

Arquitecturas que Implementan SOLID

Clean Architecture

Estructura de Capas

// Domain Layer (Entidades y Reglas de Negocio)
interface User {
  id: string;
  name: string;
  email: string;
  isActive: boolean;
}

class Product {
  constructor(
    public id: string,
    public name: string,
    public price: number,
    public stock: number
  ) {}
  reduceStock(quantity: number): void {
    if (this.stock < quantity) {
      throw new Error('Insufficient stock');
    }
    this.stock -= quantity;
  }
}

// Application Layer (Casos de Uso)
interface OrderRepository {
  save(order: Order): Promise<void>;
  findById(id: string): Promise<Order>;
}

class CreateOrderUseCase {
  constructor(
    private orderRepository: OrderRepository,
    private productRepository: ProductRepository
  ) {}
  async execute(orderData: CreateOrderDTO): Promise<Order> {
    const product = await this.productRepository.findById(orderData.productId);
    product.reduceStock(orderData.quantity);
    const order = new Order(
      generateId(),
      orderData.customerId,
      product.id,
      orderData.quantity,
      product.price * orderData.quantity
    );
    await this.orderRepository.save(order);
    await this.productRepository.save(product);
    return order;
  }
}

// Infrastructure Layer (Implementaciones Concretas)
class MongoDBOrderRepository implements OrderRepository {
  async save(order: Order): Promise<void> {
    // Implementación específica de MongoDB
  }
  async findById(id: string): Promise<Order> {
    // Implementación específica de MongoDB
  }
}

// Presentation Layer (Controllers)
class OrderController {
  constructor(private createOrderUseCase: CreateOrderUseCase) {}
  async createOrder(req: Request, res: Response): Promise<void> {
    try {
      const order = await this.createOrderUseCase.execute(req.body);
      res.status(201).json(order);
    } catch (error) {
      res.status(400).json({ error: error.message });
    }
  }
}

Domain-Driven Design (DDD) con SOLID

Agregados y Entidades

// Aggregate Root
class Order {
  private items: OrderItem[] = [];
  private status: OrderStatus = 'pending';
  constructor(
    public readonly id: string,
    public readonly customerId: string,
    private total: number
  ) {}
  addItem(productId: string, quantity: number, price: number): void {
    const existingItem = this.items.find(item => item.productId === productId);
    if (existingItem) {
      existingItem.increaseQuantity(quantity);
    } else {
      this.items.push(new OrderItem(productId, quantity, price));
    }
    this.calculateTotal();
  }
  private calculateTotal(): void {
    this.total = this.items.reduce(
      (sum, item) => sum + item.getTotal(), 
      0
    );
  }
  complete(): void {
    if (this.items.length === 0) {
      throw new Error('Cannot complete empty order');
    }
    this.status = 'completed';
  }
  // Solo expone lo necesario (ISP)
  getItems(): ReadonlyArray<OrderItem> {
    return [...this.items];
  }
  getTotal(): number {
    return this.total;
  }
}

// Value Object
class Money {
  constructor(
    public readonly amount: number,
    public readonly currency: string = 'USD'
  ) {
    if (amount < 0) {
      throw new Error('Money amount cannot be negative');
    }
  }
  add(other: Money): Money {
    if (this.currency !== other.currency) {
      throw new Error('Cannot add different currencies');
    }
    return new Money(this.amount + other.amount, this.currency);
  }
}

// Domain Service
class OrderPricingService {
  calculateDiscount(order: Order, customer: Customer): Money {
    if (customer.isPremium() && order.getTotal() > 100) {
      return new Money(order.getTotal() * 0.1);
    }
    return new Money(0);
  }
}

Event-Driven Architecture

Patrón Domain Events

interface DomainEvent {
  readonly occurredOn: Date;
  readonly eventType: string;
}

class OrderCreatedEvent implements DomainEvent {
  public readonly occurredOn: Date = new Date();
  public readonly eventType: string = 'order.created';
  constructor(
    public readonly orderId: string,
    public readonly customerId: string,
    public readonly total: number
  ) {}
}

class OrderShippedEvent implements DomainEvent {
  public readonly occurredOn: Date = new Date();
  public readonly eventType: string = 'order.shipped';
  constructor(
    public readonly orderId: string,
    public readonly shippingDate: Date
  ) {}
}

// Event Handler
interface EventHandler<T extends DomainEvent> {
  handle(event: T): Promise<void>;
}

class OrderCreatedEventHandler implements EventHandler<OrderCreatedEvent> {
  constructor(
    private emailService: EmailService,
    private inventoryService: InventoryService
  ) {}
  async handle(event: OrderCreatedEvent): Promise<void> {
    await this.emailService.sendOrderConfirmation(event.orderId);
    await this.inventoryService.reserveItems(event.orderId);
  }
}

// Event Publisher
class DomainEventPublisher {
  private handlers: Map<string, EventHandler<DomainEvent>[]> = new Map();
  subscribe(eventType: string, handler: EventHandler<DomainEvent>): void {
    if (!this.handlers.has(eventType)) {
      this.handlers.set(eventType, []);
    }
    this.handlers.get(eventType)!.push(handler);
  }
  async publish(event: DomainEvent): Promise<void> {
    const eventHandlers = this.handlers.get(event.eventType) || [];
    await Promise.all(
      eventHandlers.map(handler => handler.handle(event))
    );
  }
}

CQRS (Command Query Responsibility Segregation)

Separación de Lectura y Escritura

// Commands (Write Side)
interface Command {
  readonly commandType: string;
}

class CreateUserCommand implements Command {
  public readonly commandType: string = 'user.create';
  constructor(
    public readonly userId: string,
    public readonly name: string,
    public readonly email: string
  ) {}
}

class UpdateUserEmailCommand implements Command {
  public readonly commandType: string = 'user.update.email';
  constructor(
    public readonly userId: string,
    public readonly newEmail: string
  ) {}
}

// Command Handlers
interface CommandHandler<T extends Command> {
  handle(command: T): Promise<void>;
}

class CreateUserCommandHandler implements CommandHandler<CreateUserCommand> {
  constructor(private userRepository: UserRepository) {}
  async handle(command: CreateUserCommand): Promise<void> {
    const user = new User(command.userId, command.name, command.email);
    await this.userRepository.save(user);
    // Publicar evento de dominio
    await eventPublisher.publish(new UserCreatedEvent(command.userId));
  }
}

// Queries (Read Side)
interface Query {
  readonly queryType: string;
}

class GetUserByIdQuery implements Query {
  public readonly queryType: string = 'user.get.byId';
  constructor(public readonly userId: string) {}
}

class GetUsersByCriteriaQuery implements Query {
  public readonly queryType: string = 'user.get.byCriteria';
  constructor(
    public readonly filters: UserFilters,
    public readonly page: number,
    public readonly limit: number
  ) {}
}

// Query Handlers
interface QueryHandler<T extends Query, R> {
  handle(query: T): Promise<R>;
}

class GetUserByIdQueryHandler implements QueryHandler<GetUserByIdQuery, UserDTO> {
  constructor(private userReadRepository: UserReadRepository) {}
  async handle(query: GetUserByIdQuery): Promise<UserDTO> {
    return await this.userReadRepository.findById(query.userId);
  }
}

Microservicios y SOLID

Comunicación entre Servicios

// Service Discovery Interface
interface ServiceDiscovery {
  getServiceUrl(serviceName: string): Promise<string>;
  registerService(serviceName: string, url: string): Promise<void>;
}

// Circuit Breaker Pattern
class CircuitBreaker {
  private failures: number = 0;
  private lastFailure: Date | null = null;
  private state: 'closed' | 'open' | 'half-open' = 'closed';
  constructor(
    private failureThreshold: number = 5,
    private timeout: number = 30000
  ) {}
  async execute<T>(operation: () => Promise<T>): Promise<T> {
    if (this.state === 'open') {
      if (this.isTimeoutExpired()) {
        this.state = 'half-open';
      } else {
        throw new Error('Circuit breaker is open');
      }
    }
    try {
      const result = await operation();
      this.onSuccess();
      return result;
    } catch (error) {
      this.onFailure();
      throw error;
    }
  }
  private onSuccess(): void {
    this.failures = 0;
    this.state = 'closed';
  }
  private onFailure(): void {
    this.failures++;
    this.lastFailure = new Date();
    if (this.failures >= this.failureThreshold) {
      this.state = 'open';
    }
  }
}

// Service Client con Circuit Breaker
class UserServiceClient {
  constructor(
    private httpClient: HttpClient,
    private circuitBreaker: CircuitBreaker,
    private serviceDiscovery: ServiceDiscovery
  ) {}
  async getUser(userId: string): Promise<User> {
    return this.circuitBreaker.execute(async () => {
      const serviceUrl = await this.serviceDiscovery.getServiceUrl('user-service');
      const response = await this.httpClient.get(`${serviceUrl}/users/${userId}`);
      return response.data;
    });
  }
}

Containerization y SOLID

Dependency Injection Container

interface ServiceConstructor<T> {
  new(...args: any[]): T;
}

type ServiceIdentifier = string | symbol | ServiceConstructor<any>;

class Container {
  private services: Map<ServiceIdentifier, any> = new Map();
  private factories: Map<ServiceIdentifier, Function> = new Map();
  private singletons: Map<ServiceIdentifier, any> = new Map();
  register<T>(
    identifier: ServiceIdentifier,
    constructor: ServiceConstructor<T>,
    dependencies: ServiceIdentifier[] = []
  ): void {
    this.factories.set(identifier, (container: Container) => {
      const args = dependencies.map(dep => container.get(dep));
      return new constructor(...args);
    });
  }
  registerSingleton<T>(
    identifier: ServiceIdentifier,
    constructor: ServiceConstructor<T>,
    dependencies: ServiceIdentifier[] = []
  ): void {
    this.factories.set(identifier, (container: Container) => {
      if (this.singletons.has(identifier)) {
        return this.singletons.get(identifier);
      }
      const args = dependencies.map(dep => container.get(dep));
      const instance = new constructor(...args);
      this.singletons.set(identifier, instance);
      return instance;
    });
  }
  get<T>(identifier: ServiceIdentifier): T {
    if (this.services.has(identifier)) {
      return this.services.get(identifier);
    }
    if (this.factories.has(identifier)) {
      const factory = this.factories.get(identifier)!;
      const instance = factory(this);
      this.services.set(identifier, instance);
      return instance;
    }
    throw new Error(`Service ${String(identifier)} not found`);
  }
}

// Uso del Container
const container = new Container();

// Registro de dependencias
container.registerSingleton('Database', MySQLDatabase, []);
container.register('UserRepository', MongoDBUserRepository, ['Database']);
container.register('EmailService', SendGridEmailService, []);
container.register('UserService', UserService, ['UserRepository', 'EmailService']);

// Resolución
const userService = container.get<UserService>('UserService');

Performance y SOLID

Lazy Loading y Proxy Patterns

// Virtual Proxy para Lazy Loading
interface Image {
  display(): void;
  getSize(): number;
}

class HighResolutionImage implements Image {
  constructor(private filename: string) {
    this.loadImageFromDisk();
  }
  private loadImageFromDisk(): void {
    console.log(`Loading high resolution image: ${this.filename}`);
    // Simular carga pesada
  }
  display(): void {
    console.log(`Displaying high resolution image: ${this.filename}`);
  }
  getSize(): number {
    return 5000; // Tamaño en KB
  }
}

class ImageProxy implements Image {
  private realImage: HighResolutionImage | null = null;
  constructor(private filename: string) {}
  display(): void {
    if (this.realImage === null) {
      this.realImage = new HighResolutionImage(this.filename);
    }
    this.realImage.display();
  }
  getSize(): number {
    if (this.realImage === null) {
      return 100; // Tamaño del proxy
    }
    return this.realImage.getSize();
  }
}

// Uso
const image1 = new ImageProxy('photo1.jpg'); // Carga rápida
const image2 = new ImageProxy('photo2.jpg'); // Carga rápida

console.log(image1.getSize()); // 100 KB (proxy)
image1.display(); // Carga y muestra la imagen real
console.log(image1.getSize()); // 5000 KB (imagen real)

Monitoring y Observabilidad

Structured Logging

interface Logger {
  debug(message: string, context?: object): void;
  info(message: string, context?: object): void;
  warn(message: string, context?: object): void;
  error(message: string, error?: Error, context?: object): void;
}

class StructuredLogger implements Logger {
  constructor(private transport: LogTransport) {}
  info(message: string, context: object = {}): void {
    this.transport.send({
      level: 'info',
      message,
      timestamp: new Date().toISOString(),
      ...context
    });
  }
  error(message: string, error?: Error, context: object = {}): void {
    this.transport.send({
      level: 'error',
      message,
      timestamp: new Date().toISOString(),
      error: error ? {
        name: error.name,
        message: error.message,
        stack: error.stack
      } : undefined,
      ...context
    });
  }
}

// Decorator para logging automático
function LogExecution(
  target: any,
  propertyName: string,
  descriptor: PropertyDescriptor
): PropertyDescriptor {
  const method = descriptor.value;
  descriptor.value = async function (...args: any[]) {
    const logger: Logger = (this as any).logger;
    const startTime = Date.now();
    logger.info(`Starting ${propertyName}`, { arguments: args });
    try {
      const result = await method.apply(this, args);
      const duration = Date.now() - startTime;
      logger.info(`Completed ${propertyName}`, {
        duration,
        result: result !== undefined ? 'success' : 'void'
      });
      return result;
    } catch (error) {
      const duration = Date.now() - startTime;
      logger.error(`Failed ${propertyName}`, error, {
        duration,
        arguments: args
      });
      throw error;
    }
  };
  return descriptor;
}

// Uso del decorator
class OrderService {
  constructor(private logger: Logger) {}
  @LogExecution
  async processOrder(orderId: string): Promise<void> {
    // Lógica de procesamiento
  }
}

Security y SOLID

Principle of Least Privilege

// Role-Based Access Control
interface Permission {
  resource: string;
  action: 'read' | 'write' | 'delete';
}

interface Role {
  name: string;
  permissions: Permission[];
}

class AuthorizationService {
  constructor(private userRoles: Map<string, Role[]>) {}
  hasPermission(userId: string, resource: string, action: string): boolean {
    const roles = this.userRoles.get(userId) || [];
    return roles.some(role =>
      role.permissions.some(permission =>
        permission.resource === resource &&
        permission.action === action
      )
    );
  }
}

// Security Context
class SecureService {
  constructor(
    private authorizationService: AuthorizationService,
    private userId: string
  ) {}
  async deleteOrder(orderId: string): Promise<void> {
    if (!this.authorizationService.hasPermission(this.userId, 'order', 'delete')) {
      throw new Error('Insufficient permissions');
    }
    // Lógica de eliminación segura
  }
}

Estas arquitecturas y patrones avanzados muestran cómo SOLID se integra en sistemas complejos, proporcionando bases sólidas para el desarrollo de software escalable, mantenible y robusto.

Arquitecturas y Patrones Avanzados con SOLID

Clean Architecture

Concepto Fundamental

Arquitectura que separa las preocupaciones en capas concéntricas, donde las dependencias apuntan siempre hacia el centro (el dominio). El principio fundamental es la independencia del framework y las preocupaciones externas.

Capas Principales

Domain Layer - Corazón del sistema:

  • Contiene entidades y reglas de negocio puras
  • Zero dependencias externas
  • Ejemplo: User, Order, Product con lógica de negocio

Application Layer - Orquesta el flujo:

  • Contiene casos de uso
  • Coordina entidades del dominio
  • Define contratos para infraestructura

Infrastructure Layer - Implementaciones concretas:

  • Bases de datos, APIs externas, frameworks
  • Implementa interfaces definidas en capas internas
  • Ejemplo: MongoDBUserRepository, SendGridEmailService

Presentation Layer - Punto de entrada:

  • Controllers, GraphQL resolvers, CLI commands
  • Transforma datos de entrada/salida

Beneficios

  • Testabilidad: El dominio se prueba sin infraestructura
  • Independencia: Se pueden cambiar frameworks sin afectar negocio
  • Mantenibilidad: Cada capa tiene responsabilidad clara

Domain-Driven Design (DDD)

Filosofía Centrada en el Dominio

Metodología que alinea el software con la realidad del negocio mediante modelado colaborativo entre expertos técnicos y de dominio.

Conceptos Clave

Ubiquitous Language:

  • Lenguaje común en código, documentación y conversaciones
  • Elimina traducciones entre técnicos y negocio

Bounded Contexts:

  • Límites explícitos donde un modelo tiene significado
  • Ejemplo: “Cliente” en ventas vs “Usuario” en seguridad

Aggregate Roots:

  • Entidades que garantizan consistencia del grupo
  • Punto único de acceso para modificar el agregado
  • Ejemplo: Order controla sus OrderItems

Value Objects:

  • Objetos inmutables definidos por sus atributos
  • Sin identidad propia
  • Ejemplo: Money, Address, Email

Domain Events:

  • Eventos significativos en el negocio
  • Permiten reactividad y desacoplamiento
  • Ejemplo: OrderShippedEvent, PaymentReceivedEvent

Patrones Estratégicos

  • Context Mapping: Relaciones entre bounded contexts
  • Anti-Corruption Layer: Protege un contexto de otro
  • Shared Kernel: Modelo compartido entre contextos

Event-Driven Architecture (EDA)

Paradigma Basado en Eventos

Arquitectura donde los componentes se comunican mediante eventos asíncronos, promoviendo el desacoplamiento y la escalabilidad.

Componentes Principales

Event Producers:

  • Generan eventos cuando ocurre algo significativo
  • No conocen a los consumidores

Event Consumers:

  • Reaccionan a eventos de interés
  • Procesan eventos de forma independiente

Event Bus/Message Broker:

  • Canal que distribuye eventos
  • Ejemplos: Kafka, RabbitMQ, AWS EventBridge

Patrones Comunes

Event Sourcing:

  • El estado se reconstruye aplicando eventos
  • Ejemplo: BankAccount con historial de transacciones

CQRS:

  • Separación entre modelos de escritura y lectura
  • Optimiza cada operación por separado

Saga Pattern:

  • Coordina transacciones largas entre servicios
  • Compensa fallos con eventos de compensación

Ventajas

  • Desacoplamiento: Productores y consumidores independientes
  • Escalabilidad: Procesamiento paralelo de eventos
  • Resiliencia: Los eventos pueden reprocesarse

CQRS (Command Query Responsibility Segregation)

Separación Radical

Patrón que divide las operaciones en dos categorías: comandos (que cambian estado) y consultas (que leen estado).

Lado de Comandos (Write)

  • Responsabilidad: Cambiar el estado del sistema
  • Características: Validación estricta, lógica de negocio compleja
  • Ejemplo: CreateUserCommand, UpdateOrderCommand

Lado de Consultas (Read)

  • Responsabilidad: Leer y presentar datos
  • Características: Optimizado para consultas, proyecciones simples
  • Ejemplo: GetUserQuery, SearchProductsQuery

Beneficios

  • Optimización Independiente: Bases de datos separadas para lectura/escritura
  • Escalabilidad Selectiva: Escalar según el tipo de carga
  • Modelos Especializados: Cada modelo hace una cosa bien

Cuándo Usarlo

  • Sistemas con alta carga de lecturas vs escrituras
  • Requerimientos complejos de reporting
  • Necesidad de diferentes vistas de los mismos datos

Microservicios

Arquitectura de Servicios Independientes

Enfoque donde una aplicación se compone de servicios pequeños y autónomos, cada uno ejecutándose en su propio proceso.

Características Principales

Autonomía:

  • Cada servicio se despliega independientemente
  • Base de datos propia (si es necesario)
  • Equipos independientes por servicio

Comunicación:

  • APIs REST, gRPC, mensajería asíncrona
  • Contratos bien definidos entre servicios

Resiliencia:

  • Circuit breakers para fallos en cascada
  • Retry policies con backoff exponencial

Patrones Esenciales

Service Discovery:

  • Los servicios se encuentran dinámicamente
  • Ejemplo: Consul, Eureka, Kubernetes Services

API Gateway:

  • Punto único de entrada
  • Enrutamiento, autenticación, rate limiting

Event Sourcing + CQRS:

  • Común en microservicios para consistencia eventual

Ventajas y Desafíos

  • Ventajas: Escalabilidad independiente, deployment rápido, tolerancia a fallos
  • Desafíos: Complejidad operacional, debugging distribuido, consistencia de datos

Containerization y Dependency Injection

Contenedores como Unidad de Despliegue

Empaquetado de aplicaciones con todas sus dependencias, garantizando consistencia entre entornos.

Dependency Injection (DI)

Patrón que invierte el control de la creación de dependencias, promoviendo el desacoplamiento.

Container de DI:

  • Registra implementaciones y dependencias
  • Resuelve el grafo de dependencias automáticamente
  • Maneja ciclos de vida (singleton, transient, scoped)

Beneficios:

  • Testabilidad: Fácil de mockear dependencias
  • Flexibilidad: Cambiar implementaciones sin modificar código
  • Mantenibilidad: Dependencias explícitas en el constructor

Orchestration

  • Kubernetes: Orquestación de contenedores
  • Service Mesh: Comunicación entre servicios (Istio, Linkerd)
  • ConfigMaps y Secrets: Gestión de configuración

Performance y Optimización

Patrones de Mejora de Rendimiento

Lazy Loading:

  • Carga de recursos solo cuando se necesitan
  • Ejemplo: ImageProxy que carga imágenes pesadas bajo demanda

Caching Strategies:

  • Cache aside, write through, write behind
  • Invalidation patterns

Connection Pooling:

  • Reutilización de conexiones a base de datos
  • Reduce overhead de establecimiento de conexión

Monitoring y Observabilidad

Three Pillars:

  • Logs: Eventos discretos con contexto
  • Metrics: Medidas numéricas en el tiempo
  • Traces: Seguimiento de requests a través de servicios

Structured Logging:

  • Logs como datos, no solo texto
  • Facilita búsqueda y agregación

Distributed Tracing:

  • Seguimiento de transacciones entre servicios
  • Identifica cuellos de botella

Security by Design

Principios de Seguridad Integrada

Principle of Least Privilege:

  • Usuarios y servicios tienen solo los permisos necesarios
  • RBAC (Role-Based Access Control)

Defense in Depth:

  • Múltiples capas de seguridad
  • Validación en cada nivel

Secure Defaults:

  • Configuración segura por defecto
  • Requiere acción explícita para reducir seguridad

Patrones de Seguridad

Zero Trust Architecture:

  • Nunca confiar, siempre verificar
  • Micro-segmentación de red

Token-based Authentication:

  • JWT, OAuth2, OpenID Connect
  • Stateless authentication

API Security:

  • Rate limiting, input validation, output encoding
  • Security headers (CORS, CSP)

Refactoring hacia Arquitecturas Sólidas

Estrategias de Migración

Strangler Fig Pattern:

  • Reemplazo gradual de legacy systems
  • Nuevas funcionalidades en nueva arquitectura
  • Eventual retiro del sistema legacy

Branch by Abstraction:

  • Introducir abstracción sobre implementación existente
  • Cambiar implementación detrás de la abstracción
  • Eliminar implementación antigua

Métricas de Éxito

Code Quality:

  • Complejidad ciclomática reducida
  • Cohesión alta, acoplamiento bajo
  • Coverage de tests aumentado

Business Metrics:

  • Time to market mejorado
  • Defect rate reducido
  • Team velocity consistente

Estas arquitecturas y patrones representan la evolución natural de SOLID hacia sistemas empresariales complejos, manteniendo los principios fundamentales de diseño orientado a objetos mientras se adaptan a las demandas modernas de escalabilidad, resiliencia y mantenibilidad.

Patrones de Diseño Fundamentales

Patrones Creacionales

Factory Method

Patrón que define una interfaz para crear objetos, pero permite a las subclases decidir qué clase instanciar. Promueve el acoplamiento débil entre el creador y los productos concretos.

Aplicación: Cuando una clase no puede anticipar el tipo de objetos que debe crear, o cuando las subclases quieren especificar los objetos que crean.

Ejemplo: DocumentCreator con ResumeCreator y ReportCreator que producen diferentes tipos de documentos.

Abstract Factory

Proporciona una interfaz para crear familias de objetos relacionados sin especificar sus clases concretas. Ideal para sistemas que deben ser independientes de cómo se crean, componen y representan sus productos.

Aplicación: Interfaces de usuario multiplataforma donde cada plataforma tiene su propia familia de controles.

Builder

Separa la construcción de un objeto complejo de su representación, permitiendo el mismo proceso de construcción crear diferentes representaciones. Resuelve el problema del constructor telescópico.

Aplicación: Construcción de objetos complejos paso a paso, como consultas SQL o objetos de configuración.

Singleton

Garantiza que una clase tenga solo una instancia y proporciona un punto de acceso global a ella. Útil para recursos compartidos como conexiones a bases de datos o configuraciones.

Aplicación: Logger global, cache compartida, configuración del sistema.

Prototype

Crea nuevos objetos copiando un prototipo existente. Evita la creación de subclases para instanciar objetos y permite añadir o quitar objetos en tiempo de ejecución.

Aplicación: Cuando el coste de crear un objeto es mayor que copiarlo, o cuando el sistema necesita ser independiente de cómo se crean y representan los productos.


Patrones Estructurales

Adapter

Permite que interfaces incompatibles trabajen juntas. Actúa como puente entre dos interfaces diferentes, haciendo que parezcan compatibles.

Aplicación: Integración de librerías legacy, adaptación de APIs externas a interfaces internas.

Bridge

Separa una abstracción de su implementación, permitiendo que ambas varíen independientemente. Evita la explosión de clases en herencias multidimensionales.

Aplicación: Controladores de dispositivos donde la abstracción (control remoto) se separa de la implementación (dispositivo).

Composite

Permite tratar objetos individuales y composiciones de objetos de manera uniforme. Crea estructuras en árbol donde los nodos hoja y compuestos se tratan igual.

Aplicación: Sistemas de archivos, interfaces gráficas con contenedores y elementos.

Decorator

Añade responsabilidades adicionales a un objeto de forma dinámica. Proporciona una alternativa flexible a la herencia para extender funcionalidad.

Aplicación: Streams de Java, middleware en aplicaciones web, añadir funcionalidades a objetos existentes.

Facade

Proporciona una interfaz simplificada a un subsistema complejo. Oculta la complejidad del subsistema y facilita su uso.

Aplicación: APIs simplificadas para sistemas complejos, wrappers para librerías complicadas.

Flyweight

Utiliza el compartimiento para soportar eficientemente grandes cantidades de objetos de grano fino. Reduce el uso de memoria compartiendo partes comunes del estado.

Aplicación: Editores de texto que comparten caracteres, juegos con muchos objetos similares.

Proxy

Proporciona un sustituto o marcador de posición para otro objeto para controlar el acceso a él. Útil para acceso lazy, control de acceso o logging.

Aplicación: Virtual proxy para imágenes pesadas, protection proxy para control de acceso, remote proxy para objetos distribuidos.


Patrones de Comportamiento

Chain of Responsibility

Permite que más de un objeto maneje una solicitud, pasándola a lo largo de una cadena hasta que es manejada. Desacopla el emisor y el receptor.

Aplicación: Sistemas de ayuda contextual, procesamiento de eventos, middleware en aplicaciones web.

Command

Encapsula una solicitud como un objeto, permitiendo parametrizar clientes con diferentes solicitudes, hacer cola o registrar solicitudes. Soporta operaciones deshacer.

Aplicación: Sistemas de menús, operaciones de deshacer/rehacer, jobs en cola.

Interpreter

Define una representación gramatical para un lenguaje y un intérprete que usa la representación para interpretar sentencias del lenguaje. Para lenguajes simples.

Aplicación: Expresiones regulares, consultas de bases de datos simples, calculadoras.

Iterator

Proporciona una forma de acceder secuencialmente a los elementos de una agregación sin exponer su representación subyacente. Separa el recorrido de la estructura.

Aplicación: Colecciones en lenguajes de programación, recorrido de estructuras de datos complejas.

Mediator

Define un objeto que encapsula cómo un conjunto de objetos interactúa. Promueve el acoplamiento débil al evitar que los objetos se refieran entre sí explícitamente.

Aplicación: Controladores en MVC, sistemas de chat donde el mediator maneja la comunicación.

Memento

Captura y externaliza el estado interno de un objeto sin violar la encapsulación, para que el objeto pueda ser restaurado a este estado más tarde. Para implementar deshacer.

Aplicación: Editores de texto, herramientas de diseño, juegos con guardado de estado.

Observer

Define una dependencia uno-a-muchos entre objetos, de modo que cuando un objeto cambia estado, todos sus dependientes son notificados automáticamente. Para eventos y notificaciones.

Aplicación: Sistemas de eventos en interfaces gráficas, notificaciones en tiempo real.

State

Permite a un objeto alterar su comportamiento cuando su estado interno cambia. Parecerá que el objeto cambió su clase.

Aplicación: Máquinas de estado finito, reproductores multimedia, procesadores de pedidos.

Strategy

Define una familia de algoritmos, encapsula cada uno y los hace intercambiables. Permite que el algoritmo varíe independientemente de los clientes que lo usan.

Aplicación: Algoritmos de ordenación, estrategias de compresión, métodos de pago.

Template Method

Define el esqueleto de un algoritmo en una operación, delegando algunos pasos a las subclases. Permite a las subclases redefinir ciertos pasos sin cambiar la estructura del algoritmo.

Aplicación: Frameworks que definen flujos de trabajo, procesamiento de datos con pasos variables.

Visitor

Representa una operación a realizar en los elementos de una estructura de objetos. Permite definir nuevas operaciones sin cambiar las clases de los elementos.

Aplicación: Compiladores que realizan diferentes análisis sobre AST, herramientas de reporting sobre estructuras complejas.


Relación con Principios SOLID

SRP y Patrones

  • Factory Method: Separa la creación de objetos de su uso
  • Strategy: Separa algoritmos en clases independientes
  • Command: Separa la invocación de la ejecución

OCP y Patrones

  • Strategy: Fácil añadir nuevos algoritmos
  • Decorator: Extender funcionalidad sin modificar
  • Observer: Añadir observadores sin cambiar el sujeto

LSP y Patrones

  • Composite: Todos los componentes siguen la misma interfaz
  • Strategy: Las estrategias son intercambiables
  • State: Los estados son sustituibles

ISP y Patrones

  • Adapter: Crea interfaces específicas para clientes
  • Facade: Proporciona interfaces simplificadas
  • Proxy: Expone solo la funcionalidad necesaria

DIP y Patrones

  • Dependency Injection: Implementación directa de DIP
  • Abstract Factory: Depende de abstracciones para creación
  • Template Method: Las subclases dependen de abstracciones

Patrones en Arquitecturas Modernas

Microservicios

  • API Gateway: Facade para microservicios
  • Circuit Breaker: Proxy para control de fallos
  • Service Discovery: Abstract factory para localización

Event-Driven

  • Event Sourcing: Memento para estado del sistema
  • CQRS: Strategy para lectura vs escritura
  • Saga: Mediator para transacciones distribuidas

Domain-Driven Design

  • Repository: Abstract factory para agregados
  • Specification: Strategy para criterios de negocio
  • Domain Events: Observer para eventos de dominio

Anti-patrones Comunes

Singleton Abuse

  • Problema: Dependencias ocultas, difícil de testear
  • Solución: Dependency Injection, limitar uso a casos genuinos

God Object

  • Problema: Clase que hace demasiado, viola SRP
  • Solución: Dividir usando Strategy, Factory, Facade

Anemic Domain Model

  • Problema: Objetos solo con datos, sin comportamiento
  • Solución: Mover lógica a entidades usando State, Strategy

Callback Hell

  • Problema: Callbacks anidados, código difícil de leer
  • Solución: Usar Promise/Async, Chain of Responsibility

Los patrones de diseño son herramientas que materializan los principios SOLID en soluciones concretas y reutilizables para problemas comunes de diseño de software.

Arquitecturas y Patrones de Resiliencia y Escalabilidad

Circuit Breaker Pattern

Concepto y Mecanismos

Patrón que previene fallos en cascada al detectar problemas en servicios dependientes. Funciona como un interruptor eléctrico que “abre” cuando detecta demasiados fallos.

Estados del Circuit Breaker:

  • Closed: Operación normal, las solicitudes pasan directamente
  • Open: Solicitudes rechazadas inmediatamente sin llamar al servicio
  • Half-Open: Permite solicitudes limitadas para probar recuperación

Umbrales de Configuración:

  • Failure threshold percentage
  • Timeout duration
  • Number of calls in half-open state
  • Wait duration in open state

Estrategias de Recuperación

  • Exponential Backoff: Reintentos con intervalos crecientes
  • Fallback Responses: Respuestas alternativas cuando el servicio está caído
  • Bulkhead Isolation: Separación de recursos para evitar contagio

Bulkhead Pattern

Aislamiento de Recursos

Patrón que divide los recursos del sistema en grupos aislados para contener fallos y prevenir la propagación de problemas.

Tipos de Bulkheading:

Thread Pool Isolation:

  • Grupos separados de hilos para diferentes servicios
  • Evita que un servicio lento consuma todos los recursos

Connection Pool Isolation:

  • Pools de conexión separados por servicio
  • Protege contra saturaciones de conexiones

Resource Partitioning:

  • CPU, memoria y almacenamiento dedicados
  • Aislamiento a nivel de infraestructura

Implementación en Microservicios

  • Límites de recursos por contenedor
  • Queue separation para mensajes
  • Database connection partitioning

Retry Pattern

Mecanismos de Reintento Inteligente

Estrategia para manejar fallos temporales mediante reintentos automáticos con políticas específicas.

Políticas Comunes:

Fixed Interval:

  • Reintentos a intervalos constantes
  • Simple pero puede causar congestión

Exponential Backoff:

  • Intervalos que crecen exponencialmente
  • Da tiempo al servicio para recuperarse

Jitter:

  • Variación aleatoria en los intervalos
  • Evita el “herd effect” en sistemas distribuidos

Circuit Breaker Integration:

  • Detiene reintentos si el circuito está abierto
  • Combinación con fallback mechanisms

Timeout Pattern

Gestión de Límites Temporales

Establecimiento de tiempos máximos de espera para operaciones, previniendo bloqueos indefinidos.

Tipos de Timeouts:

Connection Timeout:

  • Tiempo máximo para establecer conexión
  • Previene esperas en conexiones lentas

Read Timeout:

  • Tiempo máximo para recibir respuesta
  • Maneja servicios que responden lentamente

Global Timeout:

  • Tiempo máximo para operación completa
  • Incluye todos los pasos y reintentos

Propagación en Sistemas Distribuidos:

  • Header propagation entre servicios
  • Deadline-based timeout management
  • Context cancellation en llamadas anidadas

Cache-Aside Pattern

Estrategia de Caching Desacoplado

Patrón donde la aplicación es responsable de leer y escribir en la cache, manteniéndola sincronizada con el almacenamiento principal.

Flujo de Operaciones:

Lectura:

  1. Buscar en cache primero
  2. Si no existe (cache miss), leer de base de datos
  3. Escribir en cache para futuras lecturas
  4. Retornar datos

Escritura:

  1. Escribir en base de datos
  2. Invalidar entrada en cache
  3. O escribir-through para mantener consistencia

Estrategias de Invalidation:

  • Time-based: Expiración después de tiempo fijo
  • Event-based: Invalidación por cambios de datos
  • Manual: Invalidación explícita por aplicación

Event Sourcing

Almacenamiento Basado en Eventos

Patrón donde el estado de la aplicación se determina mediante una secuencia de eventos, en lugar de almacenar solo el estado actual.

Componentes Principales:

Event Store:

  • Base de datos optimizada para eventos
  • Append-only, inmutable
  • Mantiene orden y consistencia

Event Stream:

  • Secuencia ordenada de eventos para una entidad
  • Identificado por aggregate ID

Projections:

  • Vistas materializadas del estado actual
  • Reconstruidas aplicando eventos
  • Optimizadas para consultas específicas

Snapshots:

  • Puntos de estado guardados periódicamente
  • Reducen tiempo de reconstrucción
  • Para streams muy largos

Beneficios en Sistemas Distribuidos

  • Audit Trail: Historial completo de cambios
  • Temporal Queries: Estado en cualquier punto del tiempo
  • Event Replay: Reprocesamiento para corregir bugs
  • Multi-Projection: Diferentes vistas del mismo evento stream

Saga Pattern

Gestión de Transacciones Distribuidas

Patrón para mantener la consistencia de datos a través de múltiples servicios sin usar transacciones distribuidas ACID.

Tipos de Saga:

Orchestration-Based:

  • Coordinador central orquesta los pasos
  • Conoce el flujo completo de la transacción
  • Más control pero más acoplamiento

Choreography-Based:

  • Cada servicio publica eventos
  • Los servicios reaccionan a eventos relevantes
  • Menos acoplamiento pero más complejidad de seguimiento

Patrones de Compensación:

  • Compensating Transactions: Operaciones inversas
  • Retry Logic: Reintento de pasos fallidos
  • Pause and Resume: Continuación desde punto de fallo

Estrategias de Recovery

  • Manual Intervention: Para casos complejos no automatizables
  • Automated Compensation: Rollback automático de pasos completados
  • Forward Recovery: Completar la transacción por caminos alternativos

API Gateway Pattern

Punto Único de Entrada

Patrón que proporciona un único endpoint para clientes, abstraiendo la complejidad de los microservicios backend.

Funcionalidades Clave:

Routing y Composition:

  • Enrutamiento inteligente a servicios
  • Aggregation de datos de múltiples servicios
  • Response transformation

Cross-Cutting Concerns:

  • Authentication y Authorization
  • Rate Limiting y Throttling
  • Caching de respuestas
  • Logging y Monitoring

Resilience Features:

  • Circuit breaker para servicios backend
  • Timeout management
  • Fallback responses

Service Mesh Integration:

  • Comunicación sidecar-to-sidecar
  • Service discovery dinámico
  • Load balancing inteligente

Sidecar Pattern

Acompañante de Aplicaciones

Patrón donde componentes auxiliares se ejecutan en procesos separados pero junto a la aplicación principal.

Casos de Uso Comunes:

Infrastructure Concerns:

  • Service discovery registration
  • Health checking
  • Configuration management

Observability:

  • Metrics collection
  • Distributed tracing
  • Log aggregation

Networking:

  • Proxy para comunicaciones
  • TLS termination
  • Protocol translation

Service Mesh Implementation:

  • Envoy, Linkerd, Istio
  • Traffic management policies
  • Security policies enforcement

Strangler Fig Pattern

Migración Gradual de Sistemas

Patrón para reemplazar sistemas legacy incrementalmente, creando nuevo sistema alrededor del existente.

Fases de Implementación:

Phase 1: Transform:

  • Crear facade sobre sistema legacy
  • Reroute algunas solicitudes
  • Mantener coexistencia

Phase 2: Coexist:

  • Ambas implementaciones activas
  • Feature toggle entre versiones
  • Data synchronization

Phase 3: Eliminate:

  • Retirar código legacy
  • Eliminar rutas antiguas
  • Completar migración

Estrategias de Ruteo:

  • URL-based: Diferentes endpoints para nuevo vs legacy
  • Feature-based: Toggle por funcionalidad
  • User-based: Grupos de usuarios migrados progresivamente

Backends for Frontends (BFF)

APIs Especializadas por Cliente

Patrón donde se crean backends específicos para cada tipo de cliente frontend.

Beneficios por Plataforma:

Mobile BFF:

  • Data aggregation optimizada para móvil
  • Protocolos eficientes en ancho de banda
  • Offline-first considerations

Web BFF:

  • Server-side rendering support
  • SEO optimization
  • Rich interaction patterns

IoT BFF:

  • Protocol translation
  • Batch processing
  • Real-time communication

Características Comunes:

  • Client-specific data transformation
  • Optimized payloads
  • Platform-aware error handling
  • Custom authentication flows

Health Check Pattern

Monitoreo de Estado de Servicios

Mecanismo para verificar continuamente la salud de los componentes del sistema.

Tipos de Health Checks:

Liveness Probe:

  • Indica si la aplicación está ejecutándose
  • Failure resulta en restart del contenedor
  • Checks básicos de proceso

Readiness Probe:

  • Indica si la aplicación está lista para recibir tráfico
  • Failure resulta en removal del load balancer
  • Checks de dependencias

Startup Probe:

  • Para aplicaciones con largo tiempo de inicio
  • Previene kills prematuros
  • Deshabilitado después del primer éxito

Composite Health Checks:

  • Database connectivity verification
  • External service availability
  • Resource utilization monitoring
  • Business logic health indicators

Feature Toggle Pattern

Desarrollo y Release Controlados

Mecanismo para alternar funcionalidades sin desplegar nuevo código.

Tipos de Feature Toggles:

Release Toggles:

  • Controlan features incompletas
  • Vida corta, removidos después del release
  • Para trunk-based development

Experiment Toggles:

  • A/B testing y canary releases
  • Basados en porcentaje de usuarios
  • Colectan métricas de performance

Ops Toggles:

  • Control operacional de features
  • Para degradación graceful bajo carga
  • Kill switches para features problemáticas

Permission Toggles:

  • Acceso basado en roles o permisos
  • Beta features para usuarios específicos
  • License-based feature control

Management Strategies:

  • Centralized configuration service
  • Runtime changes without redeployment
  • Audit trail for toggle changes
  • Automated cleanup of technical debt

Estos patrones de resiliencia y escalabilidad complementan los principios SOLID proporcionando mecanismos prácticos para construir sistemas distribuidos robustos, mantenibles y capaces de manejar fallos de manera elegante.

Patrones de Data y Arquitecturas Especializadas

Data Mesh Architecture

Paradigma de Data como Producto

Arquitectura descentralizada que trata los datos como productos, con equipos de dominio como dueños.

Principios Fundamentales:

Domain Ownership:

  • Los equipos de negocio son dueños de sus datos
  • Responsabilidad end-to-end de calidad y disponibilidad
  • Data products con SLAs definidos

Data as a Product:

  • Documentación automática
  • Contracts de calidad de datos
  • Self-service discovery

Self-Serve Data Platform:

  • Infraestructura compartida para data products
  • Standardized tooling
  • Federated computational governance

Federated Governance:

  • Políticas globales con implementación local
  • Compliance automatizado
  • Quality standards enforcement

CQRS con Event Sourcing

Combinación de Patrones

Integración de Command Query Responsibility Segregation con Event Sourcing para sistemas de alta consistencia.

Arquitectura Híbrida:

Write Side:

  • Commands validados y aplicados
  • Events almacenados en event store
  • Projections actualizan read models

Read Side:

  • Optimizado para consultas específicas
  • Multiple read models para diferentes necesidades
  • Cache strategies agresivas

Synchronization Mechanisms:

  • Event handlers asíncronos
  • Conflict resolution strategies
  • Eventually consistency patterns

Benefits para Business Logic:

  • Complete audit trail
  • Temporal queries
  • Business intelligence integration
  • Compliance and regulatory requirements

Hexagonal Architecture con DDD

Arquitectura de Puertos y Adaptadores

Combinación de principios hexagonales con Domain-Driven Design para máxima separación de concerns.

Core Domain Layer:

  • Entities ricas en comportamiento
  • Value objects inmutables
  • Domain services para lógica transversal

Application Layer:

  • Use cases como orquestadores
  • Transaction management
  • Domain event publishing

Infrastructure Layer:

  • Repository implementations
  • External service adapters
  • Framework-specific code

Ports:

  • Input ports para use cases
  • Output ports para repositories
  • Event listeners para domain events

Microfrontends Architecture

Extensión de Microservicios al Frontend

Arquitectura que aplica principios de microservicios al desarrollo de interfaces de usuario.

Approaches de Implementación:

Build-time Integration:

  • Package-based microfrontends
  • NPM modules como building blocks
  • Shared dependency management

Run-time Integration:

  • Web Components como contenedores
  • Module Federation en Webpack
  • Dynamic script loading

Server-side Integration:

  • Edge-side includes (ESI)
  • Server-side composition
  • Fragment-based assembly

Cross-cutting Concerns:

  • Shared design systems
  • Consistent user experience
  • Cross-application state management
  • Unified authentication flows

Serverless Patterns

Arquitecturas sin Servidor

Patrones diseñados específicamente para entornos serverless como AWS Lambda, Azure Functions.

Event-driven Patterns:

Event Processing:

  • Stream processing con Kinesis/Lambda
  • Batch processing con S3 triggers
  • Real-time data transformation

API Backends:

  • REST APIs con API Gateway + Lambda
  • GraphQL resolvers serverless
  • WebSocket connections management

Orchestration Patterns:

  • Step Functions para workflows complejos
  • Event Bridge para event routing
  • SQS para message queuing

Consideraciones Específicas:

  • Cold start optimization
  • Stateless design imperatives
  • Resource limits awareness
  • Cost optimization strategies

GraphQL Federation

Arquitectura de APIs Federadas

Patrón para construir GraphQL APIs distribuidas across múltiples equipos.

Federation Components:

Subgraph Services:

  • Autonomous GraphQL schemas
  • Team ownership boundaries
  • Independent deployment cycles

Gateway/Router:

  • Single entry point for clients
  • Query planning and execution
  • Schema composition and validation

Entity Resolution:

  • Cross-service data fetching
  • Reference resolvers
  • N+1 query prevention

Extended Type System:

  • @key directives for entity keys
  • @external for cross-references
  • @requires for field dependencies

Reactive Systems Principles

Sistemas Responsivos y Resilientes

Arquitecturas diseñadas alrededor de principios reactivos para sistemas modernos.

Reactive Manifesto Principles:

Responsive:

  • Fast and consistent response times
  • Real-time user feedback
  • Predictable performance

Resilient:

  • Failure containment
  • Self-healing capabilities
  • Graceful degradation

Elastic:

  • Dynamic resource allocation
  • Scale up/down automatically
  • Load distribution intelligence

Message Driven:

  • Async non-blocking communication
  • Location transparency
  • Back-pressure propagation

Implementation Patterns:

  • Actor model systems
  • Reactive streams
  • Event loop architectures
  • Non-blocking I/O

Data-intensive Applications Patterns

Patrones para Procesamiento de Datos

Arquitecturas especializadas en manejo de grandes volúmenes de datos.

Stream Processing:

  • Real-time data pipelines
  • Windowed aggregations
  • Complex event processing

Batch Processing:

  • Large-scale data transformation
  • ETL/ELT workflows
  • Distributed computing patterns

Lambda Architecture:

  • Batch layer for comprehensive views
  • Speed layer for real-time updates
  • Serving layer for query integration

Kappa Architecture:

  • Stream-only processing
  • Replay capability for reprocessing
  • Simplified maintenance model

AI/ML System Patterns

Arquitecturas para Sistemas de Machine Learning

Patrones específicos para integración y operación de modelos de ML.

ML Pipeline Architecture:

Data Collection:

  • Feature store implementation
  • Data versioning
  • Quality monitoring

Model Training:

  • Automated retraining pipelines
  • Experiment tracking
  • Hyperparameter optimization

Model Serving:

  • Real-time inference endpoints
  • Batch prediction jobs
  • A/B testing infrastructure

Model Monitoring:

  • Prediction quality metrics
  • Data drift detection
  • Performance degradation alerts

MLOps Practices:

  • Infrastructure as Code para ML
  • Automated deployment pipelines
  • Model registry management
  • Compliance and explainability

Blockchain-inspired Patterns

Patrones de Sistemas Descentralizados

Arquitecturas inspiradas en blockchain para aplicaciones empresariales.

Distributed Ledger Patterns:

Immutable Audit Trail:

  • Append-only data structures
  • Cryptographic verification
  • Timestamping services

Consensus Mechanisms:

  • Practical Byzantine Fault Tolerance
  • Proof of Authority
  • Raft consensus algorithm

Smart Contract Patterns:

  • Business logic encapsulation
  • State transition validation
  • Multi-signature authorization

Tokenization Patterns:

  • Digital asset representation
  • Ownership tracking
  • Transfer authorization

Quantum-ready Architecture

Preparación para Computación Cuántica

Patrones para sistemas que necesitan prepararse para la era cuántica.

Cryptography Transition:

  • Post-quantum cryptography adoption
  • Hybrid cryptographic systems
  • Key management evolution

Algorithm Design:

  • Quantum-resistant algorithms
  • Classical-quantum hybrid approaches
  • Problem decomposition strategies

Data Structure Preparation:

  • State representation optimization
  • Parallel processing readiness
  • Entanglement-aware design

Development Practices:

  • Quantum computing literacy
  • Algorithm selection criteria
  • Performance benchmarking
  • Security assessment frameworks

Ethical AI Patterns

Patrones para IA Responsable

Arquitecturas que incorporan consideraciones éticas en sistemas de IA.

Fairness and Bias Mitigation:

  • Bias detection pipelines
  • Fairness metrics monitoring
  • Demographic parity enforcement

Transparency and Explainability:

  • Model interpretability components
  • Decision tracing mechanisms
  • Explanation generation

Privacy Preservation:

  • Differential privacy implementation
  • Federated learning architectures
  • Data anonymization pipelines

Accountability and Governance:

  • Audit trail for AI decisions
  • Human-in-the-loop patterns
  • Ethics committee review integration

Compliance Frameworks:

  • Regulatory requirement mapping
  • Documentation automation
  • Impact assessment integration

Estas arquitecturas y patrones especializados representan la evolución hacia sistemas más descentralizados, éticos y preparados para las demandas futuras, manteniendo siempre los principios fundamentales de buen diseño y separación de responsabilidades.