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

  1. Arquitectura en capes: on viu cada patró GoF de PideYa
  2. MVC i les seves variants (MVP, MVVM)
  3. Arquitectura hexagonal: ports i adaptadors
  4. Injecció de dependències i contenidors IoC
  5. Repository i Unit of Work
  6. 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 FabricaEspanya de 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:

ComandaCreada → LiniaAfegida(pizza) → LiniaAfegida(refresc) → ComandaConfirmada → ComandaCobrada

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/repository però 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 que domini no importa infraestructura.
  • 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

  1. 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.
  2. 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.
  3. 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

  1. EstatComanda → domini (regles del cicle de vida). AdaptadorPayPal → infraestructura (implementa el port PassarellaPagament contra una API externa). FacanaCheckout → aplicació (orquestra un cas d'ús complet). ControladorComandesRest → presentació (tradueix HTTP a crides d'aplicació). RepositoriComandesJpa → infraestructura (implementa el port RepositoriComandes amb JPA). Comanda.Builder → domini (construcció de l'agregat amb les seves invariants).
  2. 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 EstimadorLliurament que retorna sempre Duration.ofMinutes(30). El domini calcula promeses de lliurament sense tocar xarxa.
  3. 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

Mòdul 2: Patrons Creacionals

Mòdul 3: Patrons Estructurals

Mòdul 4: Patrons de Comportament

Mòdul 5: Aplicació de Patrons de Disseny

Mòdul 6: Patrons de Disseny Avançats

Mòdul 7: Recursos Addicionals i Conclusió

© Copyright 2026. Tots els drets reservats