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
Patohereda deAve, debe poder usar todos los métodos deAvecorrectamente
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
IMultifuncionalenIImpresora,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/elseoswitchpara tipos
LSP:
instanceofchecks 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:
newkeyword 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,Productcon 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:
Ordercontrola susOrderItems
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:
BankAccountcon 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:
ImageProxyque 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:
- Buscar en cache primero
- Si no existe (cache miss), leer de base de datos
- Escribir en cache para futuras lecturas
- Retornar datos
Escritura:
- Escribir en base de datos
- Invalidar entrada en cache
- 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.
¿Te gusta este contenido? Suscríbete vía RSS