A la lliçó anterior vam ordenar el monòlit de PideYa per dins: capes netes, ports, adaptadors, repositoris. Però l'èxit porta un problema nou: l'equip de repartiment no pot desplegar sense coordinar-se amb el de pagaments, un pic de trànsit al catàleg obliga a escalar tota l'aplicació, i cada release és un esdeveniment. La resposta de la indústria és partir el sistema en microserveis: processos petits, desplegables per separat, que es comuniquen per xarxa. Aquesta decisió no surt de franc — cada crida a mètode es converteix en una crida remota que pot fallar — i per això existeix un catàleg propi de patrons. Com veuràs, molts són vells coneguts del GoF estirats sobre la xarxa.

Contingut

  1. Com partir el monòlit: bounded contexts
  2. API Gateway i Backend for Frontend
  3. Service Registry i Discovery
  4. Circuit Breaker
  5. Retry amb backoff i idempotència
  6. Saga: transaccions entre serveis
  7. Strangler Fig: migrar sense big bang
  8. Database per Service i les seves conseqüències

Com partir el monòlit: bounded contexts

El primer error possible és partir malament. Els talls no es fan per capes tècniques ("servei de base de dades", "servei de lògica") sinó per capacitats de negoci. El Domain-Driven Design (DDD, Eric Evans) — que aquí només esmentem — anomena bounded context cada frontera dins de la qual un model té un significat coherent. A PideYa, "plat" significa coses diferents per al catàleg (foto, descripció, al·lèrgens) i per a cuina (temps de preparació, estació): són contextos diferents.

El tall de PideYa queda així:

Servei Responsabilitat Patrons GoF que s'emporta del monòlit
Catàleg Carta, preus, disponibilitat Composite (carta), Flyweight, Visitor
Comandes Cicle de vida de la comanda State, Builder, Chain de validació
Pagaments Cobraments i reemborsaments Adapter/Abstract Factory de passarel·les
Repartiment Assignació i seguiment Mediator (CentralRepartiment), Strategy
Notificacions Email, SMS, push Bridge, Decorator (reintents, logging)

Fixa-t'hi: cada servei s'emporta amb ell els patrons interns que vam construir als mòduls 2-4. Els microserveis canvien l'entre, no el dins.

API Gateway i Backend for Frontend

Amb cinc serveis, l'app mòbil ha de conèixer cinc URLs, cinc esquemes d'autenticació, cinc formats d'error? No: s'hi anteposa un API Gateway, un únic punt d'entrada que enruta, autentica, limita trànsit i agrega respostes.

Reconeixes la intenció? És la Facade de 03-06 a escala de xarxa: ocultar un subsistema complex rere una interfície simple. FacanaCheckout simplificava crides entre objectes; el gateway simplifica crides entre processos.

flowchart LR
    APP[App client] --> GW[API Gateway]
    WEB[Web] --> GW
    RID[App repartidor] --> BFF[BFF repartidor]
    GW --> CAT[Cataleg]
    GW --> PED[Comandes]
    GW --> PAG[Pagaments]
    BFF --> PED
    BFF --> REP[Repartiment]

Quan cada tipus de client necessita agregacions molt diferents (l'app del repartidor vol rutes i comandes assignades; la web del client vol carta i seguiment), un únic gateway s'engreixa fins a convertir-se en un coll d'ampolla d'equip. El patró Backend for Frontend (BFF) crea un gateway per tipus de client, mantingut per l'equip d'aquell frontend.

Service Registry i Discovery

Al monòlit, cridar el mòdul de pagaments era passarella.cobrar(...). Ara Pagaments són tres instàncies les IPs de les quals canvien amb cada desplegament. El Service Registry (Eureka, Consul, o el DNS de Kubernetes) és un directori on cada instància es registra en arrencar i del qual els clients obtenen adreces fresques (discovery). És la vella idea del desacoblament per indirecció: ningú no referencia instàncies concretes, igual que a Factory Method ningú no referenciava classes concretes — aquí la "fàbrica" et fabrica adreces.

Circuit Breaker

La crida remota introdueix un mode de fallada nou: el servei lent. Si Pagaments triga 30 segons a respondre, cada checkout reté un fil 30 segons, els fils s'esgoten i Comandes cau també: fallada en cascada. El Circuit Breaker (popularitzat per Nussbaumer/Nygard a Release It!) actua com un fusible:

stateDiagram-v2
    [*] --> Closed
    Closed --> Open : fallades >= llindar
    Open --> HalfOpen : passa el temps d'espera
    HalfOpen --> Closed : crida de prova OK
    HalfOpen --> Open : crida de prova falla
    Closed --> Closed : exit (reseteja comptador)

I aquesta màquina d'estats no et recorda res? És el patró State de 04-09 aplicat a la salut d'una connexió. Una implementació simplificada:

public class CircuitBreaker {
    private enum Estat { CLOSED, OPEN, HALF_OPEN }

    private Estat estat = Estat.CLOSED;
    private int falladesSeguides = 0;
    private long obertDes = 0;
    private final int llindarFallades;   // p. ex. 5
    private final long esperaMs;         // p. ex. 10_000

    public CircuitBreaker(int llindarFallades, long esperaMs) {
        this.llindarFallades = llindarFallades;
        this.esperaMs = esperaMs;
    }

    public synchronized <T> T executar(Supplier<T> crida, Supplier<T> fallback) {
        if (estat == Estat.OPEN) {
            if (System.currentTimeMillis() - obertDes < esperaMs) {
                return fallback.get();          // circuit obert: ni ho intentem
            }
            estat = Estat.HALF_OPEN;            // deixem passar UNA crida de prova
        }
        try {
            T resultat = crida.get();
            estat = Estat.CLOSED;               // exit: el servei s'ha recuperat
            falladesSeguides = 0;
            return resultat;
        } catch (Exception e) {
            falladesSeguides++;
            if (estat == Estat.HALF_OPEN || falladesSeguides >= llindarFallades) {
                estat = Estat.OPEN;
                obertDes = System.currentTimeMillis();
            }
            return fallback.get();
        }
    }
}

Punts a entendre del codi: en OPEN fallem ràpid amb un fallback (per exemple, "desem la teva comanda i et cobrarem d'aquí a uns minuts") en lloc d'esperar un timeout; HALF_OPEN deixa passar una crida-sonda per comprovar si el servei ha reviscut. En producció usaries Resilience4j, que embolcalla la crida igual que un Decorator (03-05) — de fet la seva API es diu literalment CircuitBreaker.decorateSupplier.

Retry amb backoff i idempotència

Moltes fallades de xarxa són transitòries: reintentar sol bastar. Però reintentar malament és pitjor que no reintentar:

  • Backoff exponencial: espera 100 ms, després 200, 400, 800... per no rematar un servei que s'està recuperant. Afegeix-hi jitter (aleatorietat) perquè mil clients no reintentin sincronitzats.
  • Idempotència: si reintentes cobrar(comanda) perquè no ha arribat la resposta... i si el primer cobrament sí que es va executar? Cobrament doble. La solució és que l'operació sigui idempotent: executar-la dues vegades té l'efecte d'una. A PideYa, cada cobrament porta una clau d'idempotència (l'id de la comanda); si Pagaments rep dues vegades la mateixa clau, la segona vegada retorna el resultat desat sense cobrar de nou.

Regla pràctica: mai no activis reintents sobre una operació no idempotent. Aquest duet reapareixerà amb la missatgeria a 06-03.

Saga: transaccions entre serveis

Al monòlit, el checkout era una transacció de BD: comanda + cobrament + estoc, tot o res (la Unit of Work de la lliçó anterior). Amb Database per Service ja no hi ha transacció que abasti Comandes, Pagaments i Repartiment. El patró Saga la substitueix per una seqüència de transaccions locals, on cada pas que falla dispara compensacions que desfan els passos anteriors.

Estil Com funciona Recorda a
Coreografia Cada servei escolta esdeveniments i reacciona; ningú no dirigeix Observer encadenat; simple amb pocs passos, il·legible amb molts
Orquestració Un orquestrador de saga diu a cada servei què fer i n'espera resposta Mediator (04-06): centralitza la conversa, com CentralRepartiment

Saga orquestrada del checkout de PideYa:

  1. Orquestrador → Comandes: crear comanda (local, confirmada).
  2. Orquestrador → Pagaments: cobrar. Si falla → compensar: Comandes.cancelarComanda().
  3. Orquestrador → Repartiment: assignar repartidor. Si falla → compensar: Pagaments.reemborsar(), Comandes.cancelarComanda().

Les compensacions són la intenció de l'undo de Command (04-03) a escala de sistema: cada pas defineix la seva operació inversa. Compte: no és cap rollback màgic — el cobrament va passar i el reemborsament és una operació de negoci nova, visible per al client.

Strangler Fig: migrar el monòlit

PideYa no reescriu el monòlit de cop (el "big bang" gairebé sempre acaba malament). El patró Strangler Fig (Fowler) — anomenat així per la figuera estranguladora que creix envoltant un arbre fins a substituir-lo — consisteix en:

  1. Posar una façana d'enrutament (el mateix API Gateway serveix) davant del monòlit.
  2. Extreure una capacitat (p. ex. Notificacions, la més desacoblada gràcies al Bridge de 03-03) a un servei nou.
  3. Redirigir al gateway aquell trànsit cap al servei nou; la resta continua anant al monòlit.
  4. Repetir fins que el monòlit quedi buit... o fins que deixi de fer mal, que de vegades arriba abans.

És l'estratègia de refactorització de 05-04 elevada a arquitectura: passos petits, sistema sempre funcionant, i els tests de caracterització ara són tests de contracte entre serveis.

Database per Service

Cada servei posseeix la seva base de dades i ningú més no la toca; els altres hi accedeixen només per la seva API. Sense això, els microserveis són un monòlit distribuït: la BD compartida acobla els esquemes i els desplegaments.

Conseqüències que cal acceptar (no són errors, són el preu):

  • No hi ha JOIN entre serveis: la pantalla "les meves comandes amb noms de plats" exigeix compondre dades de Comandes i Catàleg (al gateway/BFF, o duplicant dades).
  • No hi ha transaccions globals: d'aquí les sagues.
  • Consistència eventual: les dades duplicades triguen a convergir — ho tractarem a fons a la lliçó següent.
  • A canvi: cada servei tria el seu emmagatzematge (Catàleg pot usar un documental; Pagaments, relacional estricte) i desplega el seu esquema sense coordinar-se.

Errors Comuns i Consells

  • Començar per microserveis: per a un equip petit, el monòlit ben organitzat de la lliçó 06-01 és més ràpid i barat. Els microserveis resolen problemes d'escala organitzativa (molts equips, desplegaments independents). "Espera fins que faci mal" també s'aplica aquí.
  • El monòlit distribuït: serveis que comparteixen BD o que s'han de desplegar junts. Tens tots els costos de la xarxa i cap avantatge. Símptoma: una història d'usuari típica toca tres serveis.
  • Nanoserveis: tallar massa fi (un servei per entitat) multiplica crides de xarxa i sagues. El tall correcte segueix els bounded contexts, no les taules.
  • Reintents sense idempotència: el clàssic cobrament duplicat. Dissenya la clau d'idempotència abans d'activar reintents.
  • Circuit breaker sense fallback pensat: obrir el circuit i retornar un error 500 pelat amb prou feines millora res. El valor és al pla B de negoci (degradar, encuar, respondre amb memòria cau).
  • Saga sense dissenyar les compensacions: si reemborsar() no existeix o pot fallar sense pla, la saga deixa el sistema en un estat intermedi invisible. Les compensacions són requisits de negoci de primera classe.

Exercicis

  1. Detecta el mal tall. Un arquitecte proposa aquests serveis per a PideYa: servei-entitats (totes les classes de domini), servei-logica (tots els casos d'ús) i servei-dades (tot l'accés a BD). Explica per què és un mal tall i quin criteri s'hauria d'usar.
  2. Traça la saga. El client demana amb un cupó de fidelització (mòdul del cas pràctic de 05-02). La saga és: crear comanda → bescanviar cupó (servei Fidelització) → cobrar la quantia amb descompte → assignar repartiment. Enumera les compensacions necessàries si falla (a) el cobrament i (b) l'assignació de repartiment.
  3. Raona el circuit breaker. Amb el CircuitBreaker de la lliçó configurat amb llindarFallades=3 i esperaMs=10000: partint de CLOSED, arriben aquestes crides: fallada, fallada, èxit, fallada, fallada, fallada, (passen 4 s) crida, (passen 7 s més) crida amb èxit. Indica l'estat després de cada pas i què rep cada cridador.

Solucions

  1. És un tall per capes tècniques, no per capacitats de negoci: qualsevol funcionalitat nova ("afegir propines") tocaria els tres serveis alhora, obligant a desplegar-los coordinats — un monòlit distribuït. El criteri correcte són els bounded contexts: fronteres dins de les quals un model és coherent i un equip pot treballar i desplegar de forma autònoma (catàleg, comandes, pagaments, repartiment, notificacions).
  2. (a) Falla el cobrament: compensar en ordre invers → Fidelitzacio.retornarCupo() i Comandes.cancelarComanda(). (b) Falla el repartiment: Pagaments.reemborsar(), Fidelitzacio.retornarCupo(), Comandes.cancelarComanda(). Observa que les compensacions s'executen en ordre invers al dels passos, exactament com una pila d'undo de Command; i que "retornar un cupó ja bescanviat" ha d'existir com a operació de negoci.
  3. Fallada (1/3, CLOSED) → fallada (2/3, CLOSED) → èxit (comptador a 0, CLOSED) → fallada (1/3) → fallada (2/3) → fallada (3/3 → OPEN, s'anota l'instant). Als 4 s: continua en OPEN (4000 < 10000), el cridador rep el fallback sense intentar la crida. Als 11 s de l'obertura: passa a HALF_OPEN, s'executa la crida de prova, té èxit → CLOSED i comptador a 0; el cridador rep el resultat real.

Conclusió

PideYa ja és una plataforma: cinc serveis tallats per bounded contexts, un gateway que els amaga (Facade), un registre que els localitza, fusibles que n'aïllen les fallades (State), reintents idempotents i sagues que substitueixen les transaccions (Mediator + undo de Command), tot migrat gradualment amb Strangler Fig. Però hem donat per fet el més fràgil: la comunicació mateixa. Què passa quan un missatge es perd, es duplica o arriba tard? Com es propaga l'esdeveniment "comanda confirmada" a cinc serveis sense cridar-los un a un? Aquest és el territori de les cues, els brokers i la consistència eventual: Patrons de Disseny en Sistemes Distribuïts.

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