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
- La idea comuna: dependències cap a l'interior
- La regla de dependència
- Els cercles concèntrics (Clean Architecture)
- L'arquitectura ceba (Onion)
- Comparació: Clean vs Onion vs Hexagonal
- Estructura de paquets d'exemple
- Inversió de dependències en acció
- Errors comuns i consells
- Exercicis
- Conclusió
- 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).
- 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:#fddConseqüè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.
- 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
Polizai 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.
- 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
endEl 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.
- 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).
- 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'úsPunts a destacar de l'estructura:
domainno importa res deinfrastructureni deinterfaces. Aquesta és la verificació clau: si ho fes, hauries trencat la regla de dependència.- La interfície
RepositorioPolizasviu adomain.repository, però la seva implementacióRepositorioPolizasJpaviu ainfrastructure. El flux de control va del domini a la infraestructura; la dependència de codi, al revés. PolizaEntity(JPA) iPoliza(domini) són classes diferents. Es mapegen entre si. Així, una anotació de JPA mai no contamina el domini.
- 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.
- 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.
- 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.
- 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
- Què és l'Arquitectura d'Aplicacions?
- El Rol de l'Arquitecte de Programari
- Atributs de Qualitat i Requisits No Funcionals
- Decisions Arquitectòniques i Compromisos (Trade-offs)
- Documentació d'Arquitectura: Vistes i el Model C4
Mòdul 2: Principis i Tàctiques de Disseny
- Acoblament, Cohesió i Separació de Responsabilitats
- Principis SOLID Aplicats a l'Arquitectura
- DRY, KISS, YAGNI i Altres Principis de Disseny
- Tàctiques Arquitectòniques per als Atributs de Qualitat
- Gestió del Deute Tècnic
Mòdul 3: Estils i Patrons Arquitectònics
- Arquitectura Monolítica
- Arquitectura en Capes (N-Tier)
- Arquitectura Client-Servidor
- Arquitectura Hexagonal (Ports i Adaptadors)
- Arquitectura Neta i Ceba (Clean & Onion)
Mòdul 4: Arquitectures Distribuïdes i Microserveis
- Introducció als Sistemes Distribuïts
- Arquitectura de Microserveis
- Descomposició de Serveis i Bounded Contexts
- API Gateway, Service Discovery i Comunicació entre Serveis
- Patrons de Resiliència: Circuit Breaker, Retry i Bulkhead
- El Teorema CAP i la Consistència de Dades
Mòdul 5: Arquitectures Dirigides per Esdeveniments i Missatgeria
- Fonaments de l'Arquitectura Orientada a Esdeveniments
- Missatgeria Asíncrona: Cues i Brokers
- Patrons d'Esdeveniments: Event Sourcing i CQRS
- Gestió de Transaccions Distribuïdes: Patró Saga
- Streaming de Dades en Temps Real
Mòdul 6: Disseny Dirigit pel Domini (DDD)
- Conceptes Fonamentals del DDD
- Disseny Estratègic: Bounded Contexts i Llenguatge Ubic
- Disseny Tàctic: Entitats, Agregats i Repositoris
- Mapatge de Contextos (Context Mapping)
Mòdul 7: Dades i Persistència
- Estratègies de Persistència: SQL vs NoSQL
- Patrons d'Accés a Dades: Repository, Unit of Work i DAO
- Base de Dades per Servei i Gestió de Dades Distribuïdes
- Cau i Estratègies d'Invalidació
Mòdul 8: Arquitectura al Núvol i Desplegament
- Fonaments del Cloud Computing (IaaS, PaaS, SaaS)
- Contenidors i Orquestració amb Docker i Kubernetes
- Arquitectura Serverless
- Patrons de Disseny Cloud-Native
- Infraestructura com a Codi (IaC)
Mòdul 9: Qualitat, Seguretat i Observabilitat
- Escalabilitat: Horitzontal vs Vertical i Balanceig de Càrrega
- Alta Disponibilitat i Tolerància a Fallades
- Seguretat per Disseny i Autenticació/Autorització
- Observabilitat: Logging, Mètriques i Traçabilitat
- Rendiment i Proves de Càrrega
