L'arquitectura neta (Clean Architecture, Robert C. Martin, 2012) i l'arquitectura ceba (Onion Architecture, Jeffrey Palermo, 2008) són dues formulacions, molt emparentades amb l'hexagonal, d'una mateixa gran idea: el domini ha de ser independent de tota la resta, i les dependències han d'apuntar sempre cap a l'interior. Totes dues representen l'aplicació com a cercles concèntrics, on el centre allotja les regles de negoci més estables i l'exterior, els detalls més volàtils (frameworks, bases de dades, interfícies). Entendre aquests estils és clau per construir sistemes que envelleixin bé, perquè permeten canviar la tecnologia sense reescriure el negoci. En aquesta lliçó estudiarem la regla de dependència, els cercles concèntrics, compararem Clean, Onion i Hexagonal, i veurem una estructura de paquets concreta.

Contingut

  1. La idea comuna: dependències cap a l'interior
  2. La regla de dependència
  3. Els cercles concèntrics (Clean Architecture)
  4. L'arquitectura ceba (Onion)
  5. Comparació: Clean vs Onion vs Hexagonal
  6. Estructura de paquets d'exemple
  7. Inversió de dependències en acció
  8. Errors comuns i consells
  9. Exercicis
  10. Conclusió

  1. La idea comuna: dependències cap a l'interior

Les tres arquitectures (Hexagonal, Onion i Clean) comparteixen un mateix principi rector:

  • En el centre hi ha el domini (entitats i regles de negoci): el més estable i valuós.
  • En l'exterior hi ha els detalls (UI, BD, frameworks, dispositius): el més volàtil.
  • Res de l'interior coneix l'exterior. El domini no sap que existeix una base de dades, un framework web o un servei de correu.

Això produeix sistemes on el que més canvia (la tecnologia) no arrossega el que menys hauria de canviar (les regles de negoci).

  1. La regla de dependència

La regla de dependència (The Dependency Rule) és el cor de Clean Architecture i s'enuncia així:

Les dependències del codi font només poden apuntar cap endins, cap a les polítiques de més alt nivell.

graph LR
    UI[Frameworks i Drivers] --> IA[Adaptadors d'Interfície]
    IA --> CU[Casos d'Ús]
    CU --> ENT[Entitats]
    style ENT fill:#cde
    style UI fill:#fdd

Conseqüències pràctiques:

  • Un cercle interior no pot anomenar res d'un cercle exterior. Les entitats no coneixen els casos d'ús; els casos d'ús no coneixen els controladors.
  • El que creua la frontera cap endins ho fa mitjançant abstraccions (interfícies) declarades a l'interior. És, de nou, la inversió de dependències.
  • Les dades que creuen fronteres són estructures simples (DTOs), mai entitats d'un framework extern.

  1. Els cercles concèntrics (Clean Architecture)

Clean Architecture proposa (com a mínim) quatre anells, de dins cap a fora:

Anell Contingut Estabilitat Coneix...
1. Entitats Regles de negoci de tota l'empresa Màxima Res
2. Casos d'ús Regles de negoci de l'aplicació Alta Entitats
3. Adaptadors d'interfície Controladors, presentadors, gateways Mitjana Casos d'ús (via interfícies)
4. Frameworks i drivers Web, BD, dispositius Mínima Adaptadors
graph TB
    subgraph Anillo4[4 - Frameworks i Drivers]
        subgraph Anillo3[3 - Adaptadors d'Interfície]
            subgraph Anillo2[2 - Casos d'Ús]
                Anillo1[1 - Entitats]
            end
        end
    end
  • Entitats: objectes amb les regles més generals i duradores (p. ex., una Poliza i el seu invariant). Sobreviurien encara que canviés tota l'aplicació.
  • Casos d'ús: orquestren les entitats per complir una funcionalitat concreta d'aquesta aplicació (p. ex., "contractar pòlissa").
  • Adaptadors d'interfície: converteixen dades entre el format dels casos d'ús i el de la tecnologia exterior (controladors, presentadors, repositoris).
  • Frameworks i drivers: Spring, JPA, el navegador, la BD. Detalls reemplaçables.

  1. L'arquitectura ceba (Onion)

L'arquitectura ceba de Palermo és anterior i molt similar. Les seves capes, de dins cap a fora:

Capa Contingut
Model de domini Entitats i objectes de valor
Serveis de domini Regles i interfícies (incloses les de repositori)
Serveis d'aplicació Orquestració de casos d'ús
Infraestructura / UI / Tests Implementacions concretes
graph TB
    subgraph Infra[Infraestructura / UI / Tests]
        subgraph SApp[Serveis d'Aplicació]
            subgraph SDom[Serveis de Domini + Interfícies]
                Modelo[Model de Domini]
            end
        end
    end

El tret més característic d'Onion: les interfícies de repositori es defineixen en el domini, i la infraestructura les implementa. De nou, inversió de dependències. Onion va popularitzar la idea que la infraestructura és un detall de l'anell més extern, igual que la UI.

  1. Comparació: Clean vs Onion vs Hexagonal

Aspecte Hexagonal Onion Clean
Autor / any Cockburn, 2005 Palermo, 2008 Martin, 2012
Metàfora Hexàgon amb costats Capes de ceba Cercles concèntrics
Centre Domini + aplicació Model de domini Entitats
Frontera externa Ports i adaptadors Capa d'infraestructura Frameworks i drivers
Regla essencial Els adaptadors depenen del nucli Dependències cap al centre Dependències cap endins
Èmfasi distintiu Ports primaris/secundaris i testabilitat Infra com a detall extern Separació entitats vs casos d'ús

La conclusió més important: són la mateixa idea amb diferent vocabulari i diferent èmfasi. Les tres col·loquen el domini al centre, prohibeixen que el centre depengui de l'exterior i resolen el creuament de fronteres amb inversió de dependències. Hexagonal posa el focus en els ports i adaptadors; Onion, que la infraestructura és perifèrica; Clean, a distingir les regles d'empresa (entitats) de les regles d'aplicació (casos d'ús). Equivalències útils:

  • Port secundari (Hexagonal) ≈ interfície de repositori en el domini (Onion) ≈ gateway en adaptadors d'interfície (Clean).
  • Adaptador primari (Hexagonal) ≈ controlador en adaptadors d'interfície (Clean).

  1. Estructura de paquets d'exemple

Una organització de paquets habitual que materialitza aquestes idees en Java:

com.fiatc.seguros
├── domain                      # ANELL 1-2: domini pur, sense frameworks
│   ├── model
│   │   └── Poliza.java         # Entitat amb els seus invariants
│   ├── service
│   │   └── TarificadorRiesgo.java   # Servei de domini
│   └── repository
│       └── RepositorioPolizas.java  # INTERFÍCIE (la defineix el domini)
│
├── application                 # ANELL 2: casos d'ús
│   └── usecase
│       └── ContratarPolizaUseCase.java
│
├── infrastructure              # ANELL 4: detalls reemplaçables
│   ├── persistence
│   │   ├── PolizaEntity.java          # Entitat JPA (NO és la del domini)
│   │   └── RepositorioPolizasJpa.java # IMPLEMENTA la interfície del domini
│   └── config
│       └── BeanConfig.java            # Cablejat / injecció
│
└── interfaces                  # ANELL 3: adaptadors d'entrada
    └── rest
        └── PolizaController.java      # Tradueix HTTP -> cas d'ús

Punts a destacar de l'estructura:

  • domain no importa res de infrastructure ni de interfaces. Aquesta és la verificació clau: si ho fes, hauries trencat la regla de dependència.
  • La interfície RepositorioPolizas viu a domain.repository, però la seva implementació RepositorioPolizasJpa viu a infrastructure. El flux de control va del domini a la infraestructura; la dependència de codi, al revés.
  • PolizaEntity (JPA) i Poliza (domini) són classes diferents. Es mapegen entre si. Així, una anotació de JPA mai no contamina el domini.

  1. Inversió de dependències en acció

Vegem com el cas d'ús (interior) utilitza la infraestructura (exterior) sense dependre'n.

// ANELL 2 (application): el cas d'ús només coneix la INTERFÍCIE del domini
package com.fiatc.seguros.application.usecase;

import com.fiatc.seguros.domain.model.Poliza;
import com.fiatc.seguros.domain.repository.RepositorioPolizas; // interfície interior
import com.fiatc.seguros.domain.service.TarificadorRiesgo;

public class ContratarPolizaUseCase {

    private final RepositorioPolizas repositorio; // abstracció del domini
    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 negoci
        Poliza poliza = new Poliza(cliente, riesgo, prima); // l'entitat valida l'invariant
        return repositorio.guardar(poliza);                 // crida cap "enfora" via interfície
    }
}
// ANELL 4 (infrastructure): implementa la interfície; depèn CAP ENDINS
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)); // mapatge domini -> entitat JPA
        return poliza;
    }
}

El raonament clau: l'import creua la frontera només en una direcció. infrastructure importa de domain (cap endins, permès). domain mai importa de infrastructure (cap enfora, prohibit). Aquesta asimetria és exactament la regla de dependència.

// Verificació automàtica amb ArchUnit: el domini no ha de dependre de la 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"));
}

Aquest test fa fallar la compilació de l'arquitectura si algú introdueix una dependència prohibida. És la millor defensa perquè la regla no s'erosioni amb el temps.

  1. Errors Comuns i Consells

  • Deixar que el domini importi el framework. L'error número u. L'anell interior ha de ser Java pur.
  • Reutilitzar l'entitat JPA com a entitat de domini. Temta per estalviar mapatge, però acobla el domini a la persistència. Mantén-les separades.
  • Crear capes buides per dogma. Si un cas d'ús només reenvia a un repositori (sense regles), revisa si aporta valor; no caiguis en la "capa embornal" disfressada.
  • Confondre flux de control amb dependència de codi. El control va del centre a l'exterior (el cas d'ús crida guardar); la dependència de codi va de l'exterior al centre (la infra implementa la interfície).
  • Aplicar la cerimònia completa a un CRUD trivial. Aquests estils brillen amb dominis rics en regles; en un CRUD simple poden ser sobreenginyeria.
  • Consell: automatitza la verificació de fronteres amb ArchUnit o el sistema de mòduls; la disciplina manual no escala.

  1. Exercicis

Exercici 1. Indica, per a cada dependència, si està permesa per la regla de dependència: (a) application importa domain. (b) domain importa infrastructure. (c) interfaces importa application. (d) domain importa org.springframework.

Exercici 2. Estableix l'equivalència entre aquests termes de les tres arquitectures: "port secundari", "interfície de repositori en el domini", "gateway".

Exercici 3. Un company col·loca la interfície RepositorioPolizas en el paquet infrastructure. Explica per què viola la regla de dependència i on hauria d'anar.

Solucions

Solució 1. (a) Permesa (cap endins). (b) Prohibida (el domini apuntaria cap enfora). (c) Permesa (cap endins). (d) Prohibida (el domini no pot dependre d'un framework extern).

Solució 2. Els tres designen el mateix concepte: una abstracció que el nucli declara per parlar amb l'exterior (típicament persistència o serveis externs), implementada en l'anell més extern. "Port secundari" és el terme hexagonal; "interfície de repositori en el domini", el d'Onion; "gateway", el de Clean.

Solució 3. Si la interfície viu a infrastructure, aleshores application/domain (interior) haurien d'importar infrastructure (exterior) per utilitzar-la, cosa que inverteix la direcció permesa de les dependències. La interfície ha de viure en el domini (p. ex. domain.repository), i només la seva implementació a infrastructure.

  1. Conclusió

Clean i Onion són, juntament amb l'hexagonal, expressions d'una mateixa veritat arquitectònica: posa el domini al centre i fes que totes les dependències apuntin cap endins. Hem vist la regla de dependència, els cercles concèntrics de Clean, les capes d'Onion, la seva equivalència amb els ports i adaptadors hexagonals, i una estructura de paquets verificable amb ArchUnit que manté el domini lliure de frameworks. Amb això tanquem el recorregut pels estils que protegeixen el nucli de negoci. A partir d'aquí, el curs avança cap als estils distribuïts i orientats a esdeveniments —microserveis, missatgeria, CQRS i event sourcing—, on aquestes mateixes regles de dependència s'apliquen ara a través de fronteres de xarxa.

Curs d'Arquitectura d'Aplicacions

Mòdul 1: Fonaments de l'Arquitectura d'Aplicacions

Mòdul 2: Principis i Tàctiques de Disseny

Mòdul 3: Estils i Patrons Arquitectònics

Mòdul 4: Arquitectures Distribuïdes i Microserveis

Mòdul 5: Arquitectures Dirigides per Esdeveniments i Missatgeria

Mòdul 6: Disseny Dirigit pel Domini (DDD)

Mòdul 7: Dades i Persistència

Mòdul 8: Arquitectura al Núvol i Desplegament

Mòdul 9: Qualitat, Seguretat i Observabilitat

Mòdul 10: Evolució, Governança i Casos Pràctics

© Copyright 2026. Tots els drets reservats