1. Arquitectura y Patrones
Estructuras de alto nivel y soluciones probadas para organizar sistemas complejos, escalables y mantenibles.
1.1 🏗️ Arquitecturas de Software
Qué: Decisiones estructurales fundamentales sobre cómo organizar un sistema.
Por qué: Define cómo el sistema crece, se mantiene y responde al cambio. Una mala arquitectura puede matar un proyecto exitoso.
Quién: Arquitectos de software, tech leads, senior developers.
Costo: Decisión temprana de alto impacto. Cambiar arquitectura en sistema maduro = 6-18 meses.
| Arquitectura | Qué | Por qué | Cuándo | Dónde | Cómo | Trade-offs |
|---|---|---|---|---|---|---|
| Monolítica | Aplicación única con todos los módulos integrados | Simplicidad, deployment único, debugging fácil | MVPs, equipos pequeños, dominios simples | Startups, sistemas internos | Todo en un proceso, shared DB, deployment único | ✅ Simple, rápido desarrollo inicial; ✅ Si esta bien estructurado puede escalar horizontalmente duplicando el proceso; ❌ Escalado vertical; ❌ Acoplamiento y la dificultad de escalar componentes específicos de manera independiente |
| MVC | Model-View-Controller: separar datos, UI y control | Claridad en responsabilidades | Apps web tradicionales, dashboards | Backend + templates | Modelos (datos), Vistas (UI), Controladores (lógica coordinación) | ✅ Patrón conocido; ❌ Controllers crecen (fat controllers) |
| Microservicios | Sistema distribuido con servicios independientes | Escalado independiente, equipos autónomos | Sistemas complejos, múltiples equipos | Netflix, Uber, Amazon | Servicios pequeños, comunicación API/eventos, DB por servicio | ✅ Escalabilidad, fault isolation; ❌ Complejidad operacional, latencia |
| Hexagonal | Lógica central aislada de interfaces externas (ports & adapters) | Testability, independencia de frameworks | Dominios complejos, larga vida | Backend crítico | Core (lógica) + Puertos (interfaces) + Adaptadores (implementaciones) | ✅ Testeo fácil, cambiar DB/UI sin tocar core; ❌ Más código inicial |
| Capas | Separación horizontal: presentación, negocio, datos | Modularidad, responsabilidades claras | Sistemas empresariales tradicionales | Monolitos estructurados | Capas solo conocen la inferior, DTO entre capas | ✅ Organización clara; ❌ Puede ser rígido |
| Event-Driven | Comunicación basada en eventos asincrónicos | Desacoplamiento, escalabilidad | Sistemas con workflows complejos, integraciones | E-commerce, IoT, streaming | Event Bus/Broker, productores/consumidores | ✅ Desacoplamiento total; ❌ Debugging complejo, eventual consistency |
| Serverless | Funciones sin servidor dedicado, auto-scaling | Costo por uso, cero gestión servidores | Tareas puntuales, APIs sencillas, jobs | AWS Lambda, Cloud Functions | Funciones stateless, triggers (HTTP, eventos), short-lived | ✅ Escalado automático, low cost; ❌ Cold starts, vendor lock-in |
1.2 ⚖️ Teorema CAP
Qué: En un sistema distribuido, solo se pueden garantizar simultáneamente dos de tres propiedades: Consistencia (Consistency), Disponibilidad (Availability) y Tolerancia a Particiones (Partition Tolerance).
Por qué: Es el pilar teórico que justifica las elecciones entre bases de datos SQL y NoSQL, y las arquitecturas distribuidas. Entender CAP permite tomar decisiones informadas sobre trade-offs.
Quién: Arquitectos de software, tech leads, desarrolladores de sistemas distribuidos.
Cuándo: Al diseñar sistemas distribuidos, elegir bases de datos, definir arquitecturas de microservicios.
1.2.1 Las Tres Propiedades
| Propiedad | Qué significa | Ejemplo |
|---|---|---|
| Consistency (C) | Todos los nodos ven los mismos datos al mismo tiempo. Lectura siempre retorna el valor más reciente | Sistemas bancarios: saldo debe ser exacto en todas las consultas |
| Availability (A) | Toda solicitud recibe una respuesta (éxito o fallo), sin garantía de que contenga el dato más reciente | Redes sociales: mejor mostrar timeline ligeramente desactualizado que error |
| Partition Tolerance (P) | El sistema continúa operando a pesar de pérdida de mensajes entre nodos (particiones de red) | Inevitable en sistemas distribuidos (red puede fallar) |
1.2.2 El Trade-off Fundamental
graph TD
CAP[Teorema CAP]
CAP --> CP[CP: Consistency + Partition Tolerance]
CAP --> AP[AP: Availability + Partition Tolerance]
CAP --> CA[CA: Consistency + Availability]
CP --> CP_Ex["Sacrifica Availability<br/>Ejemplos: HBase, MongoDB (strong), Redis"]
AP --> AP_Ex["Sacrifica Consistency<br/>Ejemplos: Cassandra, DynamoDB, Riak"]
CA --> CA_Ex["Sacrifica Partition Tolerance<br/>Ejemplos: RDBMS tradicional (single-node)<br/>⚠️ No viable en sistemas distribuidos reales"]
style CP fill:#ffcccc
style AP fill:#ccffcc
style CA fill:#ccccff
1.2.3 Decisiones Prácticas
| Escenario | Elección | Justificación | Tecnología |
|---|---|---|---|
| Sistema bancario | CP | Consistencia es crítica, mejor rechazar operación que mostrar saldo incorrecto | PostgreSQL (strong consistency), Spanner |
| Red social | AP | Disponibilidad es clave, eventual consistency es aceptable | Cassandra, DynamoDB |
| E-commerce (carrito) | AP | Mejor permitir agregar al carrito aunque inventario esté levemente desactualizado | DynamoDB, Riak |
| E-commerce (checkout) | CP | Al finalizar compra, inventario debe ser exacto | PostgreSQL con locks, MongoDB transactions |
1.2.4 Consistencia Eventual
Qué: En sistemas AP, los datos eventualmente convergen a un estado consistente, pero puede haber ventanas de inconsistencia.
Ejemplo: Publicar un tweet puede tardar segundos en aparecer para todos los seguidores (eventual consistency), pero el sistema siempre está disponible.
Herramientas: Cassandra, DynamoDB, Riak, CouchDB.
1.2.5 Recursos CAP
1.3 📈 Escalabilidad: Vertical vs Horizontal
Qué: Estrategias para aumentar la capacidad de un sistema ante mayor carga.
Por qué: Entender este trade-off es fundamental para diseñar arquitecturas que crezcan eficientemente. La elección afecta costos, complejidad y límites de crecimiento.
Quién: Arquitectos, DevOps, tech leads.
Cuándo: Al planificar crecimiento, ante problemas de performance, diseñando nuevos sistemas.
1.3.1 Comparación
| Aspecto | Escalabilidad Vertical (Scale Up) | Escalabilidad Horizontal (Scale Out) |
|---|---|---|
| Qué es | Aumentar recursos de un solo servidor (más CPU, RAM, disco) | Añadir más servidores/instancias |
| Límite | Físico (máximo hardware disponible) | Prácticamente ilimitado |
| Costo | Exponencial (hardware high-end es desproporcionadamente caro) | Lineal (agregar commodity hardware) |
| Complejidad | Baja (sin cambios arquitectónicos) | Alta (requiere load balancing, estado distribuido) |
| Downtime | Sí (al reemplazar hardware) | No (agregar nodos sin downtime) |
| Arquitectura | Favorece Monolitos | Favorece Microservicios, arquitecturas distribuidas |
| Ejemplos | Servidor de 8GB RAM → 32GB RAM | 1 servidor → 10 servidores detrás de load balancer |
1.3.2 Cuándo Usar Cada Una
| Escenario | Recomendación | Razón |
|---|---|---|
| MVP, startup temprana | Vertical | Simplicidad, menor overhead operacional |
| Base de datos SQL (PostgreSQL, MySQL) | Vertical primero, luego read replicas (horizontal) | SQL escala mejor verticalmente, sharding es complejo |
| Aplicación stateless (API REST) | Horizontal | Fácil replicar, load balancer distribuye |
| Procesamiento batch | Horizontal | Paralelizar tareas independientes |
| Cache (Redis) | Vertical hasta límite, luego Horizontal (sharding) | Redis es single-threaded, vertical es eficiente |
| Tráfico impredecible | Horizontal con auto-scaling | Agregar/quitar nodos según demanda |
1.3.3 Ejemplo Visual
graph LR
subgraph Vertical["Escalabilidad Vertical"]
V1[Servidor<br/>4 CPU, 8GB RAM] -->|Upgrade| V2[Servidor<br/>16 CPU, 64GB RAM]
end
subgraph Horizontal["Escalabilidad Horizontal"]
LB[Load Balancer]
LB --> H1[Servidor 1<br/>4 CPU, 8GB]
LB --> H2[Servidor 2<br/>4 CPU, 8GB]
LB --> H3[Servidor 3<br/>4 CPU, 8GB]
LB --> H4[Servidor N<br/>4 CPU, 8GB]
end
style Vertical fill:#ffe6e6
style Horizontal fill:#e6f3ff
1.3.4 Relación con Arquitecturas
| Arquitectura | Escalabilidad Natural | Por qué |
|---|---|---|
| Monolito | Vertical | Todo en un proceso, difícil distribuir. Puede escalar horizontalmente si es stateless y usa DB externa |
| Microservicios | Horizontal | Servicios independientes, fácil replicar cada uno según necesidad |
| Serverless | Horizontal automático | Provider escala funciones automáticamente |
| Event-Driven | Horizontal | Consumidores de eventos se pueden replicar |
1.3.5 Estrategia Híbrida
Recomendación: Combinar ambas estrategias según el componente.
Ejemplo:
- API Gateway: Horizontal (múltiples instancias)
- Base de datos: Vertical (servidor potente) + Read Replicas (horizontal para lecturas)
- Workers de procesamiento: Horizontal (escalar según cola)
- Cache (Redis): Vertical hasta 64GB, luego sharding (horizontal)
1.3.6 Recursos Escalabilidad
1.4 🔷 Arquitectura Hexagonal (Ports & Adapters)
Qué: Patrón arquitectónico que aísla la lógica de negocio (Core) de los detalles de implementación (UI, DB, Frameworks) mediante Puertos y Adaptadores.
Objetivo: Permitir que la aplicación sea dirigida por usuarios, programas, pruebas automatizadas o scripts por igual, y ser desarrollada y probada aisladamente de sus dispositivos de ejecución y bases de datos.
1.4.1 Componentes Principales
- Dominio (Core): Entidades y reglas de negocio puras. No depende de nada externo.
- Aplicación (Use Cases): Orquesta el flujo de datos desde/hacia el dominio. Define qué hace el sistema.
- Puertos (Interfaces):
- Primarios (Driver): API pública que expone la aplicación (ej.
IUserService). - Secundarios (Driven): Interfaces que la aplicación necesita (ej.
IUserRepository,IEmailSender).
- Primarios (Driver): API pública que expone la aplicación (ej.
- Adaptadores (Infraestructura): Implementaciones concretas.
- Driver Adapters: Controladores REST, CLI, GUI.
- Driven Adapters: Repositorio SQL, Cliente SMTP.
1.4.2 Diagrama de Dependencias
flowchart TD
subgraph Infrastructure[Capa de Infraestructura]
Controller[REST Controller]
DB[SQL Repository]
Mail[SMTP Service]
end
subgraph Core[Capa de Dominio & Aplicación]
UseCase[Use Case Interactor]
PortIn["Input Port <br> (Interface)"]
PortOut["Output Port <br> (Interface)"]
Entity[Domain Entity]
end
Controller --> PortIn
PortIn -.-> UseCase
UseCase --> Entity
UseCase --> PortOut
DB -.-> PortOut
Mail -.-> PortOut
style Infrastructure fill:#f9f,stroke:#333
style Core fill:#ccf,stroke:#333
Nota: Observa cómo las flechas de dependencia cruzan los límites hacia adentro. La Infraestructura depende del Core/Puertos, nunca al revés.
1.4.3 Relación con Screaming Architecture
La Screaming Architecture (ver abajo) es la forma ideal de organizar las carpetas para implementar Arquitectura Hexagonal, agrupando por contexto y segregando la infraestructura.
1.5 🧅 Onion Architecture (Arquitectura de Cebolla)
Qué: Arquitectura en capas concéntricas donde las dependencias apuntan hacia el centro. El núcleo contiene el dominio, y las capas externas contienen la infraestructura.
Por qué: Garantiza que la lógica de negocio no dependa de detalles técnicos (frameworks, DB, UI). Facilita testing y cambios tecnológicos.
Quién: Propuesta por Jeffrey Palermo (2008).
Cuándo: Aplicaciones empresariales complejas, sistemas de larga vida, cuando la lógica de negocio es crítica.
1.5.1 Capas de la Cebolla (de adentro hacia afuera)
graph TD
subgraph Layer4[Capa 4: Infrastructure]
UI[UI/Controllers]
DB[Database]
External[External Services]
end
subgraph Layer3[Capa 3: Application Services]
UseCase[Use Cases/Application Logic]
end
subgraph Layer2[Capa 2: Domain Services]
DomainService[Domain Services]
end
subgraph Layer1[Capa 1: Domain Model - CORE]
Entity[Entities]
ValueObj[Value Objects]
Aggregates[Aggregates]
end
UI --> UseCase
DB --> UseCase
External --> UseCase
UseCase --> DomainService
DomainService --> Entity
style Layer1 fill:#ffcccc
style Layer2 fill:#ffddaa
style Layer3 fill:#ffffcc
style Layer4 fill:#ccffcc
| Capa | Responsabilidad | Ejemplos | Depende de |
|---|---|---|---|
| 1. Domain Model (Core) | Entidades, Value Objects, reglas de negocio puras | User, Order, Money |
Nada (independiente) |
| 2. Domain Services | Lógica de dominio que no pertenece a una entidad | PricingService, InventoryValidator |
Domain Model |
| 3. Application Services | Casos de uso, orquestación de flujos | CreateOrderUseCase, ProcessPayment |
Domain Services + Domain Model |
| 4. Infrastructure | Implementaciones técnicas (DB, UI, APIs) | PostgresOrderRepository, ExpressController |
Application Services |
1.5.2 Regla de Oro: Dependency Rule
Las dependencias solo pueden apuntar hacia adentro. Las capas internas NO conocen las externas.
Ejemplo:
- ✅
Infrastructure→Application Services→Domain Model - ❌
Domain ModelNO puede depender deInfrastructure
1.5.3 Comparación con Hexagonal
| Aspecto | Hexagonal | Onion |
|---|---|---|
| Concepto clave | Puertos y Adaptadores | Capas concéntricas |
| Dependencias | Hacia el Core | Hacia el centro |
| Organización | Horizontal (Ports/Adapters) | Vertical (Capas) |
| Similitud | Ambas aíslan el dominio de la infraestructura | Ambas aíslan el dominio de la infraestructura |
1.5.4 Ejemplo de Código
1.5.4.1 Capa 1: Domain Model
// domain/entities/Order.ts
export class Order {
constructor(
public readonly id: string,
public readonly items: OrderItem[],
public status: OrderStatus
) {}
calculateTotal(): Money {
return this.items.reduce((sum, item) => sum.add(item.price), Money.zero());
}
canBeCancelled(): boolean {
return this.status === OrderStatus.Pending;
}
}
1.5.4.2 Capa 3: Application Service
// application/use-cases/CreateOrder.ts
export class CreateOrderUseCase {
constructor(
private orderRepository: IOrderRepository, // Puerto (interface)
private pricingService: PricingService
) {}
async execute(request: CreateOrderRequest): Promise<Order> {
const order = new Order(uuid(), request.items, OrderStatus.Pending);
const total = this.pricingService.calculateWithTax(order);
await this.orderRepository.save(order);
return order;
}
}
1.5.4.3 Capa 4: Infrastructure
// infrastructure/repositories/PostgresOrderRepository.ts
export class PostgresOrderRepository implements IOrderRepository {
async save(order: Order): Promise<void> {
await db.query('INSERT INTO orders ...', order);
}
}
1.5.5 Cuándo Usar Onion Architecture
- ✅ Lógica de negocio compleja: Fintech, healthcare, e-commerce
- ✅ Sistemas de larga vida: Proyectos que evolucionarán años
- ✅ Testing crítico: Necesitas testear dominio sin infraestructura
- ⚠️ MVPs simples: Puede ser over-engineering
- ❌ CRUD básicos: Capas tradicionales pueden ser suficientes
1.6 🏛️ Clean Architecture
Qué: Arquitectura propuesta por Robert C. Martin (Uncle Bob) que combina principios de Hexagonal, Onion y otras arquitecturas para crear sistemas independientes de frameworks, UI, DB y agentes externos.
Por qué: Maximiza la independencia del dominio, facilita testing, permite cambiar tecnologías sin afectar la lógica de negocio.
Cuándo: Sistemas empresariales complejos, aplicaciones de larga vida, cuando la lógica de negocio es el activo más valioso.
1.6.1 Los 4 Círculos de Clean Architecture
graph TD
subgraph Circle4[Círculo 4: Frameworks & Drivers - Azul]
Web[Web/UI]
DBImpl[Database Implementation]
Devices[Devices/External APIs]
end
subgraph Circle3[Círculo 3: Interface Adapters - Verde]
Controllers[Controllers]
Presenters[Presenters]
Gateways[Gateways]
end
subgraph Circle2[Círculo 2: Application Business Rules - Amarillo]
UseCases[Use Cases/Interactors]
end
subgraph Circle1[Círculo 1: Enterprise Business Rules - Rojo]
Entities[Entities]
end
Web --> Controllers
DBImpl --> Gateways
Controllers --> UseCases
Gateways --> UseCases
UseCases --> Entities
style Circle1 fill:#ffcccc
style Circle2 fill:#ffffcc
style Circle3 fill:#ccffcc
style Circle4 fill:#ccccff
| Círculo | Nombre | Responsabilidad | Ejemplos |
|---|---|---|---|
| 1 (Centro) | Enterprise Business Rules | Entidades de negocio, reglas críticas | User, Order, Invoice |
| 2 | Application Business Rules | Casos de uso específicos de la aplicación | CreateUser, PlaceOrder, GenerateReport |
| 3 | Interface Adapters | Convertir datos entre casos de uso y frameworks | UserController, OrderPresenter, DatabaseGateway |
| 4 (Exterior) | Frameworks & Drivers | Frameworks, DB, UI, dispositivos | Express, React, PostgreSQL, AWS SDK |
1.6.2 The Dependency Rule (Regla de Dependencia)
El código fuente solo puede apuntar hacia adentro. Nada en un círculo interno puede saber sobre algo en un círculo externo.
Implicaciones:
- ✅ Entities no conocen Use Cases
- ✅ Use Cases no conocen Controllers
- ✅ Use Cases definen interfaces (puertos), Infrastructure las implementa
- ❌ Entities NO pueden importar frameworks
- ❌ Use Cases NO pueden importar Express/React
1.6.3 Flujo de Control vs Flujo de Dependencias
Problema: El Controller (externo) necesita llamar al Use Case (interno), pero el Use Case no puede depender del Controller.
Solución: Dependency Inversion Principle (DIP)
sequenceDiagram
participant Controller
participant UseCase
participant IRepository
participant PostgresRepo
Controller->>UseCase: execute(request)
UseCase->>IRepository: save(entity)
Note over IRepository: Interface definida en Use Case
PostgresRepo->>IRepository: implements
PostgresRepo-->>UseCase: entity saved
UseCase-->>Controller: response
Código:
// Circle 2: Use Case define la interface (Puerto)
export interface IUserRepository {
save(user: User): Promise<void>;
}
export class CreateUserUseCase {
constructor(private userRepo: IUserRepository) {} // Depende de abstracción
async execute(request: CreateUserRequest): Promise<User> {
const user = new User(request.name, request.email);
await this.userRepo.save(user);
return user;
}
}
// Circle 4: Infrastructure implementa la interface
export class PostgresUserRepository implements IUserRepository {
async save(user: User): Promise<void> {
await db.query('INSERT INTO users ...', user);
}
}
1.6.4 Comparación: Clean vs Hexagonal vs Onion
| Aspecto | Clean Architecture | Hexagonal | Onion |
|---|---|---|---|
| Autor | Robert C. Martin | Alistair Cockburn | Jeffrey Palermo |
| Concepto clave | 4 círculos concéntricos | Puertos y Adaptadores | Capas concéntricas |
| Foco principal | Independencia total del dominio | Testability y flexibilidad | Separación por capas |
| Similitud | Todas aíslan el dominio de la infraestructura y aplican Dependency Inversion | Todas aíslan el dominio de la infraestructura y aplican Dependency Inversion | Todas aíslan el dominio de la infraestructura y aplican Dependency Inversion |
1.6.5 Ejemplo Completo: Sistema de Pedidos
1.6.5.1 Circle 1: Entity
// entities/Order.ts
export class Order {
constructor(
public id: string,
public customerId: string,
public items: OrderItem[],
public status: OrderStatus
) {}
calculateTotal(): number {
return this.items.reduce((sum, item) => sum + item.price * item.quantity, 0);
}
}
1.6.5.2 Circle 2: Use Case + Interface
// use-cases/CreateOrder.ts
export interface IOrderRepository {
save(order: Order): Promise<void>;
}
export interface IEmailService {
sendOrderConfirmation(order: Order): Promise<void>;
}
export class CreateOrderUseCase {
constructor(
private orderRepo: IOrderRepository,
private emailService: IEmailService
) {}
async execute(request: CreateOrderRequest): Promise<Order> {
const order = new Order(uuid(), request.customerId, request.items, OrderStatus.Pending);
await this.orderRepo.save(order);
await this.emailService.sendOrderConfirmation(order);
return order;
}
}
1.6.5.3 Circle 3: Controller (Adapter)
// adapters/controllers/OrderController.ts
export class OrderController {
constructor(private createOrderUseCase: CreateOrderUseCase) {}
async create(req: Request, res: Response) {
const request = new CreateOrderRequest(req.body.customerId, req.body.items);
const order = await this.createOrderUseCase.execute(request);
res.json({ orderId: order.id });
}
}
1.6.5.4 Circle 4: Infrastructure
// infrastructure/PostgresOrderRepository.ts
export class PostgresOrderRepository implements IOrderRepository {
async save(order: Order): Promise<void> {
await db.query('INSERT INTO orders (id, customer_id, status) VALUES ($1, $2, $3)',
[order.id, order.customerId, order.status]);
}
}
// infrastructure/SendGridEmailService.ts
export class SendGridEmailService implements IEmailService {
async sendOrderConfirmation(order: Order): Promise<void> {
await sendgrid.send({ to: order.customerId, template: 'order-confirmation' });
}
}
1.6.6 Beneficios de Clean Architecture
| Beneficio | Explicación |
|---|---|
| Independencia de Frameworks | Cambiar de Express a Fastify no afecta Use Cases |
| Testability | Testear Use Cases sin DB/UI (mocks de interfaces) |
| Independencia de UI | Misma lógica para Web, Mobile, CLI |
| Independencia de DB | Cambiar de PostgreSQL a MongoDB sin tocar dominio |
| Independencia de Agentes Externos | APIs de terceros son detalles reemplazables |
1.6.7 Cuándo Usar Clean Architecture
- ✅ Sistemas críticos de negocio: Fintech, healthcare, legal
- ✅ Proyectos de larga vida: 5+ años de evolución
- ✅ Equipos grandes: Múltiples devs trabajando en paralelo
- ✅ Alta cobertura de tests: Testing es prioritario
- ⚠️ MVPs rápidos: Puede ralentizar desarrollo inicial
- ❌ Prototipos desechables: Over-engineering
1.7 🎯 Domain-Driven Design (DDD)
Qué: Enfoque de diseño de software que pone el dominio del negocio en el centro del desarrollo, usando un lenguaje ubicuo (Ubiquitous Language) compartido entre técnicos y expertos del dominio.
Por qué: Sistemas complejos requieren que el código refleje fielmente el negocio. DDD provee patrones tácticos y estratégicos para modelar dominios complejos.
Quién: Propuesto por Eric Evans en su libro "Domain-Driven Design" (2003).
Cuándo: Dominios complejos (fintech, healthcare, logística), sistemas empresariales de larga vida.
1.7.1 Conceptos Estratégicos (Strategic Design)
1.7.1.1 1. Ubiquitous Language (Lenguaje Ubicuo)
Qué: Vocabulario compartido entre desarrolladores y expertos del dominio, usado en código, documentación y conversaciones.
Por qué: Elimina ambigüedades, el código se vuelve auto-documentado.
Ejemplo:
- ❌ Mal:
processData(),handleRequest(),doStuff() - ✅ Bien:
approveInsuranceClaim(),calculatePremium(),issuePolicy()
1.7.1.2 2. Bounded Context (Contexto Delimitado)
Qué: Límite explícito dentro del cual un modelo de dominio es válido. Diferentes contextos pueden tener modelos diferentes para el mismo concepto.
Por qué: Evita que un modelo único intente representar todo (God Model). Permite que equipos trabajen independientemente.
1.7.1.3 Ejemplo: E-commerce
graph LR
subgraph Sales[Sales Context]
SalesCustomer[Customer: name, email, cart]
end
subgraph Shipping[Shipping Context]
ShippingCustomer[Customer: address, phone, delivery_preferences]
end
subgraph Billing[Billing Context]
BillingCustomer[Customer: payment_method, billing_address, credit_limit]
end
Sales -.->|Context Map| Shipping
Sales -.->|Context Map| Billing
style Sales fill:#ffe6e6
style Shipping fill:#e6f3ff
style Billing fill:#e6ffe6
Nota: Customer significa cosas diferentes en cada contexto. No intentar unificar en un solo modelo.
1.7.1.4 3. Context Map (Mapa de Contextos)
Qué: Diagrama que muestra cómo se relacionan los Bounded Contexts.
Patrones de integración:
| Patrón | Qué | Cuándo |
|---|---|---|
| Shared Kernel | Contextos comparten un subconjunto del modelo | Equipos muy coordinados, bajo acoplamiento aceptable |
| Customer/Supplier | Un contexto (Supplier) provee datos al otro (Customer) | Relación upstream/downstream clara |
| Conformist | Customer acepta modelo del Supplier sin traducción | Supplier no puede cambiar (legacy, third-party) |
| Anti-Corruption Layer (ACL) | Traducir modelo externo al propio | Proteger dominio de modelos legacy/externos |
| Published Language | Formato estándar de intercambio (JSON, XML) | Integración entre contextos independientes |
1.7.2 Conceptos Tácticos (Tactical Design)
1.7.2.1 1. Entity (Entidad)
Qué: Objeto con identidad única que persiste en el tiempo, aunque sus atributos cambien.
Ejemplo:
export class User {
constructor(
public readonly id: UserId, // Identidad única
public name: string,
public email: Email
) {}
changeName(newName: string): void {
this.name = newName; // Atributos cambian, identidad no
}
}
1.7.2.2 2. Value Object
Qué: Objeto inmutable sin identidad, definido solo por sus atributos. Dos Value Objects con mismos valores son iguales.
Ejemplo:
export class Money {
constructor(
public readonly amount: number,
public readonly currency: string
) {}
add(other: Money): Money {
if (this.currency !== other.currency) throw new Error('Currency mismatch');
return new Money(this.amount + other.amount, this.currency);
}
equals(other: Money): boolean {
return this.amount === other.amount && this.currency === other.currency;
}
}
Características:
- ✅ Inmutable
- ✅ Sin identidad (comparación por valor)
- ✅ Encapsula validación (
Email,PhoneNumber,Money)
1.7.2.3 3. Aggregate (Agregado)
Qué: Cluster de Entities y Value Objects tratados como una unidad. Tiene un Aggregate Root (entidad raíz) que es el único punto de acceso.
Por qué: Garantiza consistencia, simplifica transacciones.
1.7.2.4 Ejemplo: Order Aggregate
export class Order { // Aggregate Root
constructor(
public readonly id: OrderId,
private items: OrderItem[], // Entities internas
public status: OrderStatus
) {}
addItem(product: Product, quantity: number): void {
// Lógica de negocio: validar stock, calcular precio
this.items.push(new OrderItem(product, quantity));
}
// Solo Order puede modificar OrderItems
removeItem(itemId: string): void {
this.items = this.items.filter(item => item.id !== itemId);
}
calculateTotal(): Money {
return this.items.reduce((sum, item) => sum.add(item.price), Money.zero());
}
}
// OrderItem NO se expone directamente, solo a través de Order
class OrderItem {
constructor(
public readonly id: string,
public product: Product,
public quantity: number
) {}
}
Reglas:
- ✅ Modificaciones solo a través del Aggregate Root
- ✅ Transacciones no cruzan límites de Aggregates
- ✅ Referencias externas solo al Root (por ID)
1.7.2.5 4. Repository
Qué: Abstracción para acceder a Aggregates, simula una colección en memoria.
Por qué: Desacopla dominio de la persistencia.
Ejemplo:
export interface IOrderRepository {
findById(id: OrderId): Promise<Order | null>;
save(order: Order): Promise<void>;
delete(order: Order): Promise<void>;
}
// Uso en Use Case
export class PlaceOrderUseCase {
constructor(private orderRepo: IOrderRepository) {}
async execute(request: PlaceOrderRequest): Promise<void> {
const order = new Order(uuid(), request.items, OrderStatus.Pending);
await this.orderRepo.save(order); // Abstracción, no SQL directo
}
}
1.7.2.6 5. Domain Service
Qué: Lógica de dominio que no pertenece a una Entity o Value Object específico.
Cuándo: Operaciones que involucran múltiples Aggregates o cálculos complejos.
Ejemplo:
export class PricingService {
calculateWithTax(order: Order, customer: Customer): Money {
const subtotal = order.calculateTotal();
const taxRate = customer.country === 'AR' ? 0.21 : 0.15;
return subtotal.multiply(1 + taxRate);
}
}
1.7.2.7 6. Domain Event
Qué: Evento que representa algo significativo que ocurrió en el dominio.
Por qué: Desacopla reacciones (enviar email, actualizar inventario) de la acción principal.
Ejemplo:
export class OrderPlacedEvent {
constructor(
public readonly orderId: string,
public readonly customerId: string,
public readonly total: Money,
public readonly occurredAt: Date
) {}
}
// En el Aggregate
export class Order {
place(): void {
this.status = OrderStatus.Placed;
this.events.push(new OrderPlacedEvent(this.id, this.customerId, this.calculateTotal(), new Date()));
}
}
// Event Handler
export class SendOrderConfirmationHandler {
handle(event: OrderPlacedEvent): void {
emailService.send(event.customerId, 'Order Confirmation', event.orderId);
}
}
1.7.3 Estructura de Carpetas DDD
/src
/sales (Bounded Context)
/domain
/entities
- Order.ts
- Customer.ts
/value-objects
- Money.ts
- Email.ts
/aggregates
- OrderAggregate.ts
/repositories (interfaces)
- IOrderRepository.ts
/services
- PricingService.ts
/events
- OrderPlacedEvent.ts
/application
/use-cases
- PlaceOrder.ts
- CancelOrder.ts
/infrastructure
/repositories
- PostgresOrderRepository.ts
/controllers
- OrderController.ts
/shipping (Bounded Context)
/domain
...
1.7.4 DDD + Clean Architecture
DDD (qué modelar) se combina perfectamente con Clean Architecture (cómo estructurar):
| DDD | Clean Architecture |
|---|---|
| Entities, Value Objects, Aggregates | Circle 1: Enterprise Business Rules |
| Domain Services | Circle 2: Application Business Rules |
| Repositories (interfaces) | Circle 2: Use Cases definen puertos |
| Repositories (implementaciones) | Circle 4: Infrastructure |
1.7.5 Cuándo Usar DDD
- ✅ Dominio complejo: Fintech, healthcare, seguros, logística
- ✅ Larga vida del proyecto: 5+ años
- ✅ Expertos del dominio disponibles: Para definir Ubiquitous Language
- ✅ Múltiples Bounded Contexts: E-commerce (sales, shipping, billing)
- ⚠️ CRUD simples: Over-engineering
- ❌ Prototipos rápidos: Demasiada ceremonia inicial
1.7.6 Recursos DDD
- Domain-Driven Design - Eric Evans
- Implementing Domain-Driven Design - Vaughn Vernon
- DDD Reference - Eric Evans (PDF gratuito)
1.8 📢 Screaming Architecture
Qué: Arquitectura que hace obvio el dominio/propósito de la aplicación desde la estructura de carpetas y nombres, no el framework usado.
Por qué: Cuando mirás la estructura del proyecto, debería "gritar" qué hace la aplicación (ej: healthcare, e-commerce), no qué framework usa (ej: Rails, Angular).
Quién: Acuñado por Robert C. Martin (Uncle Bob)
Cuándo: Todos los proyectos, especialmente aplicaciones domain-driven
Cómo:
- Carpetas de nivel superior representan dominios de negocio, no capas técnicas
- El framework es un detalle, aislado en capa de infraestructura
- Los casos de uso son explícitos y visibles en la estructura
1.8.1 Ejemplo - Sistema de Salud
✅ Screaming Architecture (Grita "Healthcare"):
/src
/patients
/use-cases
- RegisterPatient.ts
- ScheduleAppointment.ts
- UpdateMedicalHistory.ts
/entities
- Patient.ts
- MedicalRecord.ts
/repositories
- IPatientRepository.ts
/appointments
/use-cases
- BookAppointment.ts
- CancelAppointment.ts
/entities
- Appointment.ts
/billing
/use-cases
- GenerateInvoice.ts
- ProcessPayment.ts
/infrastructure # Framework vive acá
/express
/database
/email
❌ Framework-Centric (Grita "Express/MVC"):
/src
/controllers
- PatientController.ts
- AppointmentController.ts
/services
- PatientService.ts
- AppointmentService.ts
/models
- Patient.ts
- Appointment.ts
/views
/routes
1.8.2 Principio Clave
"Your architecture should tell readers about the system, not about the frameworks you used in your system." — Robert C. Martin
1.8.3 Beneficios
| Beneficio | Explicación |
|---|---|
| Claridad de dominio | Nuevos devs entienden el negocio mirando carpetas |
| Independencia de framework | Cambiar de Express a Fastify no afecta estructura core |
| Testability | Casos de uso son testables sin framework |
| Mantenibilidad | Features relacionadas están juntas, no dispersas por capas |
| Onboarding rápido | La estructura documenta el sistema |
1.8.4 Cuándo Aplicar
- ✅ Aplicaciones de negocio complejas: E-commerce, healthcare, fintech
- ✅ Proyectos de larga vida: Sistemas que evolucionarán años
- ✅ Equipos grandes: Múltiples devs trabajando en paralelo
- ⚠️ MVPs simples: Puede ser over-engineering para prototipos
- ⚠️ CRUD básicos: Si solo es ABM, capas tradicionales pueden bastar
1.9 🧩 Patrones de Diseño (Gang of Four)
Qué: Soluciones reutilizables a problemas recurrentes de diseño OOP.
Por qué: No reinventar la rueda, vocabulario común entre developers.
Ver todos los patrones explicados en Refactoring Guru
1.9.1 Patrones Creacionales
| Patrón | Qué | Por qué | Cuándo | Cómo |
|---|---|---|---|---|
| Factory Method | Crea objetos sin especificar clase exacta | Delegar creación a subclases | Crear objetos de familias similares | Interface create(), subclases deciden tipo concreto |
| Abstract Factory | Crea familias de objetos relacionados | Consistencia entre productos | UI con temas (Dark/Light) | Factory retorna conjunto de objetos relacionados |
| Builder | Construye objetos complejos paso a paso | Muchas opciones de configuración | DTOs complejos, requests HTTP | builder.setName().setAge().build() |
| Prototype | Clona objetos existentes | Creación costosa, muchas variaciones | Clonar configuraciones, templates | Implementar clone(), copiar estado |
| Singleton | Garantiza única instancia global | Un punto de acceso (config, logger) | Recursos compartidos únicos | Constructor privado, getInstance() estática |
1.9.2 Patrones Estructurales
| Patrón | Qué | Por qué | Cuándo | Cómo |
|---|---|---|---|---|
| Adapter | Convierte interfaz incompatible | Integrar código legacy/third-party | Librerías externas con APIs distintas | Wrapper que traduce llamadas |
| Bridge | Separa abstracción de implementación | Variar ambas independientemente | UI multiplataforma (misma lógica, distinto render) | Abstracción tiene referencia a implementación |
| Composite | Composición jerárquica (árbol) | Tratar individual y compuesto igual | Menús, file systems, org charts | Interface común, contenedor tiene lista de hijos |
| Decorator | Añade funcionalidades dinámicamente | Extender sin modificar clase | Logging, caching, autenticación en requests | Wrapper que implementa misma interface |
| Facade | Interfaz simplificada a subsistema complejo | Ocultar complejidad interna | APIs complejas (AWS SDK → helper simple) | Clase que expone métodos high-level |
| Flyweight | Minimiza memoria compartiendo datos | Muchos objetos similares | Renderizar 10k íconos (compartir imagen) | Separar estado intrínseco (compartido) de extrínseco |
| Proxy | Controla acceso a objeto | Lazy loading, caching, seguridad | Imágenes pesadas, permisos | Proxy implementa misma interface, delega a real |
1.9.3 Patrones Comportamiento
| Patrón | Qué | Por qué | Cuándo | Cómo |
|---|---|---|---|---|
| Strategy | Familia de algoritmos intercambiables | Cambiar comportamiento en runtime | Ordenamiento (bubble, quick, merge) | Interface execute(), contexto recibe estrategia |
| Observer | Notifica cambios a múltiples objetos | Reacción automática ante eventos | UI reactiva (state → re-render) | Sujeto tiene lista de observadores, notify() |
| Command | Encapsula solicitud como objeto | Parametrizar, deshacer, encolar | Undo/Redo, job queues | Interface execute(), receiver realiza acción |
| State | Cambia comportamiento según estado interno | Manejar estados complejos | Workflows (draft→review→published) | Context delega a objeto State actual |
| Template Method | Esqueleto de algoritmo, pasos personalizables | Reutilizar estructura | Conectar a DB (común: connect, query, close) | Clase abstracta define pasos, subclases implementan |
| Chain of Responsibility | Pasa solicitud por cadena de manejadores | Procesamiento flexible | Middleware (auth → logging → handler) | Cada handler procesa o pasa al siguiente |
| Visitor | Añade operaciones sin modificar clases | Operaciones sobre estructura compleja | Export (HTML, PDF, JSON) de mismo árbol | Interface visit(), elementos aceptan visitor |
| Mediator | Centraliza comunicación entre objetos | Reducir acoplamiento | UI forms (campos se habilitan según otros) | Componentes se comunican vía mediador |
| Memento | Guarda y restaura estado | Undo/Redo, snapshots | Editores, juegos | Originator crea memento, caretaker lo guarda |
| Iterator | Recorre elementos sin exponer estructura | Acceso secuencial estándar | Colecciones custom | Interface next(), hasNext() |
1.10 🏗️ Patrones Arquitectónicos Avanzados
| Patrón | Qué | Por qué | Cuándo | Dónde | Cómo | Herramientas |
|---|---|---|---|---|---|---|
| Event Sourcing | Persistir cambios como secuencia de eventos inmutables | Auditoría completa, time travel, proyecciones | Sistemas financieros, compliance | Event Store | Cada cambio → evento (OrderPlaced), reconstruir estado reproduciendo |
EventStore, Kafka |
| CQRS | Separar modelos de lectura (Query) y escritura (Command) | Optimizar cada uno independientemente | Escrituras complejas + lecturas frecuentes | APIs de alta carga | Commands modifican, Queries leen vistas desnormalizadas | MediatR, Axon |
| Saga Pattern | Transacciones distribuidas con compensación | Consistencia eventual entre microservicios | Workflows multi-servicio (order→payment→shipping) | Microservicios | Orquestada (coordinador) o Coreografiada (eventos) | Temporal, Camunda |
| Circuit Breaker | Prevenir cascadas de fallos | Sistema resiliente ante servicios caídos | Llamadas a APIs externas inestables | Clientes HTTP | Closed→Open (tras N fallos)→Half-Open (test)→Closed | Resilience4j, Hystrix |
| Strangler Fig | Migrar legacy gradualmente | Reemplazo sin big bang | Modernizar monolito → microservicios | Proxy/Gateway | Nuevo código intercepta requests, delega a legacy o nuevo | nginx, Envoy |
| API Gateway | Punto de entrada único para múltiples servicios | Routing, auth, rate limiting centralizado | Microservicios con necesidades cross-cutting | Edge de la red | Gateway maneja auth, transforma requests, agrega respuestas | Kong, AWS API Gateway |
| Bulkhead Pattern | Aislar recursos para prevenir fallo total | Un servicio lento no consume todo | Pools de conexiones, threads | Thread pools, circuit breakers | Separar pools por tipo de operación | Resilience4j Bulkhead |
1.11 🎭 Finite State Machines (FSM)
Qué: Modelar sistemas con estados finitos y transiciones explícitas.
Por qué: Elimina bugs de estados inválidos, documentación visual ejecutable.
Cuándo: Workflows complejos (pedidos, aprobaciones, onboarding), procesos con múltiples actores.
| Concepto | Qué | Ejemplo |
|---|---|---|
| Estados | Conjunto finito de condiciones | Pending, Paid, Shipped, Delivered, Cancelled |
| Transiciones | Cambios entre estados con condiciones | Pending → Paid (al recibir pago) |
| Eventos | Triggers que activan transiciones | PaymentReceived, ShipmentDispatched |
| Guards | Condiciones para permitir transición | Paid → Shipped solo si inventory > 0 && address_valid |
| Acciones | Side effects al entrar/salir | Al entrar en Paid: enviar email, decrementar stock |
Herramientas: XState, Spring State Machine, Python transitions
Ejemplo XState:
import { createMachine } from 'xstate';
const orderMachine = createMachine({
id: 'order',
initial: 'pending',
states: {
pending: {
on: { PAYMENT_RECEIVED: 'paid' }
},
paid: {
on: {
SHIP: {
target: 'shipped',
cond: 'hasInventory'
}
},
entry: 'sendConfirmationEmail'
},
shipped: {
on: { DELIVER: 'delivered' }
},
delivered: { type: 'final' }
}
});
1.12 📐 Principios de Arquitectura
Nota: Estos principios se aplican a nivel arquitectónico. Para ver su definición fundamental y aplicación a nivel de código, consultar Reglas Generales de Código.
| Principio | Qué | Por qué |
|---|---|---|
| Separation of Concerns | Separar responsabilidades en módulos/capas | Mantenimiento, testing, escalabilidad |
| Single Responsibility | Cada módulo/clase tiene una razón para cambiar | Cohesión alta, bajo acoplamiento |
| Dependency Inversion | Depender de abstracciones, no concreciones | Testability, flexibilidad |
| Open/Closed | Abierto a extensión, cerrado a modificación | Agregar features sin romper existente |
| Least Knowledge | Módulos conocen lo mínimo necesario | Reduce fragilidad |
1.13 🗂️ Distribución de Carpetas
| Enfoque | Qué | Cuándo | Ejemplo |
|---|---|---|---|
| Por tipo | Separar por categoría técnica | Proyectos pequeños | /controllers, /services, /models |
| Por feature | Agrupar por funcionalidad | Proyectos medianos/grandes | /auth, /dashboard, /billing |
| Por dominio | Agrupar por contexto de negocio | DDD | /sales, /inventory, /shipping |
| Monorepo modular | Múltiples apps/packages en un repo | Microservicios, libs compartidas | /apps/web, /apps/api, /packages/ui |
1.14 🚫 Anti-patrones Arquitectónicos
| Anti-patrón | Problema | Solución |
|---|---|---|
| Big Ball of Mud | Sin estructura clara, todo acoplado | Refactorizar incremental, definir módulos |
| God Object | Una clase hace todo | Aplicar SRP, extraer responsabilidades |
| Spaghetti Code | Flujo imposible de seguir | Linealizar, extraer funciones, FSM |
| Golden Hammer | Usar misma solución para todo | Evaluar trade-offs por caso |
| Premature Generalization | Abstracciones sin casos de uso reales | Esperar 3 casos antes de generalizar |