La arquitectura limpia (Clean Architecture, Robert C. Martin, 2012) y la arquitectura cebolla (Onion Architecture, Jeffrey Palermo, 2008) son dos formulaciones, muy emparentadas con la hexagonal, de una misma gran idea: el dominio debe ser independiente de todo lo demás, y las dependencias deben apuntar siempre hacia el interior. Ambas representan la aplicación como círculos concéntricos, donde el centro alberga las reglas de negocio más estables y el exterior, los detalles más volátiles (frameworks, bases de datos, interfaces). Entender estos estilos es clave para construir sistemas que envejezcan bien, porque permiten cambiar la tecnología sin reescribir el negocio. En esta lección estudiaremos la regla de dependencia, los círculos concéntricos, compararemos Clean, Onion y Hexagonal, y veremos una estructura de paquetes concreta.

Contenido

  1. La idea común: dependencias hacia el interior
  2. La regla de dependencia
  3. Los círculos concéntricos (Clean Architecture)
  4. La arquitectura cebolla (Onion)
  5. Comparación: Clean vs Onion vs Hexagonal
  6. Estructura de paquetes de ejemplo
  7. Inversión de dependencias en acción
  8. Errores comunes y consejos
  9. Ejercicios
  10. Conclusión

  1. La idea común: dependencias hacia el interior

Las tres arquitecturas (Hexagonal, Onion y Clean) comparten un mismo principio rector:

  • En el centro está el dominio (entidades y reglas de negocio): lo más estable y valioso.
  • En el exterior están los detalles (UI, BD, frameworks, dispositivos): lo más volátil.
  • Nada del interior conoce el exterior. El dominio no sabe que existe una base de datos, un framework web o un servicio de correo.

Esto produce sistemas donde lo que más cambia (la tecnología) no arrastra a lo que menos debería cambiar (las reglas de negocio).

  1. La regla de dependencia

La regla de dependencia (The Dependency Rule) es el corazón de Clean Architecture y se enuncia así:

Las dependencias del código fuente solo pueden apuntar hacia adentro, hacia las políticas de más alto nivel.

graph LR
    UI[Frameworks y Drivers] --> IA[Adaptadores de Interfaz]
    IA --> CU[Casos de Uso]
    CU --> ENT[Entidades]
    style ENT fill:#cde
    style UI fill:#fdd

Consecuencias prácticas:

  • Un círculo interior no puede nombrar nada de un círculo exterior. Las entidades no conocen los casos de uso; los casos de uso no conocen los controladores.
  • Lo que cruza la frontera hacia adentro lo hace mediante abstracciones (interfaces) declaradas en el interior. Es, de nuevo, la inversión de dependencias.
  • Los datos que cruzan fronteras son estructuras simples (DTOs), nunca entidades de un framework externo.

  1. Los círculos concéntricos (Clean Architecture)

Clean Architecture propone (al menos) cuatro anillos, de dentro hacia fuera:

Anillo Contenido Estabilidad Conoce a...
1. Entidades Reglas de negocio de toda la empresa Máxima Nada
2. Casos de uso Reglas de negocio de la aplicación Alta Entidades
3. Adaptadores de interfaz Controladores, presentadores, gateways Media Casos de uso (vía interfaces)
4. Frameworks y drivers Web, BD, dispositivos Mínima Adaptadores
graph TB
    subgraph Anillo4[4 - Frameworks y Drivers]
        subgraph Anillo3[3 - Adaptadores de Interfaz]
            subgraph Anillo2[2 - Casos de Uso]
                Anillo1[1 - Entidades]
            end
        end
    end
  • Entidades: objetos con las reglas más generales y duraderas (p. ej., una Poliza y su invariante). Sobrevivirían aunque cambiara toda la aplicación.
  • Casos de uso: orquestan las entidades para cumplir una funcionalidad concreta de esta aplicación (p. ej., "contratar póliza").
  • Adaptadores de interfaz: convierten datos entre el formato de los casos de uso y el de la tecnología exterior (controladores, presentadores, repositorios).
  • Frameworks y drivers: Spring, JPA, el navegador, la BD. Detalles reemplazables.

  1. La arquitectura cebolla (Onion)

La arquitectura cebolla de Palermo es anterior y muy similar. Sus capas, de dentro hacia fuera:

Capa Contenido
Modelo de dominio Entidades y objetos de valor
Servicios de dominio Reglas e interfaces (incluidas las de repositorio)
Servicios de aplicación Orquestación de casos de uso
Infraestructura / UI / Tests Implementaciones concretas
graph TB
    subgraph Infra[Infraestructura / UI / Tests]
        subgraph SApp[Servicios de Aplicación]
            subgraph SDom[Servicios de Dominio + Interfaces]
                Modelo[Modelo de Dominio]
            end
        end
    end

El rasgo más característico de Onion: las interfaces de repositorio se definen en el dominio, y la infraestructura las implementa. De nuevo, inversión de dependencias. Onion popularizó la idea de que la infraestructura es un detalle del anillo más externo, igual que la UI.

  1. Comparación: Clean vs Onion vs Hexagonal

Aspecto Hexagonal Onion Clean
Autor / año Cockburn, 2005 Palermo, 2008 Martin, 2012
Metáfora Hexágono con lados Capas de cebolla Círculos concéntricos
Centro Dominio + aplicación Modelo de dominio Entidades
Frontera externa Puertos y adaptadores Capa de infraestructura Frameworks y drivers
Regla esencial Adaptadores dependen del núcleo Dependencias hacia el centro Dependencias hacia adentro
Énfasis distintivo Puertos primarios/secundarios y testabilidad Infra como detalle externo Separación entidades vs casos de uso

La conclusión más importante: son la misma idea con distinto vocabulario y distinto énfasis. Las tres colocan el dominio en el centro, prohíben que el centro dependa del exterior y resuelven el cruce de fronteras con inversión de dependencias. Hexagonal pone el foco en los puertos y adaptadores; Onion, en que la infraestructura es periférica; Clean, en distinguir las reglas de empresa (entidades) de las reglas de aplicación (casos de uso). Equivalencias útiles:

  • Puerto secundario (Hexagonal) ≈ interfaz de repositorio en el dominio (Onion) ≈ gateway en adaptadores de interfaz (Clean).
  • Adaptador primario (Hexagonal) ≈ controlador en adaptadores de interfaz (Clean).

  1. Estructura de paquetes de ejemplo

Una organización de paquetes habitual que materializa estas ideas en Java:

com.fiatc.seguros
├── domain                      # ANILLO 1-2: dominio puro, sin frameworks
│   ├── model
│   │   └── Poliza.java         # Entidad con sus invariantes
│   ├── service
│   │   └── TarificadorRiesgo.java   # Servicio de dominio
│   └── repository
│       └── RepositorioPolizas.java  # INTERFAZ (la define el dominio)
│
├── application                 # ANILLO 2: casos de uso
│   └── usecase
│       └── ContratarPolizaUseCase.java
│
├── infrastructure              # ANILLO 4: detalles reemplazables
│   ├── persistence
│   │   ├── PolizaEntity.java          # Entidad JPA (NO es la del dominio)
│   │   └── RepositorioPolizasJpa.java # IMPLEMENTA la interfaz del dominio
│   └── config
│       └── BeanConfig.java            # Cableado / inyección
│
└── interfaces                  # ANILLO 3: adaptadores de entrada
    └── rest
        └── PolizaController.java      # Traduce HTTP -> caso de uso

Puntos a destacar de la estructura:

  • domain no importa nada de infrastructure ni de interfaces. Esa es la verificación clave: si lo hiciera, habrías roto la regla de dependencia.
  • La interfaz RepositorioPolizas vive en domain.repository, pero su implementación RepositorioPolizasJpa vive en infrastructure. El flujo de control va del dominio a la infraestructura; la dependencia de código, al revés.
  • PolizaEntity (JPA) y Poliza (dominio) son clases distintas. Se mapean entre sí. Así, una anotación de JPA nunca contamina el dominio.

  1. Inversión de dependencias en acción

Veamos cómo el caso de uso (interior) usa la infraestructura (exterior) sin depender de ella.

// ANILLO 2 (application): el caso de uso solo conoce la INTERFAZ del dominio
package com.fiatc.seguros.application.usecase;

import com.fiatc.seguros.domain.model.Poliza;
import com.fiatc.seguros.domain.repository.RepositorioPolizas; // interfaz interior
import com.fiatc.seguros.domain.service.TarificadorRiesgo;

public class ContratarPolizaUseCase {

    private final RepositorioPolizas repositorio; // abstracción del dominio
    private final TarificadorRiesgo tarificador;

    public ContratarPolizaUseCase(RepositorioPolizas r, TarificadorRiesgo t) {
        this.repositorio = r; this.tarificador = t;
    }

    public Poliza ejecutar(String cliente, String riesgo) {
        double prima = tarificador.calcularPrima(riesgo);   // regla de negocio
        Poliza poliza = new Poliza(cliente, riesgo, prima); // entidad valida invariante
        return repositorio.guardar(poliza);                 // llama hacia "afuera" vía interfaz
    }
}
// ANILLO 4 (infrastructure): implementa la interfaz; depende HACIA ADENTRO
package com.fiatc.seguros.infrastructure.persistence;

import com.fiatc.seguros.domain.model.Poliza;
import com.fiatc.seguros.domain.repository.RepositorioPolizas;
import org.springframework.stereotype.Repository;

@Repository
class RepositorioPolizasJpa implements RepositorioPolizas {
    private final JpaPolizaDao dao;
    RepositorioPolizasJpa(JpaPolizaDao dao) { this.dao = dao; }

    @Override public Poliza guardar(Poliza poliza) {
        dao.save(PolizaEntity.desde(poliza)); // mapeo dominio -> entidad JPA
        return poliza;
    }
}

El razonamiento clave: el import cruza la frontera solo en una dirección. infrastructure importa de domain (hacia adentro, permitido). domain nunca importa de infrastructure (hacia afuera, prohibido). Esa asimetría es exactamente la regla de dependencia.

// Verificación automática con ArchUnit: el dominio no debe depender de infraestructura
@Test
void el_dominio_no_depende_de_la_infraestructura() {
    noClasses().that().resideInAPackage("..domain..")
        .should().dependOnClassesThat().resideInAPackage("..infrastructure..")
        .check(new ClassFileImporter().importPackages("com.fiatc.seguros"));
}

Este test falla la compilación de la arquitectura si alguien introduce una dependencia prohibida. Es la mejor defensa para que la regla no se erosione con el tiempo.

  1. Errores Comunes y Consejos

  • Dejar que el dominio importe el framework. El error número uno. El anillo interior debe ser Java puro.
  • Reutilizar la entidad JPA como entidad de dominio. Tienta por ahorrar mapeo, pero acopla el dominio a la persistencia. Mantenlas separadas.
  • Crear capas vacías por dogma. Si un caso de uso solo reenvía a un repositorio (sin reglas), revisa si aporta valor; no caigas en la "capa sumidero" disfrazada.
  • Confundir flujo de control con dependencia de código. El control va del centro al exterior (el caso de uso llama a guardar); la dependencia de código va del exterior al centro (la infra implementa la interfaz).
  • Aplicar la ceremonia completa a un CRUD trivial. Estos estilos brillan con dominios ricos en reglas; en un CRUD simple pueden ser sobreingeniería.
  • Consejo: automatiza la verificación de fronteras con ArchUnit o el sistema de módulos; la disciplina manual no escala.

  1. Ejercicios

Ejercicio 1. Indica, para cada dependencia, si está permitida por la regla de dependencia: (a) application importa domain. (b) domain importa infrastructure. (c) interfaces importa application. (d) domain importa org.springframework.

Ejercicio 2. Establece la equivalencia entre estos términos de las tres arquitecturas: "puerto secundario", "interfaz de repositorio en el dominio", "gateway".

Ejercicio 3. Un compañero coloca la interfaz RepositorioPolizas en el paquete infrastructure. Explica por qué viola la regla de dependencia y dónde debería ir.

Soluciones

Solución 1. (a) Permitida (hacia adentro). (b) Prohibida (el dominio apuntaría hacia afuera). (c) Permitida (hacia adentro). (d) Prohibida (el dominio no puede depender de un framework externo).

Solución 2. Los tres designan el mismo concepto: una abstracción que el núcleo declara para hablar con el exterior (típicamente persistencia o servicios externos), implementada en el anillo más externo. "Puerto secundario" es el término hexagonal; "interfaz de repositorio en el dominio", el de Onion; "gateway", el de Clean.

Solución 3. Si la interfaz vive en infrastructure, entonces application/domain (interior) tendrían que importar infrastructure (exterior) para usarla, lo que invierte la dirección permitida de las dependencias. La interfaz debe vivir en el dominio (p. ej. domain.repository), y solo su implementación en infrastructure.

  1. Conclusión

Clean y Onion son, junto con la hexagonal, expresiones de una misma verdad arquitectónica: pon el dominio en el centro y haz que todas las dependencias apunten hacia adentro. Hemos visto la regla de dependencia, los círculos concéntricos de Clean, las capas de Onion, su equivalencia con los puertos y adaptadores hexagonales, y una estructura de paquetes verificable con ArchUnit que mantiene el dominio libre de frameworks. Con esto cerramos el recorrido por los estilos que protegen el núcleo de negocio. A partir de aquí, el curso avanza hacia los estilos distribuidos y orientados a eventos —microservicios, mensajería, CQRS y event sourcing—, donde estas mismas reglas de dependencia se aplican ahora a través de fronteras de red.

Curso de Arquitectura de Aplicaciones

Módulo 1: Fundamentos de la Arquitectura de Aplicaciones

Módulo 2: Principios y Tácticas de Diseño

Módulo 3: Estilos y Patrones Arquitectónicos

Módulo 4: Arquitecturas Distribuidas y Microservicios

Módulo 5: Arquitecturas Dirigidas por Eventos y Mensajería

Módulo 6: Diseño Dirigido por el Dominio (DDD)

Módulo 7: Datos y Persistencia

Módulo 8: Arquitectura en la Nube y Despliegue

Módulo 9: Calidad, Seguridad y Observabilidad

Módulo 10: Evolución, Gobernanza y Casos Prácticos

© Copyright 2026. Todos los derechos reservados