Arquitectura de la Aplicación
Cómo organizar el código dentro de cada aplicación: slicing horizontal (by layer) vs. vertical (by feature), y arquitecturas domain-centric —Hexagonal, Onion, Clean— para que las reglas de negocio no queden acopladas a la infraestructura.
Una vez definida la estructura del sistema, todavía queda una pregunta:
¿Cómo organizamos el código dentro de cada aplicación o módulo?
Aquí aparecen dos ideas diferentes: Slicing Strategies y Domain-Centric Architecture.
Slicing Strategies
Una estrategia de slicing decide cómo dividir el código: de forma Horizontal o Vertical.
By Layer — Horizontal Slicing
Organiza el código según responsabilidades técnicas:
payments/
├── controllers/
├── services/
├── repositories/
├── entities/
└── dto/
El flujo típico de una request es:
flowchart TD
H["HTTP"] --> C["Controller"] --> S["Service"] --> R["Repository"] --> DB[("Database")]Supongamos que necesitamos agregar Refund Payment. Probablemente terminemos modificando RefundController, PaymentService, PaymentRepository, PaymentEntity y RefundDTO. La funcionalidad queda repartida horizontalmente.
Ventaja
Es simple de entender y puede funcionar muy bien en aplicaciones pequeñas.
Problema
A medida que crecen las features, disminuye la feature locality:
flowchart TD F["Payment feature"] --> C["controllers"] F --> S["services"] F --> R["repositories"] F --> E["entities"] F --> D["dto"]
Un cambio de negocio obliga a recorrer muchas partes de la estructura.
By Feature / Vertical Slice
Vertical Slice reorganiza el código en torno a funcionalidades o use cases:
payments/
│
├── authorize-payment/
│ ├── endpoint
│ ├── validator
│ ├── handler
│ └── repository
│
├── capture-payment/
│ ├── endpoint
│ ├── validator
│ ├── handler
│ └── repository
│
└── refund-payment/
├── endpoint
├── validator
├── handler
└── repository
Una feature puede atravesar todas las capas necesarias:
flowchart LR RP["Refund Payment"] --> H["HTTP"] RP --> V["Validation"] RP --> AL["Application Logic"] RP --> DL["Domain Logic"] RP --> P["Persistence"]
La ventaja principal es la localidad del cambio: cuando cambia Refund Payment, gran parte del trabajo queda concentrada en un mismo lugar.
Vertical Slice no es CQRS
Pueden combinarse Vertical Slice, CQRS y DDD, pero son conceptos diferentes. Vertical Slice describe principalmente cómo organizamos el código. CQRS separa responsabilidades de commands y queries.
Vertical Slice tampoco reemplaza la inversión de dependencias
Una aplicación puede combinar Vertical Slice con Hexagonal Architecture:
refund-payment/
├── RefundPaymentHandler
├── RefundPaymentValidator
├── RefundPaymentController
└── RefundPaymentPort
mientras los adapters concretos quedan en infrastructure.
Referencia principal
Jimmy Bogard popularizó el término Vertical Slice Architecture para referirse a organizar las aplicaciones en torno a funcionalidades en lugar de capas técnicas. Vertical Slice Architecture — Jimmy Bogard.
Domain-Centric Architecture
Las estrategias de slicing responden “¿cómo organizamos el código?”. Hexagonal, Onion y Clean ponen el énfasis en otra pregunta:
¿Cómo evitamos que las reglas del negocio queden acopladas a los detalles de infraestructura?
flowchart TD I["Infrastructure"] --> A["Application"] --> D["Domain"]
El objetivo es que el core sea relativamente independiente de Database, HTTP, Framework, Message Broker, Cloud Provider y External APIs.
Hexagonal Architecture
Las arquitecturas domain-centric son, en el fondo, el Principio de Inversión de Dependencias aplicado a la escala de una aplicación entera: el dominio define los puertos y la infraestructura los implementa, de modo que la flecha de dependencia apunta siempre hacia adentro.
También llamada Puertos y Adaptadores.
flowchart TD HTTP["HTTP"] --> AD["Adapter"] --> IP["Input Port"] --> App["Application"] --> Dom["Domain"] --> OP["Output Port"] OP --> PG["PostgreSQL Adapter"] OP --> PP["Payment Provider Adapter"]
El core define puertos; los adapters implementan esos puertos:
PaymentRepository
▲
│ implements
│
PostgresPaymentRepository
Esto evita que el dominio conozca PostgreSQL.
Ejemplo
Domain
└── PaymentRepository
Infrastructure
└── PostgresPaymentRepository
La aplicación puede reemplazar PostgreSQL por otro almacenamiento sin modificar las reglas principales del dominio.
Onion Architecture
La idea fundamental es que las dependencias apunten hacia el centro:
flowchart TD
subgraph Infra["Infrastructure"]
subgraph App["Application"]
subgraph Dom["Domain"]
Core["Domain core"]
end
end
endEl domain es el núcleo.
Clean Architecture
Clean Architecture propone un enfoque similar:
flowchart TD FD["Frameworks & Drivers"] --> IA["Interface Adapters"] --> UC["Application / Use Cases"] --> BR["Business Rules"]
Su regla fundamental es:
Las dependencias deben apuntar hacia adentro.
El dominio no debería depender de Spring, Hibernate, PostgreSQL, Kafka, AWS o HTTP. Esas tecnologías pueden depender de las abstracciones definidas por el core.
Hexagonal vs. Onion vs. Clean
Son conceptualmente cercanas:
| Estilo | Idea central |
|---|---|
| Hexagonal | Puertos y Adaptadores |
| Onion | Las dependencias apuntan hacia adentro |
| Clean | Regla de dependencia + casos de uso + límites |
No conviene tratarlas como tres soluciones completamente diferentes. En una aplicación real pueden incluso combinarse ideas de las tres.
Cuándo usar Domain-Centric Architecture
Tiene mucho sentido cuando:
- el dominio tiene reglas complejas;
- el sistema debe sobrevivir a cambios de infraestructura;
- se quiere testear la lógica de negocio de forma aislada;
- existen múltiples adaptadores;
- la aplicación tendrá una vida larga.
Cuándo evitarla
Para una aplicación muy sencilla (GET /products, POST /products), introducir cinco capas, múltiples puertos y una estructura enterprise puede agregar complejidad sin beneficio suficiente.
Tecnologías habituales
Este enfoque aparece frecuentemente en Spring Boot, .NET, NestJS, Kotlin, Java y TypeScript. El framework no determina la arquitectura.
