Vam tancar el mòdul anterior amb una constatació: el catàleg GoF es va escriure per a objectes que conviuen dins d'un mateix procés, però el programari actual s'organitza a una escala més gran. Abans de repartir PideYa per la xarxa (això arribarà a les dues properes lliçons), necessitem pujar un nivell de zoom dins del propi monòlit: com s'organitzen els objectes en capes, com es connecta el domini amb el món exterior i com s'estructura l'accés a dades. Veuràs que els patrons arquitectònics no substitueixen el GoF: el contenen. Cada patró d'aquesta lliçó és, en el fons, una intenció GoF (o un principi SOLID) aplicada a l'estructura global de l'aplicació.
Contingut
- Arquitectura en capes: on viu cada patró GoF de PideYa
- MVC i les seves variants (MVP, MVVM)
- Arquitectura hexagonal: ports i adaptadors
- Injecció de dependències i contenidors IoC
- Repository i Unit of Work
- CQRS i Event Sourcing: introducció conceptual
Arquitectura en capes
El patró arquitectònic més veterà (ja apareix a POSA, que vam conèixer a la lliçó 01-02) consisteix a organitzar el codi en capes horitzontals, on cada capa només coneix la inferior:
| Capa | Responsabilitat | Patrons GoF de PideYa que hi viuen |
|---|---|---|
| Presentació | UI, controladors web, API REST | Command (panell amb undo), Observer (refrescar la vista de la comanda) |
| Aplicació | Casos d'ús, orquestració | Facade (FacanaCheckout), Mediator (CentralRepartiment), Template Method (informes) |
| Domini | Regles de negoci, entitats | State (EstatComanda), Strategy (enviament), Builder (Comanda.Builder), Composite (carta), Chain (validació) |
| Infraestructura | BD, xarxa, serveis externs | Adapter (AdaptadorPayPal), Proxy (memòria cau), Abstract Factory (FabricaEspanya/FabricaMexic) |
Fixa't que la taula no és una classificació nova: és el mapa de tot el que vam construir als mòduls 2-4, col·locat al seu lloc. La regla d'or és la direcció de les dependències: el domini no ha d'importar res d'infraestructura. Si Comanda conegués PassarellaRedsys, un canvi a la passarel·la obligaria a recompilar el cor del negoci.
flowchart TD
P[Presentacio] --> A[Aplicacio]
A --> D[Domini]
A --> I[Infraestructura]
I -.implementa interficies de.-> D
La fletxa puntejada és la clau i és pura inversió de dependències (DIP), que vam veure a 01-03: la infraestructura implementa interfícies definides pel domini, no al revés.
MVC i les seves variants
Dins de la capa de presentació, el patró dominant és MVC (Model-Vista-Controlador):
- Model: l'estat i les regles (el nostre domini:
Comanda,EstatComanda...). - Vista: allò que veu l'usuari (plantilles HTML, pantalla de l'app del repartidor).
- Controlador: rep l'acció de l'usuari, invoca el model i tria la vista.
MVC és, essencialment, Observer + Strategy + Composite treballant plegats: la vista observa el model, el controlador és una estratègia de gestió d'entrada i les vistes es componen de subvistes. No és casualitat: el GoF cita MVC com a exemple a la seva introducció.
Les seves variants ajusten qui coneix qui — les esmentem sense desenvolupar-les:
| Variant | Diferència clau | On es veu |
|---|---|---|
| MVP | El Presenter parla amb la vista a través d'una interfície; la vista és passiva | UIs d'escriptori, Android clàssic |
| MVVM | El ViewModel exposa estat observable i la vista s'hi enllaça per data binding | Frontends reactius, Jetpack Compose, frameworks JS |
Arquitectura hexagonal: ports i adaptadors
L'arquitectura hexagonal (Alistair Cockburn) radicalitza la idea de les capes: el domini al centre, i tota la resta (web, BD, passarel·les, cues) connectada mitjançant ports i adaptadors.
- Un port és una interfície definida pel domini: és DIP fet arquitectura.
- Un adaptador és una implementació concreta d'aquest port: és, literalment, el patró Adapter de 03-02 elevat a peça arquitectònica.
A PideYa ja teníem l'exemple perfecte sense saber-ho:
// PORT (viu al domini): el negoci defineix QUE necessita
public interface PassarellaPagament {
ResultatCobrament cobrar(Comanda comanda, DadesTargeta targeta);
}
// ADAPTADORS (viuen a infraestructura): COM s'aconsegueix
public class AdaptadorRedsys implements PassarellaPagament { /* API de Redsys */ }
public class AdaptadorPayPal implements PassarellaPagament { /* el de 03-02 */ }
public class PassarellaEnMemoria implements PassarellaPagament { /* per a tests */ }El domini de PideYa cobra comandes sense saber si al darrere hi ha Redsys, PayPal o un doble de proves. Es distingeixen dos tipus de ports:
- Ports primaris (driving): per on entren les peticions (la interfície que implementa
FacanaCheckout— la nostra Facade ja era un port primari avant la lettre). - Ports secundaris (driven): allò que el domini necessita de l'exterior (
PassarellaPagament,RepositoriComandes,Notificador).
Injecció de dependències i contenidors IoC
Si el domini només coneix interfícies, algú ha de decidir quina implementació concreta s'usa i passar-la. Això és la injecció de dependències (DI): en lloc que FacanaCheckout faci new AdaptadorRedsys(), rep una PassarellaPagament per constructor.
public class FacanaCheckout {
private final PassarellaPagament passarella;
private final Notificador notificador;
// Les dependencies s'INJECTEN: la facana no sap quines son
public FacanaCheckout(PassarellaPagament passarella, Notificador notificador) {
this.passarella = passarella;
this.notificador = notificador;
}
}Un contenidor IoC (Spring, Guice, CDI) ho industrialitza: escaneja les classes, decideix quina implementació satisfà cada interfície i construeix el graf d'objectes complet. Vist des del catàleg:
- El contenidor és una Abstract Factory generalitzada: la
FabricaEspanyade 02-04 creava famílies coherents a mà; Spring ho fa per configuració. - Els beans singleton de Spring resolen la intenció del Singleton de 02-02 sense els seus inconvenients: una sola instància, però testejable i injectable — la crítica que hi vam fer troba aquí la seva resposta.
Repository i Unit of Work
Baixem ara a la frontera amb la base de dades, amb dos patrons del catàleg PoEAA de Fowler (presentat a 01-02).
Repository ofereix al domini una col·lecció aparent d'objectes, ocultant l'SQL:
// Port secundari: el domini parla de comandes, no de taules
public interface RepositoriComandes {
Optional<Comanda> cercarPerId(String id);
List<Comanda> pendentsDeRepartiment(Zona zona);
void desar(Comanda comanda);
}La implementació (RepositoriComandesJpa) viu a infraestructura. El repositori combina intencions conegudes: és un Adapter sobre la persistència, sovint retorna resultats via Iterator, i les seves consultes complexes es poden construir amb un Builder (així funciona la Criteria API de JPA, com vam veure a 05-03).
Unit of Work registra tots els objectes tocats durant una transacció de negoci i els persisteix d'una vegada en confirmar. Si el checkout modifica la comanda, l'estoc i els punts de fidelització, la Unit of Work garanteix que s'escriguin junts o cap. A Hibernate ja l'uses sense saber-ho: la Session/EntityManager és una Unit of Work que fa dirty checking de les entitats carregades.
CQRS i Event Sourcing
Acabem amb dos patrons que preparen el terreny per als mòduls següents. Només la idea; el seu desplegament real és cosa dels sistemes distribuïts.
CQRS (Command Query Responsibility Segregation) separa el model d'escriptura (comandes d'acció: RealitzarComanda, CancelarComanda) del model de lectura (consultes: "pantalla de seguiment", "llistat de la cuina"). A PideYa ho va motivar una dada real: per cada escriptura d'una comanda hi ha centenars de lectures del seu estat. Amb CQRS, les lectures ataquen vistes desnormalitzades i barates, i les escriptures passen per tot el domini (validacions Chain, EstatComanda...). Et sona la meitat "command"? És la intenció del patró Command de 04-03: peticions cosificades com a objectes.
Event Sourcing va un pas més enllà: en lloc de desar l'estat actual de la comanda, desa la seqüència d'esdeveniments que el va produir:
L'estat actual es reconstrueix reproduint els esdeveniments. Reconeix-hi les intencions GoF:
- Com Memento (04-07), captura el passat per poder tornar-hi — però desant deltes (esdeveniments) en lloc de fotos completes.
- Com Observer (04-08), cada esdeveniment persistit pot notificar altres interessats: les vistes de lectura de CQRS s'actualitzen escoltant aquests esdeveniments.
Aquesta parella CQRS + Event Sourcing és la porta natural a les arquitectures orientades a esdeveniments que veurem a 06-03.
Errors Comuns i Consells
- Capes de mentida: tenir paquets
controller/service/repositoryperò amb el domini important classes d'infraestructura. Les capes es mesuren per les dependències, no pels noms de paquet. Consell: usa ArchUnit per verificar en un test quedominino importainfraestructura. - Hexagonal cerimonial: crear port + adaptador per a tot, inclosa lògica que mai no tindrà segona implementació. És la patternitis de 05-05 a escala arquitectònica. Crea el port quan hi hagi una frontera real (alguna cosa externa o alguna cosa que necessites substituir en tests).
- Repository que filtra la tecnologia: mètodes com
executarHql(String)a la interfície del repositori en destrueixen el propòsit. Si el domini veu HQL, no hi ha abstracció. - Adoptar CQRS/Event Sourcing per moda: són patrons cars (dos models, consistència diferida, versionat d'esdeveniments). Aplica l'"espera fins que faci mal" de 05-01: si un sol model CRUD et serveix, queda-te'l.
- Confondre DI amb tenir un framework: la injecció de dependències és passar col·laboradors per constructor; pots (i has de poder) fer-la a mà en un
main. El contenidor només automatitza l'assemblatge.
Exercicis
- Classifica en capes. Col·loca cada classe de PideYa a la seva capa (presentació, aplicació, domini o infraestructura) i justifica-ho:
EstatComanda,AdaptadorPayPal,FacanaCheckout,ControladorComandesRest,RepositoriComandesJpa,Comanda.Builder. - Dissenya un port. PideYa necessita calcular temps estimats de lliurament consultant un servei extern de trànsit. Defineix el port (interfície de domini), un adaptador real i un adaptador fals per a tests. Indica si és un port primari o secundari.
- Esdeveniments d'una comanda. Escriu la seqüència d'esdeveniments (Event Sourcing) que deixaria una comanda que es crea amb dos plats, es confirma, es cobra, i a la qual el client canvia l'adreça abans del repartiment. Com reconstruiries l'adreça actual de la comanda?
Solucions
EstatComanda→ domini (regles del cicle de vida).AdaptadorPayPal→ infraestructura (implementa el portPassarellaPagamentcontra una API externa).FacanaCheckout→ aplicació (orquestra un cas d'ús complet).ControladorComandesRest→ presentació (tradueix HTTP a crides d'aplicació).RepositoriComandesJpa→ infraestructura (implementa el portRepositoriComandesamb JPA).Comanda.Builder→ domini (construcció de l'agregat amb les seves invariants).- Port secundari (el domini necessita alguna cosa de l'exterior):
public interface EstimadorLliurament { Duration estimar(Adreca origen, Adreca desti); }. Adaptador real:EstimadorGoogleMaps implements EstimadorLliurament, que crida l'API i en tradueix la resposta. Adaptador de test:EstimadorFix implements EstimadorLliuramentque retorna sempreDuration.ofMinutes(30). El domini calcula promeses de lliurament sense tocar xarxa. ComandaCreada(id, client)→LiniaAfegida(plat1)→LiniaAfegida(plat2)→ComandaConfirmada(adrecaInicial)→ComandaCobrada(quantia)→AdrecaCanviada(novaAdreca). L'adreça actual s'obté reproduint els esdeveniments en ordre: l'última escriptura rellevant (AdrecaCanviada) guanya. Fixa't en el bonus: l'historial conserva que hi va haver un canvi d'adreça, informació que un model d'estat actual hauria perdut.
Conclusió
Hem pujat el zoom i hem descobert que l'arquitectura de PideYa està teixida amb els mateixos fils del catàleg: les capes ordenen on viu cada patró GoF, l'hexagonal converteix DIP i Adapter en el plànol de l'edifici, els contenidors IoC industrialitzen les fàbriques, Repository i Unit of Work disciplinen la persistència, i CQRS/Event Sourcing reinterpreten Command, Memento i Observer sobre el flux de dades. Tot això, encara, dins d'un sol procés desplegable. Però PideYa continua creixent: l'equip de repartiment vol desplegar sense esperar el de pagaments, i el monòlit comença a ser el coll d'ampolla. Toca partir-lo — amb els seus propis patrons i els seus propis perills — a Patrons de Disseny en Microserveis.
Curs de Patrons de Disseny de Programari
Mòdul 1: Introducció als Patrons de Disseny
- Què són els Patrons de Disseny?
- Història i Origen dels Patrons de Disseny
- Principis de Disseny: SOLID i Altres Fonaments
- UML Essencial per Entendre Patrons
- Classificació dels Patrons de Disseny
- Avantatges i Desavantatges d'Usar Patrons de Disseny
Mòdul 2: Patrons Creacionals
- Introducció als Patrons Creacionals
- Singleton
- Factory Method
- Abstract Factory
- Builder
- Prototype
- Comparativa i Elecció de Patrons Creacionals
Mòdul 3: Patrons Estructurals
- Introducció als Patrons Estructurals
- Adapter
- Bridge
- Composite
- Decorator
- Facade
- Flyweight
- Proxy
- Comparativa i Elecció de Patrons Estructurals
Mòdul 4: Patrons de Comportament
- Introducció als Patrons de Comportament
- Chain of Responsibility
- Command
- Interpreter
- Iterator
- Mediator
- Memento
- Observer
- State
- Strategy
- Template Method
- Visitor
- Comparativa i Elecció de Patrons de Comportament
Mòdul 5: Aplicació de Patrons de Disseny
- Com Seleccionar el Patró Adequat
- Exemples Pràctics d'Ús de Patrons
- Patrons de Disseny en Projectes Reals
- Refactorització Usant Patrons de Disseny
- Antipatrons: Quan els Patrons es Tornen un Problema
Mòdul 6: Patrons de Disseny Avançats
- Patrons de Disseny en Arquitectures Modernes
- Patrons de Disseny en Microserveis
- Patrons de Disseny en Sistemes Distribuïts
- Patrons de Concurrència
- Patrons de Disseny en Desenvolupament Àgil
