Onion Architecture

Idea y contexto

Onion Architecture organiza dependencias alrededor de un modelo de dominio central. Jeffrey Palermo la presenta en 2008 como una aproximación para aplicaciones empresariales duraderas y comportamiento complejo, con infraestructura situada afuera. El núcleo no conoce database, ORM, UI ni frameworks externos. La representación por círculos comunica dependencias hacia el interior, no cuatro carpetas obligatorias. Formulación original, parte 1.

El énfasis en el dominio es útil cuando sus reglas merecen independencia respecto de mecanismos técnicos. Una herramienta pequeña de operaciones CRUD puede no necesitar un modelo rico ni múltiples fronteras de abstracción. «Onion» no convierte ese costo adicional en valor automático.

Modelo conceptual

Dependencias de código, representación posible:
 
Infrastructure / UI
        ↓
Application Services
        ↓
Domain Services
        ↓
Domain Model

La flecha indica que una zona exterior puede conocer una interior; no que cada petición deba recorrer todos esos pasos. El número de zonas y la existencia de Domain Services separados dependen del problema.

En un ejemplo propio de aprobaciones, el dominio conserva reglas sobre quién puede aprobar y qué transiciones son válidas. Application Services coordinan obtener una solicitud, aplicar la operación y registrar el resultado. Infrastructure implementa acceso al almacenamiento; UI traduce una petición externa. Una clase usada para guardar filas no se convierte por eso en Domain Model.

No basta con mover entidades del ORM a Domain. Si sus métodos, herencia o anotaciones obligatorias requieren el framework de persistencia, hay una decisión externa introducida en el núcleo. La revisión necesita examinar qué comportamiento y dependencias posee el modelo.

Contratos e inversión de dependencias

Supongamos que el caso de uso necesita cargar una solicitud. El contrato se define en la zona interior que expresa esa necesidad; el adaptador lo implementa afuera. Palermo muestra interfaces del application core implementadas por colaboradores externos en su ejemplo de la parte 2.

En nuestro diseño, el núcleo no importa SqlSolicitudStore ni una sesión de un ORM. El adaptador conoce el contrato del núcleo y traduce la operación al mecanismo real. El ensamblaje concreto se realiza fuera de las reglas de dominio.

Un contrato puede estar cerca del dominio o de la aplicación según a quién sirve. No es necesario colocar cada interfaz en el círculo más central. Un contrato para enviar un recibo de una operación puede corresponder a la coordinación de aplicación; incluirlo en una entidad solo para que quede «más adentro» mezcla responsabilidades.

La inversión protege dependencias de código. En runtime, Application Services pueden invocar persistencia y esperar su resultado. La regla no prohíbe esa interacción: exige que las políticas internas no conozcan el detalle que la satisface.

Servicios de aplicación y dominio

En nuestro ejemplo, decidir si una solicitud cumple condiciones de aprobación es una regla de negocio. Abrir una transacción, cargar datos y presentar el resultado son tareas de coordinación o infraestructura. Ubicar todo en un único ApplicationService puede dejar el dominio como un contenedor de datos; trasladar toda coordinación a las entidades puede introducir dependencias externas.

Un Domain Service puede representar una regla que no pertenece naturalmente a una sola entidad. No debe convertirse en un requisito ceremonial para cada operación. El criterio de ubicación es el significado de la responsabilidad y los datos que necesita, no completar una estructura predeterminada.

Las garantías transaccionales también necesitan un contrato explícito. Un método Save que no define conflictos ni atomicidad puede resultar insuficiente para preservar una invariante. El dominio protegido no corrige por sí solo una carrera entre dos ejecuciones.

Pruebas y costos

Podemos probar la transición de aprobación con estados y valores del dominio, sin levantar UI o almacenamiento. Una prueba de Application Services puede observar qué sucede cuando no se encuentra la solicitud o falla una operación externa. Después, pruebas del adaptador real deben confirmar que el mapping y las garantías de almacenamiento satisfacen el contrato.

Aislar el núcleo reduce dependencias para ciertas pruebas, pero no elimina pruebas de integración. Tampoco justifica una interfaz por cada clase ni una réplica completa de la API del proveedor: ambos pueden encarecer mantenimiento sin proteger una política relevante.

El costo aparece en traducciones, ensamblaje, contratos y disciplina para evitar imports exteriores. Si una modificación sencilla exige atravesar numerosos wrappers sin aislar decisiones, conviene simplificar. Si un cambio tecnológico exige modificar reglas de negocio que deberían permanecer estables, conviene revisar filtraciones.

Relaciones y composición

ports-and-adapters enfatiza interacciones inside/outside; Onion hace visible un dominio central rodeado por capas de comportamiento. clean-architecture distingue políticas de negocio y casos de uso con su Dependency Rule. El parecido es fuerte, pero no obliga a tratar sus vocabularios como idénticos.

Una organización layered-architecture puede aplicar inversión de dependencias y coincidir con parte de este modelo. vertical-slice-architecture puede agrupar Application Services por caso de uso conservando un dominio protegido. La combinación debe describirse como decisiones concretas, no como una arquitectura garantizada por dos etiquetas.

En un modular-monolith, cada módulo puede tener su propio núcleo y adaptadores. Una única carpeta Domain compartida por todos los módulos no establece ownership: incluso con dependencias inward, pueden existir reglas mezcladas y cambios coordinados. internal-structure conecta esas diferencias de organización con los perfiles del sistema.