Amb els principis de disseny com a llista de comprovació, l'equip del Luis s'enfronta a la pregunta pràctica: per on es talla el monòlit? Descompondre no és dibuixar sis caixes amb els noms de les carpetes del repositori. És trobar les costures reals del sistema (els llocs on el codi i les dades ja estan, o poden estar, poc acoblats), decidir una estratègia de tall coherent, triar un ordre d'extracció que minimitzi el risc i aplicar tècniques que permetin extreure peces sense aturar la botiga.
En aquesta lliçó recorrem les estratègies de descomposició (per capacitat de negoci, per subdomini, per casos d'ús enfront de recursos, per equips), les tècniques per descobrir les costures (event storming introductori, anàlisi de dependències del codi i anàlisi d'acoblament de les taules de l'esquema SQL de 01-05), les dues tècniques d'extracció segura més utilitzades (strangler fig i branch by abstraction), els criteris per ordenar l'extracció i què fer amb les utilitats compartides com bd/connexio.js o serveis/correu.js. Acabem amb l'exemple guiat: el monòlit techcorp-shop descompost en els sis serveis del mapa objectiu, amb el diagrama de dependències abans i després. Com es reparteixen físicament les dades (02-04) i com se substitueix la transacció única (02-05) queden per a les lliçons següents.
Contingut
- Què significa descompondre (i què no)
- Estratègies de descomposició
- Tècniques per descobrir les costures
- Extreure sense trencar: strangler fig i branch by abstraction
- En quin ordre extreure: risc, valor i dependències
- Què fer amb les utilitats compartides
- Exemple guiat:
techcorp-shopen sis serveis
- Què significa descompondre (i què no)
Descompondre un monòlit és redistribuir responsabilitats, codi i dades en unitats autònomes que compleixin els principis de 02-01. Tres aclariments eviten malentesos freqüents:
- No és "un servei per carpeta". Que el monòlit tingui
controladors/comandesControlador.jsno garanteix que "comandes" sigui una bona frontera: com hem vist, aquesta funció toca cinc àrees. Les carpetes són una pista, no la resposta. - No és "un servei per taula". Seria el servei d'entitat de 01-04. Un servei agrupa les taules que canvien juntes sota una capacitat.
- No és un acte únic. És un procés incremental en què cada extracció deixa el sistema funcionant i aporta valor per si mateixa. Si una extracció només té sentit "quan hi siguin totes", és un big bang disfressat.
El resultat de la descomposició és un pla: quins serveis hi haurà, què s'emporta cadascun del monòlit, en quin ordre s'extreuen i amb quina tècnica. Aquest pla és el lliurable d'aquesta lliçó per a TechCorp.
- Estratègies de descomposició
Hi ha quatre maneres habituals de decidir les fronteres. No són excloents: a la pràctica es combinen, i una serveix per validar-ne una altra.
2.1 Per capacitat de negoci
Es pregunta "què fa l'empresa?" i es respon amb una llista de capacitats estables en el temps: gestionar el catàleg, controlar l'inventari, tramitar comandes, cobrar, atendre clients, comunicar-s'hi. Cada capacitat té un responsable de negoci, un vocabulari i un ritme de canvi propis (principi 3 de 02-01).
És l'estratègia més utilitzada perquè les capacitats canvien molt menys que la tecnologia o l'organització: TechCorp cobrava comandes fa deu anys i les cobrarà d'aquí a deu, encara que canviï de passarel·la tres vegades.
2.2 Per subdomini (DDD)
El Domain-Driven Design parteix del domini (el negoci sencer) i el divideix en subdominis: parts del problema amb el seu propi model i el seu propi llenguatge. Sol produir fronteres molt semblants a les capacitats de negoci, però amb dues aportacions addicionals:
- Distingeix subdominis core (on l'empresa competeix), de suport i genèrics, cosa que ajuda a decidir on invertir esforç de disseny.
- Detecta que una mateixa paraula ("producte", "client") significa coses diferents en subdominis diferents, i que aquesta diferència de significat és una costura.
En aquesta lliçó fem servir DDD només com a estratègia de tall; la definició rigorosa dels bounded contexts de TechCorp, el llenguatge ubic i el mapa de contextos són el contingut de 02-03.
2.3 Per "verbs / casos d'ús" enfront de "substantius / recursos"
És una decisió de granularitat dins de qualsevol de les anteriors:
| Enfocament | S'organitza al voltant de... | Exemple | Quan funciona | Risc |
|---|---|---|---|---|
| Substantius / recursos | Entitats del domini: producte, comanda, pagament. | servei-comandes és propietari de l'entitat Comanda i de tot el seu cicle de vida. |
Quan l'entitat té un cicle de vida ric i les operacions sobre ella estan cohesionades. | Degenerar en servei d'entitat (CRUD sense negoci). |
| Verbs / casos d'ús | Processos: fer una comanda, retornar una comanda, reposar estoc. | servei-checkout que orquestra la compra de principi a fi. |
Quan un procés creua moltes entitats i mereix un propietari propi. | Que el servei "verb" acabi coneixent les dades de tothom (torna el crearComanda de sempre, però per HTTP). |
Per a TechCorp l'elecció és substantius com a base (comandes, productes, estoc, pagaments, clients) i el flux "fer una comanda" repartit entre ells mitjançant esdeveniments, sense un servei "checkout" separat. La raó es veurà amb detall a 02-05: el flux es coordina per coreografia i servei-comandes és propietari de l'estat de la comanda, no de les operacions dels altres. Si el flux es compliqués (devolucions parcials, comandes amb diversos enviaments), un servei orquestrador seria el moment de reconsiderar-ho.
2.4 Per equips
De vegades es talla segons qui mantindrà què. És la Llei de Conway feta servir a propòsit (02-01, apartat 9): si Pagaments i Notificacions seran del mateix equip, han de ser un servei o dos? La resposta de TechCorp és dos, perquè tenen ritmes de canvi i perfils de risc diferents (Pagaments toca diners i una passarel·la externa; Notificacions toca plantilles i proveïdors de correu), però és legítim que l'organització influeixi en la granularitat final.
| Estratègia | Pregunta que respon | Fortalesa | Debilitat | Ús a TechCorp |
|---|---|---|---|---|
| Capacitat de negoci | Què fa l'empresa? | Estable en el temps; entenedora per a negoci. | Pot ignorar acoblaments tècnics reals. | Base del tall. |
| Subdomini (DDD) | On canvia el significat de les paraules? | Detecta costures subtils; prioritza el core. | Requereix formació de l'equip. | Refinament a 02-03. |
| Verbs vs. substantius | Entitats o processos? | Ajusta la granularitat. | Els "verbs" tendeixen a acaparar dades. | Substantius + esdeveniments. |
| Per equips | Qui ho manté? | Alinea organització i sistema. | Fossilitza l'organització actual. | Ajust final de granularitat. |
- Tècniques per descobrir les costures
Les estratègies diuen com pensar; les tècniques diuen on mirar. TechCorp en fa servir tres, de més conversacional a més mecànica.
3.1 Event storming (nivell introductori)
És un taller en què negoci i tècnics reconstrueixen el domini en una paret amb notes adhesives. En la seva versió mínima:
- Esdeveniments de domini (taronja): fets rellevants en passat. "Comanda creada", "Estoc reservat", "Pagament confirmat", "Correu enviat", "Producte publicat", "Preu canviat", "Client registrat", "Estoc reposat".
- Ordres (blau): la intenció que provoca l'esdeveniment. "Crear comanda", "Reservar estoc", "Cobrar".
- Actors i sistemes externs (groc / rosa): client, operari de magatzem, passarel·la de pagament, proveïdor de correu.
- S'ordenen en una línia temporal i es busquen esdeveniments pivot: aquells després dels quals canvia el vocabulari i canvien les persones que parlen.
El que es descobreix per a TechCorp, resumit:
flowchart LR
subgraph Cataleg[Catàleg]
E1[Producte publicat] --> E2[Preu canviat]
end
subgraph Clients
E3[Client registrat]
end
subgraph Comandes
E4[Comanda creada] --> E7[Comanda confirmada]
E4 --> E8[Comanda cancel·lada]
end
subgraph Inventari
E5[Estoc reservat]
E9[Estoc reposat]
E10[Estoc alliberat]
end
subgraph Pagaments
E6[Pagament confirmat]
E11[Pagament reemborsat]
end
subgraph Notificacions
E12[Correu enviat]
end
E4 -.-> E5 -.-> E6 -.-> E7 -.-> E12
Els grups d'esdeveniments que comparteixen vocabulari i actor són els candidats a servei; les fletxes discontínues entre grups són els esdeveniments que creuaran fronteres (els comanda.creada, estoc.reservat, pagament.confirmat, comanda.confirmada i comanda.cancellada de 01-05 surten literalment d'aquesta paret). Fixa't que hi apareixen dos esdeveniments que encara no havíem anomenat, "Estoc alliberat" i "Pagament reemborsat": són les compensacions que necessitarà la saga de 02-05. El taller també revela que "Preu canviat" no interessa a Comandes (una comanda desa el preu del moment de la compra), cosa que anticipa una decisió de dades de 02-03.
3.2 Anàlisi de dependències del codi
La segona tècnica és mecànica: qui importa qui dins del repositori del monòlit. Un graf de require/import (eines com madge o dependency-cruiser el generen en segons) mostra els mòduls amb moltes dependències entrants (difícils d'extreure perquè tothom els fa servir) i sortints (difícils d'extreure perquè fan servir tothom).
# A l'arrel del monòlit techcorp-shop: graf de dependències entre mòduls
npx madge --extensions js --json ./ > dependencies.json
# Mòduls amb més dependències entrants (de qui depèn tothom?)
npx madge --extensions js --summary ./Resum del que veu l'equip del Luis a techcorp-shop:
| Mòdul | Depèn de (sortints) | El fan servir (entrants) | Lectura |
|---|---|---|---|
bd/connexio.js |
— | Tots els controladors | Utilitat tècnica compartida; tractar segons l'apartat 6. |
controladors/comandesControlador.js |
bd, passarellaPagament, correu |
rutes/comandes.js |
Poques dependències de mòdul, però moltíssimes de taules (vegeu 3.3): l'acoblament és a l'SQL, no als require. |
controladors/catalegControlador.js |
bd |
rutes/cataleg.js |
Autònom a nivell de codi. Bon primer candidat. |
serveis/correu.js |
— | comandesControlador, clientsControlador, enviamentsControlador |
Utilitat amb lògica de negoci a dins (plantilles). Candidata a servei. |
serveis/passarellaPagament.js |
— | comandesControlador, devolucionsControlador |
Adaptador extern; se l'emportarà servei-pagaments. |
La lliçó d'aquesta taula: en un monòlit amb base de dades compartida, el graf de require enganya. Els mòduls semblen independents perquè l'acoblament real passa per les taules. D'aquí la tercera tècnica.
3.3 Anàlisi d'acoblament de taules
Es construeix una matriu taula × àrea marcant qui llegeix (L) i qui escriu (E) cada taula, a partir del codi SQL del monòlit. Per a l'esquema de 01-05:
| Taula | Clients | Catàleg | Inventari | Comandes | Pagaments | Notificacions | Propietari natural |
|---|---|---|---|---|---|---|---|
clients |
E | — | — | L (crearComanda) |
— | L (email, nom) | Clients |
productes |
— | E | L (FK) | L (nom, preu) | — | — | Catàleg |
atributs_producte |
— | E | — | — | — | — | Catàleg |
estoc |
— | — | E | E (crearComanda reserva i descompta) |
— | — | Inventari |
comandes |
— | — | — | E | L (comanda_id) |
L (correu nou) | Comandes |
linies_comanda |
— | L (informe "més venuts") | — | E | — | — | Comandes |
pagaments |
— | — | — | E (crearComanda insereix) |
E | — | Pagaments |
notificacions |
— | — | — | — | — | E | Notificacions |
Com es llegeix:
- Una taula amb una sola E i cap L aliena (
atributs_producte,notificacions) és una costura neta: s'extreu amb la seva àrea sense tocar res més. - Una taula amb una E pròpia i diverses L alienes (
clients,productes,comandes) s'extreu amb el seu propietari; les lectures alienes es converteixen en crides al contracte o en còpies de només lectura (02-04). - Una taula amb dues E (
estoc,pagaments) és un problema de disseny, no només d'extracció: avuicrearComandaescriu a taules d'Inventari i de Pagaments. Abans d'extreure cal retornar l'escriptura al seu propietari (Comandes demanarà a Inventari que reservi, no reservarà ell). Aquesta és la primera refactorització que farà l'equip del Luis dins del monòlit, abans de moure res.
Aquesta matriu és l'eina més valuosa de tota la lliçó: converteix "crec que comandes està molt acoblat" en "comandes escriu a dues taules alienes i en llegeix dues més", que és una cosa sobre la qual es pot planificar.
- Extreure sense trencar: strangler fig i branch by abstraction
Un cop decidit què extreure, cal com fer-ho amb la botiga funcionant. Dues tècniques cobreixen la majoria de casos. Aquí les presentem com a eines de descomposició; el relat complet de la migració de TechCorp, pas a pas, és a 08-01.
4.1 Strangler fig (figuera estranguladora)
Es col·loca una façana (a TechCorp, l'API Gateway del port 8080) davant del monòlit. Al principi, tot el trànsit va al monòlit. Quan un servei nou està a punt, la façana redirigeix només les seves rutes al servei; la resta continua anant al monòlit. A poc a poc, el monòlit rep menys rutes fins a quedar buit i apagar-se.
flowchart LR
C[Client] --> GW[API Gateway :8080]
GW -- "/productes/* (ja extret)" --> CAT[servei-cataleg :3001]
GW -- "/comandes/*, /clients/*, /pagaments/* (encara)" --> MONO[Monòlit techcorp-shop]
CAT --> M1[(MongoDB catàleg)]
MONO --> PG[(PostgreSQL techcorp)]
Avantatges: reversible (si el servei nou falla, la façana torna a apuntar al monòlit), incremental i visible per al negoci (cada ruta migrada és una fita). Requisit: la façana ha d'existir abans de la primera extracció, cosa que la converteix en el primer lliurable tècnic del pla.
4.2 Branch by abstraction
Serveix per a les dependències internes que la façana no veu: per exemple, crearComanda obté preus llegint la taula productes. No es pot redirigir "per ruta"; cal canviar el codi del monòlit. La tècnica:
- Introduir una abstracció davant de la funcionalitat (una interfície
RepositoriProductes). - Fer que el codi existent la faci servir (la implementació inicial continua llegint la taula).
- Escriure una segona implementació que faci servir el servei nou (client HTTP de
servei-cataleg). - Commutar entre totes dues amb configuració (una feature flag), primer en proves, després en producció, amb marxa enrere immediata si alguna cosa falla.
- Esborrar la implementació antiga.
// monolit/comandes/repositoriProductes.js (branch by abstraction, esquelet)
// Pas 1-2: l'abstracció. crearComanda deixa de fer SQL directe i crida això.
class RepositoriProductesSQL { // implementació antiga: taula local
async obtenirPerIds(ids) {
const { rows } = await bd.query(
'SELECT id AS "producteId", nom, preu FROM productes WHERE id = ANY($1) AND actiu', [ids]);
return rows;
}
}
class RepositoriProductesHTTP { // implementació nova: contracte del servei
async obtenirPerIds(ids) {
// El detall del client HTTP (timeouts, reintents) es veu a 04-04; aquí només el contracte.
return await catalegClient.get('/productes', { ids: ids.join(',') });
}
}
// Pas 4: la commutació per configuració. Mateix contracte, dos orígens.
function crearRepositoriProductes(config) {
return config.CATALEG_REMOT === 'true'
? new RepositoriProductesHTTP()
: new RepositoriProductesSQL();
}
module.exports = { crearRepositoriProductes };Fixa't que totes dues implementacions retornen la mateixa forma (producteId, nom, preu): aquesta forma és l'embrió del contracte del catàleg, i fer-la explícita dins del monòlit ja és un pas de descomposició encara que no s'hagi mogut ni una sola línia de servidor.
| Tècnica | Actua sobre | Reversible | Serveix per a |
|---|---|---|---|
| Strangler fig | Trànsit d'entrada (rutes HTTP) | Sí (canvi d'encaminament) | Extreure funcionalitats exposades a l'exterior. |
| Branch by abstraction | Dependències internes del codi | Sí (feature flag) | Substituir un col·laborador intern per un servei remot. |
- En quin ordre extreure: risc, valor i dependències
No tots els serveis s'extreuen alhora ni en qualsevol ordre. TechCorp puntua cada candidat en tres eixos:
- Valor: quant alleuja un problema real dels cinc de 01-05 (escalat en campanyes, desplegaments, incidents que es propaguen, model de dades forçat, equips que es trepitgen).
- Risc: quant negoci es posa en joc si l'extracció surt malament, i quanta lògica delicada (diners, identitat) es toca.
- Dependències: quantes àrees en depenen (entrants) i de quantes depèn (sortints). Poques dependències, extracció fàcil.
| Servei | Valor | Risc | Dependències entrants / sortints | Puntuació i ordre |
|---|---|---|---|---|
servei-cataleg |
Molt alt: és el que se satura ×20 en campanyes i el que pateix el model clau-valor. | Baix: és de lectura majoritària; si falla, la botiga mostra un error de cerca, no perd cobraments. | Entrants: Comandes (nom i preu). Sortints: cap. | 1r |
servei-inventari |
Alt: escala amb les campanyes i és el primer pas de la saga. | Mitjà: un error d'estoc ven el que no hi ha. | Entrants: Comandes. Sortints: cap (només necessita producteId). |
2n |
servei-notificacions |
Alt: és l'origen de la fuita de memòria que va tombar els cobraments; treure'l aïlla l'incident. | Baix: si falla, s'endarrereix un correu. | Entrants: Comandes, Clients. Sortints: llegeix clients i comandes (se substitueix per dades a l'esdeveniment). |
3r |
servei-pagaments |
Alt: aïlla la passarel·la externa i la seva latència; permet acotar l'abast PCI. | Alt: diners. | Entrants: Comandes. Sortints: passarel·la externa. | 4t |
servei-comandes |
Molt alt: és el core, el que més canvia i el que el Luis vol desplegar cada dia. | Alt: és el flux de venda. | Entrants: Notificacions, Pagaments, informes. Sortints: totes (clients, catàleg, inventari, pagaments). | 5è: s'extreu quan els seus col·laboradors ja existeixen com a serveis i la saga està dissenyada. |
servei-clients |
Mitjà: l'autenticació es delega a Keycloak, així que el monòlit ja s'alleugereix sense extreure'l. | Alt: identitat i dades personals; client_id és a tot arreu. |
Entrants: totes les àrees. Sortints: cap. | 6è: l'últim; mentrestant, el monòlit residual serveix /clients. |
Tres raons resumeixen per què el catàleg va primer: és on hi ha el problema més car (les caigudes de campanya), és l'extracció amb menys risc (no toca diners ni el flux de comanda al seu camí crític) i no depèn de ningú: només Comandes en depèn per a dos camps, i això es resol amb l'abstracció de l'apartat 4.2. Extreure'l primer dona a més una victòria primerenca que justifica la inversió davant de la Marta i entrena l'equip de plataforma en el pipeline complet (Docker, Kubernetes, canary) amb el servei menys perillós.
I una raó per la qual comandes no va primer encara que sigui el core: extreure primer el servei que depèn de tots obligaria a construir sis contractes alhora o que servei-comandes continués llegint la base de dades del monòlit, és a dir, a néixer com a monòlit distribuït.
- Què fer amb les utilitats compartides
Tot monòlit té codi que "fa servir tothom". A techcorp-shop: bd/connexio.js, serveis/correu.js, serveis/passarellaPagament.js, a més d'utilitats menors (registre de logs, validació de peticions, format d'errors). Hi ha quatre destinacions possibles, i triar malament crea acoblament nou:
| Utilitat | Naturalesa | Destinació recomanada | Per què |
|---|---|---|---|
bd/connexio.js (pool de PostgreSQL) |
Tècnica, sense negoci, 30 línies | Copiar a cada servei (o plantilla de projecte). | Cada servei té la seva pròpia BD i la seva pròpia configuració; una llibreria compartida per a 30 línies acobla els desplegaments per no res. |
| Registre de logs, format d'errors HTTP, validació de JSON | Tècnica, sense negoci | Llibreria interna versionada (@techcorp/comu-http), publicada en un registre privat, amb versions semàntiques. |
S'accepta llibreria compartida només per a codi sense lògica de negoci; cada servei tria quan actualitzar la versió, així que no hi ha acoblament de desplegament. |
serveis/correu.js (plantilles, enviament SMTP) |
Conté negoci (quins correus existeixen, què diuen) | Es converteix en el nucli de servei-notificacions. No es comparteix. |
Si fos llibreria, cada servei enviaria els seus correus i la fuita de memòria continuaria dins de tots ells. Com a servei, reacciona a esdeveniments i aïlla la fallada. |
serveis/passarellaPagament.js (adaptador de la passarel·la) |
Adaptador extern amb regles de cobrament | Se l'emporta servei-pagaments en exclusiva. |
Només un servei ha de parlar amb la passarel·la; la resta demana "cobrar" per contracte. |
Models / DTO de negoci (models/Comanda.js, models/Producte.js) |
Negoci | No es comparteixen mai. Cada servei defineix els seus. | Compartir models de domini és la "capa compartida" de 01-04: un canvi a Producte redesplega tothom. Cada context té el seu propi "producte" (02-03). |
La regla que TechCorp adopta: es comparteix codi tècnic com a llibreria versionada; el codi de negoci no es comparteix, es converteix en un servei o es duplica conscientment. Duplicar un DTO de 5 camps en dos serveis és barat; compartir-lo costa l'autonomia de tots dos.
- Exemple guiat:
techcorp-shop en sis serveis
techcorp-shop en sis serveisApliquem tot l'anterior. El pla de descomposició de TechCorp:
Pas 1. Inventari del monòlit. Carpetes, mòduls i taules (01-05), i matriu taula × àrea (3.3).
Pas 2. Tall per capacitat de negoci, validat amb els grups d'esdeveniments de l'event storming (3.1) i amb la matriu de taules: sis capacitats, sis serveis. Coincideix amb les sis àrees funcionals, amb dos matisos que la matriu ha destapat: la reserva d'estoc la farà Inventari (avui la fa crearComanda) i el registre del pagament el farà Pagaments (avui el fa crearComanda).
Pas 3. Repartiment de codi i dades (les dades es detallen a 02-04; aquí, l'assignació):
| Servei | S'emporta del monòlit | Taules de què serà propietari | Deixa de fer |
|---|---|---|---|
servei-cataleg (3001) |
controladors/catalegControlador.js, rutes/cataleg.js, cerca |
productes, atributs_producte (que a 02-04 es convertiran en documents de MongoDB) |
— |
servei-inventari (3006) |
Lògica de reserva/alliberament avui repartida entre crearComanda i magatzemControlador.js |
estoc (+ nova taula de reserves) |
— |
servei-comandes (3002) |
comandesControlador.js, rutes/comandes.js |
comandes, linies_comanda |
Llegir clients i productes; escriure estoc i pagaments; enviar correu. |
servei-pagaments (3003) |
serveis/passarellaPagament.js, devolucionsControlador.js |
pagaments |
— |
servei-notificacions (3005) |
serveis/correu.js, plantilles |
Registre mínim d'enviaments (notificacions) |
Llegir clients i comandes (rebrà el que necessita als esdeveniments). |
servei-clients (3004) |
clientsControlador.js, perfil, adreces |
clients |
Autenticar (es delega a Keycloak, 07-01). |
Pas 4. Diagrama de dependències abans i després.
Abans: tots els mòduls depenen de totes les taules a través d'un únic pool de connexió. El graf és, a la pràctica, complet.
flowchart TB
subgraph MONO[Monòlit techcorp-shop - un procés, un desplegament]
PC[comandesControlador]
CC[catalegControlador]
CLC[clientsControlador]
AC[magatzemControlador]
DC[devolucionsControlador]
CO[serveis/correu]
PP[serveis/passarellaPagament]
PC --> CO
PC --> PP
DC --> PP
CLC --> CO
end
subgraph PG[(PostgreSQL techcorp - un esquema)]
T1[clients]
T2[productes]
T3[estoc]
T4[comandes / linies_comanda]
T5[pagaments]
T6[notificacions]
end
PC --> T1
PC --> T2
PC --> T3
PC --> T4
PC --> T5
CC --> T2
CC --> T4
CLC --> T1
AC --> T3
AC --> T2
DC --> T5
DC --> T4
CO --> T6
CO --> T1
Després: cada servei depèn només del seu emmagatzematge; entre serveis només hi ha contractes (fletxes contínues: crides síncrones a l'API) i esdeveniments (fletxes discontínues: publicació/subscripció). El com d'aquestes fletxes és el mòdul 3.
flowchart TB
GW[API Gateway :8080] --> CAT[servei-cataleg :3001]
GW --> CLI[servei-clients :3004]
GW --> PED[servei-comandes :3002]
CAT --> BDC[(MongoDB catàleg)]
CLI --> BDL[(PG clients)]
PED --> BDP[(PG comandes)]
INV[servei-inventari :3006] --> BDI[(PG inventari)]
PAG[servei-pagaments :3003] --> BDG[(PG pagaments)]
NOT[servei-notificacions :3005]
PED -- "GET /productes (nom, preu)" --> CAT
PED -- "GET /clients/{id} (existeix, email)" --> CLI
PED -. "comanda.creada / comanda.confirmada / comanda.cancellada" .-> MQ[(RabbitMQ)]
MQ -. "comanda.creada" .-> INV
INV -. "estoc.reservat" .-> MQ
MQ -. "estoc.reservat" .-> PAG
PAG -. "pagament.confirmat" .-> MQ
MQ -. "pagament.confirmat" .-> PED
MQ -. "comanda.confirmada / comanda.cancellada" .-> NOT
PAG --> EXT1[Passarel·la externa]
NOT --> EXT2[Correu / SMS]
Compara els dos grafs amb la taula d'acoblaments de 02-01: al primer, tot és acoblament de dades i de desplegament; al segon, només hi ha acoblament de contracte (dues crides síncrones de Comandes) i d'esdeveniments. Queda un acoblament temporal deliberat (Comandes consulta Catàleg i Clients de manera síncrona en crear la comanda); a 02-04 veurem l'alternativa de replicar aquestes dades i a 06-03 com protegir aquestes crides.
Pas 5. Ordre i tècnica d'extracció. Catàleg (strangler per rutes /productes + branch by abstraction per als preus a crearComanda), Inventari (branch by abstraction per a la reserva), Notificacions (el monòlit comença a publicar esdeveniments i el servei els consumeix), Pagaments (branch by abstraction per a passarellaPagament), Comandes (strangler de /comandes, ja amb la saga de 02-05) i Clients (strangler de /clients; el monòlit s'apaga).
Errors Comuns i Consells
- Tallar per carpetes del repositori. Les carpetes reflecteixen com es va organitzar el codi fa anys, no les costures de negoci. Fes servir la matriu de taules: no menteix.
- Refiar-se només del graf de
require. En un monòlit amb BD compartida, l'acoblament és a l'SQL. Un mòdul sense dependències de codi pot escriure a quatre taules alienes. - Extreure el core primer "perquè és l'important". És el que depèn de tots; extreure'l primer obliga a néixer acoblat. Comença pel que no depèn de ningú i aporta valor visible.
- Convertir les utilitats en una gran llibreria compartida. Cada actualització de la llibreria redesplega tothom: és acoblament de desplegament en estat pur. Només codi tècnic, versionat, i cada servei decideix quan actualitzar.
- Extreure sense façana. Sense gateway no hi ha strangler fig ni marxa enrere barata. El gateway és la primera peça, no l'última.
- Consell: abans de moure codi a un servei nou, arregla l'acoblament dins del monòlit (retorna les escriptures al seu propietari, introdueix les abstraccions). Un monòlit ben modularitzat es descompon en setmanes; un d'embolicat, en anys.
Exercicis
Exercici 1: Llegir la matriu
Amb la matriu taula × àrea de l'apartat 3.3: (a) quines dues taules exigeixen una refactorització dins del monòlit abans de poder extreure'n el servei, i en què consisteix? (b) Quina taula és la d'extracció més neta i per què? (c) Notificacions llegeix clients i comandes; com obtindrà aquestes dades quan sigui un servei, sense llegir taules alienes?
Exercici 2: Un setè servei
La Marta vol afegir "promocions" (cupons de descompte, el cas de 01-03) durant la migració. Aplica els criteris de valor, risc i dependències i decideix: es construeix directament com a servei-promocions o s'afegeix primer al monòlit? En quina posició de l'ordre d'extracció encaixaria? Justifica-ho en 5-8 línies.
Exercici 3: Branch by abstraction per a la reserva d'estoc
Escriu l'esquelet (sense implementar detalls) de l'abstracció que permetria a crearComanda deixar d'escriure a estoc i passar a demanar la reserva a servei-inventari, amb commutació per configuració. Defineix el contracte de l'abstracció (nom del mètode, entrada, sortida) i indica què retornaria cada implementació quan no hi ha estoc.
Solucions
Exercici 1
(a) estoc i pagaments: totes dues tenen dos escriptors. crearComanda reserva i descompta estoc, i insereix a pagaments. Abans d'extreure Inventari i Pagaments, cal moure aquesta lògica als seus mòduls propietaris dins del monòlit (per exemple, magatzem.reservar(...) i pagaments.registrarCobrament(...)), de manera que crearComanda només invoqui i no escrigui. Després, aquestes invocacions se substitueixen per crides al servei amb branch by abstraction.
(b) atributs_producte (i notificacions): un sol escriptor, cap lector aliè. S'extreu amb Catàleg sense que ningú més se n'assabenti; a més, el seu model clau-valor és la raó per portar el catàleg a MongoDB.
(c) Rebent-les a l'esdeveniment: comanda.confirmada inclourà el comandaId, el total i les dades mínimes del client necessàries per al correu (email, nom) que servei-comandes haurà obtingut per contracte de Clients. Alternativament, Notificacions podria consultar GET /clients/{id} en rebre l'esdeveniment; totes dues opcions es comparen a 02-04. El que no farà mai és SELECT sobre clients.
Exercici 2
Valor: mitjà-alt per a negoci (campanyes), però no resol cap dels cinc problemes de 01-05. Risc: baix en si mateix, però alt pel moment: afegir un servei nou mentre es migra el core duplica el front de treball. Dependències: promocions necessitaria conèixer productes (a què s'aplica) i comandes (on s'aplica), és a dir, depèn de dos serveis que encara no estan extrets. Decisió raonable: construir-lo dins del monòlit com un mòdul ben aïllat (la seva pròpia taula cupons, una abstracció Promocions.calcularDescompte(linies) que faci servir crearComanda), respectant des del primer dia els principis de 02-01 perquè la seva extracció posterior sigui trivial. Encaixaria en l'ordre entre Pagaments i Comandes (posició 4-5): després que existeixin Catàleg i Inventari (les seves dependències) i abans que Comandes s'extregui amb la saga definitiva, perquè el camp descompteAplicat de comanda.confirmada neixi ja al contracte. Si el negoci no ho necessita amb urgència, encara millor: després de Comandes, amb el sistema estable.
Exercici 3
// Contracte de l'abstracció: reservar(comandaId, linies) -> { ok: true, reservaId }
// | { ok: false, motiu: 'SENSE_ESTOC', producteId }
class ReservaEstocSQL { // implementació antiga: taula local del monòlit
async reservar(comandaId, linies) {
// UPDATE estoc SET reservat = reservat + quantitat WHERE ... AND quantitat - reservat >= quantitat
// Si algun UPDATE afecta 0 files -> retornar { ok: false, motiu: 'SENSE_ESTOC', producteId }
// Si tot va bé -> { ok: true, reservaId: `local-${comandaId}` }
}
async alliberar(reservaId) { /* UPDATE estoc SET reservat = reservat - quantitat ... */ }
}
class ReservaEstocHTTP { // implementació nova: contracte de servei-inventari
async reservar(comandaId, linies) {
// POST /reserves { comandaId, linies } -> 201 { reservaId } o 409 { motiu: 'SENSE_ESTOC', producteId }
// Retorna la mateixa forma que la implementació SQL.
}
async alliberar(reservaId) { /* DELETE /reserves/{reservaId} */ }
}
function crearReservaEstoc(config) {
return config.INVENTARI_REMOT === 'true' ? new ReservaEstocHTTP() : new ReservaEstocSQL();
}L'essencial: les dues implementacions retornen la mateixa forma en èxit i en fallada ({ ok, reservaId } / { ok: false, motiu }), de manera que crearComanda no distingeix d'on ve la reserva; i l'abstracció ja inclou l'operació inversa (alliberar), perquè tan bon punt la reserva surti de la transacció única, caldran compensacions (02-05).
Conclusió
Descompondre un monòlit és una feina d'anàlisi abans que de codi. Hem vist les estratègies (capacitat de negoci com a base, subdominis de DDD com a refinament, substantius enfront de verbs per ajustar la granularitat, equips com a últim ajust), les tècniques per descobrir costures (event storming, graf de dependències del codi i, la més reveladora en un monòlit amb BD compartida, la matriu d'acoblament taula × àrea, que ha destapat que crearComanda escriu a estoc i pagaments sense ser-ne el propietari), les tècniques d'extracció segura (strangler fig darrere del gateway i branch by abstraction amb commutació per configuració) i els criteris d'ordre (valor, risc, dependències) que col·loquen el catàleg primer i comandes i clients al final. També hem decidit la destinació de les utilitats compartides: codi tècnic com a llibreria versionada; codi de negoci com a servei (correu.js → servei-notificacions, passarellaPagament.js → servei-pagaments) o duplicat a consciència. El resultat és el pla de TechCorp: sis serveis, cadascun amb els seus mòduls i les seves taules, i un graf de dependències que ha passat de "tots contra totes les taules" a "contractes i esdeveniments".
Aquest pla té encara les fronteres dibuixades a traç gruixut. La lliçó següent les afina amb DDD estratègic: definirem els bounded contexts de TechCorp, veurem per què "producte" i "client" no signifiquen el mateix a cada context, dibuixarem el mapa de contextos amb els seus patrons de relació i decidirem què és un agregat (i per què l'estoc no forma part de l'agregat Comanda).
Curs de Microserveis
Mòdul 1: Introducció als Microserveis
- Conceptes Bàsics de Microserveis
- Avantatges i Desavantatges dels Microserveis
- Comparació amb l'Arquitectura Monolítica
- Quan Adoptar Microserveis: Criteris de Decisió
- El Cas Pràctic del Curs: la Botiga Online de TechCorp
Mòdul 2: Disseny de Microserveis
- Principis de Disseny de Microserveis
- Descomposició d'Aplicacions Monolítiques
- Definició de Bounded Contexts
- Gestió de Dades: una Base de Dades per Servei
- Consistència Distribuïda: Sagues, CQRS i Event Sourcing
Mòdul 3: Comunicació entre Microserveis
- APIs RESTful
- Missatgeria Asíncrona
- Protocols de Comunicació: gRPC, GraphQL
- API Gateway i Backend for Frontend
- Descobriment de Serveis i Balanceig de Càrrega
- Contractes i Versionat d'APIs
Mòdul 4: Implementació de Microserveis
- Elecció de Tecnologies i Eines
- Desenvolupament d'un Microservei Simple
- Gestió de Configuració
- Integració Pràctica: Consumir APIs i Publicar Esdeveniments
- Proves en Microserveis: Unitàries, d'Integració i de Contracte
Mòdul 5: Desplegament i Orquestració
- Contenidors i Docker
- Orquestració amb Kubernetes
- CI/CD per a Microserveis
- Estratègies de Desplegament: Rolling, Blue-Green i Canary
- Service Mesh: Istio i Linkerd
Mòdul 6: Monitoratge i Manteniment
- Monitoratge i Logging
- Traçabilitat Distribuïda amb OpenTelemetry
- Gestió d'Errors i Recuperació
- Escalabilitat i Rendiment
- SLOs, Alertes i Gestió d'Incidents
Mòdul 7: Seguretat en Microserveis
- Autenticació i Autorització
- Seguretat en la Comunicació
- Pràctiques de Seguretat
- Seguretat en Contenidors i Kubernetes
