El pla de descomposició de la lliçó anterior deixa sis serveis amb fronteres dibuixades a traç gruixut. Falta el que separa un disseny acceptable d'un de bo: decidir amb precisió on acaba el significat de cada paraula. Al monòlit de TechCorp, "producte" és una fila de la taula productes i tothom la fa servir; però el que el catàleg entén per producte (una fitxa amb descripció, imatges i atributs) no és el que entén inventari (un identificador amb quantitats) ni el que entén comandes (un nom i un preu congelats en el moment de la compra). Mentre les tres àrees compartien taula, l'ambigüitat es tolerava. Tan bon punt són serveis, l'ambigüitat es converteix en acoblament.
El Domain-Driven Design (DDD) en diu DDD estratègic i ofereix les eines per fer-ho bé: subdominis, llenguatge ubic, bounded contexts, mapa de contextos i agregats. Aquesta lliçó les aplica una per una a TechCorp: classificarem els subdominis, veurem exemples concrets d'ambigüitat, definirem els sis contextos i la seva relació amb els serveis, dibuixarem el mapa de contextos amb els seus patrons de relació, definirem l'agregat Comanda (i per què l'estoc no en forma part) i concretarem quins camps de "producte" i de "client" desa cada context. Com es materialitza això en bases de dades físiques separades és el tema de 02-04.
Contingut
- DDD estratègic en poques paraules
- Els subdominis de TechCorp: core, suport i genèrics
- Llenguatge ubic: quan "producte" i "client" no signifiquen el mateix
- Què és un bounded context i com es relaciona amb un microservei
- El mapa de contextos i els seus patrons de relació
- Agregats i arrels d'agregat: la frontera transaccional
- El model de dades de cada context
- DDD estratègic en poques paraules
DDD (Eric Evans, 2003) té dues meitats. La tàctica parla de com escriure el codi d'un model (entitats, objectes de valor, repositoris, serveis de domini). L'estratègica parla de com dividir un problema gran en models que puguin evolucionar per separat. Per dissenyar microserveis, la meitat estratègica és la que importa, i es recolza en cinc idees:
| Idea | Què és | Pregunta que respon |
|---|---|---|
| Domini | L'àrea de coneixement i activitat de l'empresa. Per a TechCorp, "vendre electrònica de consum per internet". | De què va el negoci? |
| Subdomini | Una part del domini amb la seva pròpia problemàtica. Catàleg, inventari, comandes... | En quines parts es divideix el problema? |
| Llenguatge ubic | El vocabulari precís, compartit per negoci i tècnics, dins d'un context. | Què significa exactament cada paraula aquí? |
| Bounded context | El límit dins del qual un model i el seu llenguatge són vàlids i coherents. | Fins on arriba aquest model? |
| Mapa de contextos | El diagrama de com es relacionen els contextos i qui mana a cada relació. | Com parlen entre si i qui s'adapta a qui? |
La distinció que més confusió genera: subdomini és un tros del problema (existeix encara que no hi hagi programari); bounded context és un tros de la solució (un model que construïm). L'ideal és que coincideixin un a un, i a TechCorp així serà, però convé tenir clar que són coses diferents.
- Els subdominis de TechCorp: core, suport i genèrics
DDD classifica els subdominis segons el seu valor competitiu, i aquesta classificació dicta quant d'esforç de disseny mereix cadascun:
- Core (nucli): on l'empresa es diferencia. És el que cal dissenyar amb més cura i amb el millor equip, i el que mai no es compra fet.
- De suport: necessari per al negoci, específic de l'empresa, però no diferenciador. Es dissenya bé, sense obsessió.
- Genèric: un problema resolt igual a totes les empreses. Es compra, es fa servir un servei extern o s'implementa de la manera més senzilla possible.
| Subdomini de TechCorp | Tipus | Justificació | Conseqüència de disseny |
|---|---|---|---|
| Comandes (cicle de vida de la comanda, flux de compra) | Core | És on TechCorp guanya o perd vendes; l'experiència de compra i la fiabilitat del flux la diferencien de la competència. | Equip del Luis; model més ric; saga dissenyada amb cura (02-05); mai un servei d'entitat. |
| Catàleg (fitxes, cerca, preus) | Core | La manera de presentar i cercar productes d'electrònica amb atributs molt variables forma part de la proposta de valor. | Mereix tecnologia pròpia (MongoDB) i ser el primer a escalar. |
| Inventari (estoc, reserves) | Suport | Imprescindible i específic (regles de reserva, magatzems), però no diferencia TechCorp. | Model senzill i correcte; el seu invariant (no vendre el que no hi ha) és sagrat. |
| Clients (perfil, adreces) | Suport | Necessari, específic en petits detalls (adreces, preferències). | Model simple; l'autenticació, que sí que és genèrica, es delega. |
| Pagaments (cobraments, devolucions) | Suport, amb nucli genèric | Cobrar és genèric (ho fa la passarel·la externa); quan i com cobrar segons l'estat de la comanda és específic. | Adaptador a la passarel·la aïllat darrere d'una capa anticorrupció (apartat 5). |
| Notificacions (correu, SMS) | Genèric | Enviar un correu és igual a tot arreu. | Implementació mínima; proveïdor extern; gens de lògica de negoci a dins. |
| Identitat (autenticació) | Genèric | Login, contrasenyes, tokens: problema resolt. | Es delega del tot a Keycloak (07-01); no hi ha cap servei de TechCorp per a això. |
Aquesta taula justifica decisions que ja vam prendre per intuïció: per què el catàleg i les comandes concentren la inversió, per què la identitat no apareix al mapa de serveis i per què Notificacions ha de ser avorrit.
- Llenguatge ubic: quan "producte" i "client" no signifiquen el mateix
El llenguatge ubic és el vocabulari que negoci i desenvolupament fan servir sense traducció, i que apareix tal qual al codi (per això els identificadors del curs són en català: crearComanda, ComandaRepositori, clientId). L'observació clau de DDD és que el llenguatge ubic només és coherent dins d'un context: la mateixa paraula canvia de significat en creuar la frontera, i pretendre un únic model "producte" per a tota l'empresa produeix un model gegant que no serveix bé a ningú (és la taula productes + atributs_producte de TechCorp).
3.1 "Producte"
| Context | Què és un "producte" | Atributs que li importen | Què li és igual |
|---|---|---|---|
| Catàleg | Una fitxa comercial que es mostra i es cerca. | Nom, descripció llarga, categoria, imatges, atributs variables (voltatge, compatibilitat, color), preu de venda actual, si està publicat. | Quant d'estoc hi ha; a quines comandes apareix. |
| Inventari | Una referència emmagatzemable de la qual hi ha unitats. | producteId, quantitat física, quantitat reservada, ubicació al magatzem, llindar de reposició. |
La descripció, les imatges, el preu. |
| Comandes | Una línia de comanda: el que el client va comprar, al preu al qual ho va comprar. | producteId, nom en el moment de la compra, preu unitari en el moment de la compra, quantitat. |
Que el preu hagi canviat després; l'estoc actual; els atributs. |
La conseqüència pràctica més important és a la tercera fila: si el catàleg canvia el preu de p-501 demà, la comanda com-88213 d'avui no ha de canviar. Per això comandes desa una còpia congelada de nom i preu a linies_comanda, i per això l'esdeveniment "Preu canviat" de l'event storming de 02-02 no li interessa. No és duplicació accidental: és que són dos conceptes diferents que comparteixen identificador.
3.2 "Client"
| Context | Què és un "client" | Atributs que li importen |
|---|---|---|
| Clients | Una persona registrada amb perfil i adreces. | clientId, email, nom, adreces desades, preferències de contacte, data d'alta, identificador a Keycloak. |
| Comandes | Qui fa la comanda i on s'envia. | clientId (referència), adreça d'enviament d'aquesta comanda (còpia; si el client canvia la seva adreça després, la comanda en curs no s'ha de moure). |
| Notificacions | Un destinatari. | Email i nom per saludar. Res més. I els rep a cada esdeveniment, no els emmagatzema com a mestre. |
| Pagaments | Gairebé no existeix: un pagament s'associa a una comanda, no a un client. | comandaId, import. El "titular" el coneix la passarel·la, no TechCorp. |
3.3 Altres paraules amb doble sentit
- "Estat": a Comandes, PENDENT / CONFIRMADA / CANCELLADA; a Pagaments, AUTORITZAT / CAPTURAT / REEMBORSAT; a Inventari, una reserva està ACTIVA / CONSUMIDA / ALLIBERADA. Tres màquines d'estats diferents que avui conviuen en columnes
estat TEXTsense relació. - "Cancel·lar": per a Comandes és tancar la comanda sense completar-la; per a Pagaments és no capturar un cobrament autoritzat; per a Inventari és alliberar una reserva. La saga de 02-05 encadenarà les tres, però cada context fa servir el seu verb.
- "Disponible": a Catàleg significa publicat; a Inventari significa
quantitat - reservat > 0. Un producte pot estar disponible al catàleg i no a l'inventari, i a l'inrevés.
Detectar aquestes col·lisions és, a la pràctica, la manera més fiable de trobar fronteres: allà on una paraula canvia de significat, hi ha una costura.
- Què és un bounded context i com es relaciona amb un microservei
Un bounded context és el límit explícit dins del qual un model de domini i el seu llenguatge ubic són coherents. A dins, "producte" significa una sola cosa; a fora, en pot significar una altra. Cada context té el seu propi model, el seu propi codi i (com veurem a 02-04) les seves pròpies dades.
La relació amb els microserveis:
| Relació | Quan | Exemple | Valoració |
|---|---|---|---|
| 1 context = 1 servei | L'habitual i el recomanable de partida. | Els sis contextos de TechCorp → els sis serveis del mapa objectiu. | Màxima claredat: la frontera del model és la frontera de desplegament i de dades. |
| 1 context = diversos serveis | Quan dins d'un context hi ha parts amb escalat o ritme de canvi molt diferent. | Si la cerca del catàleg necessités un motor d'indexació separat de la gestió de fitxes, tots dos continuarien parlant el mateix llenguatge ("fitxa", "atribut") però serien dos desplegaments. | Acceptable; els dos serveis comparteixen llenguatge però no base de dades ni codi de negoci. |
| Diversos contextos = 1 servei | En etapes primerenques o en dominis petits. | El monòlit modular de 01-03; o el "monòlit residual" de TechCorp mentre Comandes i Clients encara no s'han extret. | Vàlid com a pas intermedi si els contextos estan separats com a mòduls i no comparteixen taules per disseny. |
| 1 servei abasta mig context | Mai. | Un servei-linies-comanda separat de servei-comandes. |
Trenca el model per la meitat: els invariants de la comanda (apartat 6) queden repartits en dos processos. |
La regla de TechCorp: un context, un servei, un equip propietari. I l'excepció admesa: durant la migració, el monòlit residual conté diversos contextos, cadascun al seu mòdul, amb les abstraccions de 02-02 marcant on són les fronteres.
- El mapa de contextos i els seus patrons de relació
Els contextos no viuen aïllats: Comandes necessita preus de Catàleg, Notificacions necessita saber que una comanda s'ha confirmat. El mapa de contextos dibuixa aquestes relacions i, sobretot, qui mana a cadascuna: en qualsevol relació hi ha un context upstream (aigües amunt: el seu model influeix) i un downstream (aigües avall: s'adapta o es protegeix). DDD cataloga els patrons de relació:
| Patró | Què significa | Qui mana | Quan es fa servir |
|---|---|---|---|
| Customer-Supplier (client-proveïdor) | L'upstream (proveïdor) atén necessitats del downstream (client); hi ha negociació: el client pot demanar camps, el proveïdor planifica. | Upstream, però escolta. | Equips que col·laboren de bona fe. |
| Conformist (conformista) | El downstream accepta el model de l'upstream tal qual, sense traduir-lo ni influir-hi. | Upstream del tot. | Quan l'upstream no canviarà per tu (o no val la pena traduir). |
| Anticorruption Layer, ACL (capa anticorrupció) | El downstream posa una capa que tradueix el model de l'upstream al seu propi llenguatge, perquè el model aliè no "contamini" el seu. | Upstream, però el downstream es protegeix. | Sistemes externs, sistemes heretats, models molt diferents. |
| Shared Kernel (nucli compartit) | Dos contextos comparteixen un tros de model (codi o esquema) i acorden canviar-lo junts. | Tots dos, de mutu acord. | Només entre equips molt propers; és acoblament assumit. |
| Open Host Service, OHS (servei de host obert) | L'upstream ofereix un protocol públic i estable perquè qualsevol el consumeixi, en lloc d'integracions a mida. | L'upstream defineix el protocol. | Contextos amb molts consumidors. |
| Published Language, PL (llenguatge publicat) | El format d'intercanvi està documentat i versionat (esquema JSON, contracte d'esdeveniments). Sol acompanyar l'OHS. | L'upstream publica; tothom l'entén. | Gairebé sempre, juntament amb OHS o amb esdeveniments. |
| Partnership (associació) | Dos contextos evolucionen junts per necessitat mútua, coordinant plans. | Cap; iguals. | Contextos del mateix equip o amb dependència bidireccional forta. |
| Separate Ways (camins separats) | No hi ha relació; si calgués alguna cosa, es duplica. | — | Quan integrar costa més que no integrar. |
Aplicat a TechCorp:
flowchart LR
CAT[Catàleg<br/>OHS + PL]
CLI[Clients<br/>OHS + PL]
PED[Comandes<br/>core]
INV[Inventari]
PAG[Pagaments]
NOT[Notificacions]
KC[Keycloak<br/>extern]
PAS[Passarel·la de pagament<br/>externa]
CAT -- "U: preus i noms<br/>D: ACL 'traductorProducte'" --> PED
CLI -- "U: existència, email, adreça<br/>D: Customer-Supplier" --> PED
PED <-->|"Partnership<br/>esdeveniments comanda.creada / estoc.reservat"| INV
PED -- "U: esdeveniments (PL)<br/>D: Customer-Supplier" --> PAG
PAG -- "U: pagament.confirmat (PL)" --> PED
PED -- "U: comanda.confirmada / cancellada (PL)<br/>D: Conformist" --> NOT
KC -- "U: tokens OIDC<br/>D: Conformist" --> CLI
PAS -- "U: API propietària<br/>D: ACL" --> PAG
Lectura relació per relació (U = upstream, D = downstream):
- Catàleg → Comandes: OHS + PL amb ACL a Comandes. Catàleg publica un contracte obert (
GET /productes?ids=..., format JSON documentat) per a tots els consumidors (Comandes, la web, futurs serveis). Comandes no incorpora el model "fitxa de catàleg" al seu domini: una petita capa (traductorProducte) converteix{ producteId, nom, preu, descripcio, atributs... }en l'única cosa que Comandes entén, unaLiniaComandaamb nom i preu congelats. Així, si Catàleg canvia els seus atributs, Comandes no se n'assabenta. - Clients → Comandes: Customer-Supplier. Comandes necessita saber que el client existeix i obtenir-ne l'email i l'adreça d'enviament. És una relació negociada: l'equip de Comandes pot demanar a Clients que exposi un
GET /clients/{id}amb exactament aquests camps. - Comandes ↔ Inventari: Partnership. Tots dos són de l'equip del Luis i evolucionen junts: Comandes publica
comanda.creada, Inventari respon ambestoc.reservat(o la seva negativa). Els contractes d'aquests esdeveniments es dissenyen a la mateixa taula. Fixem-nos que Partnership no vol dir base de dades compartida: vol dir planificació compartida. - Comandes → Pagaments → Comandes: Customer-Supplier per esdeveniments, amb PL. Pagaments consumeix
estoc.reservati publicapagament.confirmat(o el seu rebuig). El llenguatge publicat són els esquemes d'aquests esdeveniments. - Comandes → Notificacions: Conformist. Notificacions accepta els esdeveniments de Comandes tal qual, sense demanar canvis: és un subdomini genèric i la seva feina és reaccionar. Si
comanda.confirmadaportaemailinom, els fa servir; no té cap model propi de client que protegir. - Keycloak → Clients: Conformist. Keycloak és extern i estàndard (OpenID Connect); Clients s'adapta als seus tokens i desa l'identificador de Keycloak (
sub) al costat delclientId. - Passarel·la → Pagaments: ACL. La passarel·la té la seva pròpia API, els seus propis estats i els seus propis errors.
servei-pagamentsl'embolcalla en un adaptador (serveis/passarellaPagament.js, heretat del monòlit i ara en exclusiva) que tradueix als conceptes de Pagaments: AUTORITZAT, CAPTURAT, REBUTJAT, REEMBORSAT. Canviar de passarel·la només toca aquesta capa. - Shared Kernel: cap, per decisió. La temptació seria compartir "identificadors", "diners" o "el sobre dels esdeveniments" com a llibreria comuna de domini. TechCorp ho evita: els identificadors són cadenes opaques i el format del sobre d'esdeveniments és llenguatge publicat (documentat), no codi compartit. És la mateixa regla de 02-02 sobre utilitats compartides.
- Separate Ways: Catàleg i Notificacions, Inventari i Clients. No es parlen. Si Notificacions algun dia necessités el nom d'un producte per a un correu, el rebria a l'esdeveniment abans que integrar-se amb Catàleg.
- Agregats i arrels d'agregat: la frontera transaccional
Baixem un nivell. Dins d'un context, el model s'organitza en agregats: grups d'objectes que es tracten com una unitat per als canvis d'estat. Cada agregat té una arrel (l'única entitat a la qual s'accedeix des de fora) i defineix una frontera de consistència: els invariants (regles que sempre s'han de complir) es garanteixen dins de l'agregat, en una única transacció; entre agregats, la consistència és eventual.
Regles pràctiques de disseny d'agregats:
- Una transacció modifica un sol agregat. Si en necessites modificar dos, o el disseny està malament o toca acceptar consistència eventual entre ells (02-05).
- Els agregats es referencien per identificador, mai per objecte: una Comanda desa
clientId, no un objecte Client. - Agregats petits. Un agregat gran bloqueja molt i escala malament.
- L'arrel protegeix els invariants. Ningú no modifica una línia de comanda "per fora"; es demana a la Comanda que l'afegeixi, i la Comanda comprova que pot.
6.1 L'agregat Comanda
En el context Comandes, l'agregat és Comanda (arrel) amb les seves LíniaComanda (entitats internes) i la seva AdreçaEnviament (objecte de valor, còpia). Els seus invariants:
- El total és sempre la suma de
preuUnitari × quantitatde les seves línies. - Una comanda té com a mínim una línia i cap línia no té quantitat ≤ 0.
- Una comanda CONFIRMADA o CANCELLADA no admet canvis de línies.
- Les transicions d'estat segueixen la màquina d'estats que veurem a 02-05.
// domini/Comanda.js (context Comandes) - esquelet il·lustratiu de l'agregat, sense persistència
class Comanda {
#linies = []; // només l'arrel toca les línies
constructor({ comandaId, clientId, adrecaEnviament }) {
this.comandaId = comandaId; // 'com-88213'
this.clientId = clientId; // referència per id: NO un objecte Client
this.adrecaEnviament = { ...adrecaEnviament }; // còpia congelada (objecte de valor)
this.estat = 'PENDENT';
}
afegirLinia({ producteId, nom, preuUnitari, quantitat }) {
if (this.estat !== 'PENDENT') throw new Error('COMANDA_NO_MODIFICABLE');
if (quantitat <= 0) throw new Error('QUANTITAT_INVALIDA');
// nom i preuUnitari arriben ja traduïts des de Catàleg (ACL) i es congelen aquí
this.#linies.push({ producteId, nom, preuUnitari, quantitat });
}
get total() { // invariant: sempre derivat de les línies
return this.#linies.reduce((suma, l) => suma + l.preuUnitari * l.quantitat, 0);
}
get linies() { return this.#linies.map(l => ({ ...l })); } // còpia: ningú no modifica per fora
}
module.exports = { Comanda };Què no hi ha en aquest agregat, i per què:
- No hi ha estoc. L'invariant "no reservar més del que hi ha" (
quantitat - reservat >= 0) pertany al context Inventari i al seu agregat (unaReservaassociada a uncomandaId, o el mateixEstocper producte). Ficar l'estoc a la Comanda tindria tres problemes: (1) la Comanda hauria de conèixer un invariant que no és seu; (2) una fila d'estoc la toquen centenars de comandes concurrents, així que l'agregat Comanda "abastaria" files compartides per totes les comandes i les transaccions es bloquejarien entre si (exactament el que passava acrearComandaamb la passarel·la dins de la transacció); (3) l'estoc canvia per motius aliens a la comanda (entrades de magatzem, minves). Per això, en el disseny objectiu, Comandes demana la reserva i la resposta arriba com a esdeveniment. - No hi ha objecte Client ni objecte Producte. Només
clientIdiproducteId, més les còpies de valor (adreça, nom, preu) que la comanda necessita per ser autosuficient. - No hi ha pagament. El Pagament és agregat d'un altre context; la Comanda només sap que el seu estat va passar a PAGADA quan va arribar l'esdeveniment.
I als altres contextos, els agregats principals:
| Context | Agregat (arrel) | Conté | Invariant principal |
|---|---|---|---|
| Comandes | Comanda | Línies, adreça d'enviament, estat, total | Total = suma de línies; transicions vàlides. |
| Inventari | Estoc per producte i Reserva | Quantitat, reservat; reserva: comandaId, línies, estat | quantitat - reservat >= 0; una reserva no es consumeix dues vegades. |
| Catàleg | Producte (fitxa) | Descripció, atributs, imatges, preu actual, publicat | Un producte publicat té nom i preu. |
| Pagaments | Pagament | comandaId, import, estat, referència de la passarel·la | No capturar dues vegades la mateixa comanda. |
| Clients | Client | Perfil, adreces | Email únic. |
| Notificacions | Enviament (mínim) | Destinatari, plantilla, estat | No reenviar el mateix avís. |
- El model de dades de cada context
Concretem què desa cada context dels conceptes compartits. Encara no parlem de bases de dades físiques (02-04), sinó de quins camps existeixen a cada model.
7.1 "Producte" als tres contextos
| Camp | Catàleg | Inventari | Comandes (linies_comanda) |
|---|---|---|---|
producteId |
Sí (el genera) | Sí (referència) | Sí (referència) |
nom |
Sí (mestre) | No | Sí, còpia congelada |
descripcio, imatges, categoria |
Sí | No | No |
atributs variables (voltatge, color...) |
Sí (document flexible) | No | No |
preu |
Sí (preu actual) | No | Sí, còpia congelada com a preu_unitari |
publicat / actiu |
Sí | No | No |
quantitat, reservat |
No | Sí (mestre) | No |
ubicacio, llindar_reposicio |
No | Sí | No |
quantitat demanada |
No | A la reserva | Sí |
Exemple del mateix producte vist des de cada context (format il·lustratiu; l'esquema físic arriba a 02-04):
// Catàleg: la fitxa (document flexible)
{ "producteId": "p-501", "nom": "Auriculars BT X200", "preu": 59.90,
"categoria": "audio", "publicat": true,
"atributs": { "color": "negre", "autonomiaHores": 30, "bluetooth": "5.3" },
"imatges": ["x200-frontal.webp", "x200-lateral.webp"] }
// Inventari: la referència emmagatzemable
{ "producteId": "p-501", "quantitat": 120, "reservat": 7, "ubicacio": "A-03-2", "llindarReposicio": 20 }
// Comandes: la línia de la comanda com-88213 (preu del dia de la compra, encara que demà canviï)
{ "producteId": "p-501", "nom": "Auriculars BT X200", "preuUnitari": 59.90, "quantitat": 1 }7.2 "Client" als contextos que el fan servir
| Camp | Clients | Comandes | Notificacions |
|---|---|---|---|
clientId |
Sí (el genera) | Sí (referència) | Només si ve a l'esdeveniment |
email, nom |
Sí (mestre) | No els emmagatzema com a mestre; els obté per contracte en crear la comanda i els inclou als esdeveniments | Els rep a cada esdeveniment; no els desa com a mestre |
adreces[] desades |
Sí | No | No |
adrecaEnviament de la comanda |
No | Sí, còpia congelada per comanda | No |
keycloakSub, preferències, data d'alta |
Sí | No | No |
7.3 La comanda completa tal com la veu el seu context
{
"comandaId": "com-88213",
"clientId": "c-1024",
"estat": "PENDENT",
"adrecaEnviament": { "carrer": "Gran Vía 12", "cp": "28013", "ciutat": "Madrid" },
"linies": [
{ "producteId": "p-501", "nom": "Auriculars BT X200", "preuUnitari": 59.90, "quantitat": 1 },
{ "producteId": "p-777", "nom": "Cable USB-C 2 m", "preuUnitari": 9.90, "quantitat": 2 }
],
"total": 79.70,
"creatEn": "2026-08-15T10:42:00Z"
}És la mateixa estructura que retornava GET /comandes/{id} a 01-01, ara explicada: tot el que hi ha a dins és del context Comandes (referències per id + còpies de valor); res del que hi ha a dins no obliga a consultar un altre context per llegir la comanda.
Errors Comuns i Consells
- Buscar el "model canònic" de producte per a tota l'empresa. És el camí de tornada a la taula
productes+atributs_producte. Cada context té el seu producte; comparteixen identificador, no model. - Confondre bounded context amb mòdul tècnic. "Context de persistència" o "context d'API" no són bounded contexts: no tenen llenguatge de negoci propi.
- Agregats enormes. Una Comanda que conté el Client, els Productes i l'Estoc "per tenir-ho tot a mà" bloqueja mitja empresa a cada transacció. Referències per id i còpies de valor.
- Copiar dades "per si de cas" en lloc de per semàntica. Comandes copia nom i preu perquè el negoci diu que la comanda conserva el preu de la compra, no per rendiment. Si copies alguna cosa que semànticament ha d'estar sempre actualitzada (per exemple, l'email per contactar), tindràs dades obsoletes; aquí toca consultar o subscriure's als canvis (02-04).
- Ignorar l'ACL amb sistemes externs. Deixar que els estats de la passarel·la de pagament es filtrin al model de Pagaments (i d'aquí als esdeveniments) fa que canviar de passarel·la sigui un canvi a tota l'empresa.
- Consell: mantén un glossari per context (una taula: terme, significat en aquest context, camp al codi). Quan dos glossaris discrepin sobre una paraula, no ho resolguis: és una frontera confirmada.
Exercicis
Exercici 1: Classificar subdominis i triar patró
TechCorp estudia afegir dues capacitats: (a) valoracions de productes pels clients (estrelles i comentaris a la fitxa) i (b) facturació (emissió de factures legals per cada comanda confirmada). Per a cadascuna: classifica-la com a core, suport o genèrica; indica de quins contextos existents seria downstream i quin patró de relació faries servir amb cadascun.
Exercici 2: Detectar la paraula ambigua
En una reunió, l'equip d'Inventari diu "el producte p-501 no està disponible" i l'equip de Catàleg respon "sí que ho està, el vaig publicar ahir". Explica l'ambigüitat amb el vocabulari d'aquesta lliçó, indica com la resoldries als contractes (quin camp exposa cada context) i què hauria de mostrar la web al client en aquest cas.
Exercici 3: Dins o fora de l'agregat?
Per a cada element, decideix si forma part de l'agregat Comanda, és un agregat propi del context Comandes, o pertany a un altre context, i justifica-ho amb la regla dels invariants: (1) el cupó de descompte aplicat a una comanda; (2) l'historial de canvis d'estat de la comanda; (3) la valoració que el client deixa de la comanda després de rebre-la; (4) la reserva d'estoc de la comanda.
Solucions
Exercici 1
(a) Valoracions: subdomini de suport (aporta a l'experiència, però no és el nucli de venda ni un problema genèric). Seria downstream de Catàleg (necessita saber que el producte existeix: OHS + PL, consumint el contracte públic) i de Comandes o Clients per verificar que qui valora va comprar el producte (Customer-Supplier: demanaria un GET /comandes?clientId=...&producteId=... o un esdeveniment comanda.lliurada). El seu model de "producte" seria mínim: producteId i nom per mostrar. Podria viure dins del context Catàleg si és molt simple; com a servei propi si el volum d'escriptura o l'equip ho justifiquen.
(b) Facturació: subdomini de suport amb un fort component genèric (les regles fiscals són iguals per a tothom; moltes empreses fan servir un proveïdor). Downstream de Comandes (Conformist o Customer-Supplier sobre comanda.confirmada, del qual necessita línies, imports i impostos) i de Clients (dades fiscals: Customer-Supplier, demanant un camp dadesFiscals). Si es fa servir un proveïdor extern de facturació, ACL davant de la seva API. Mai no compartiria taules amb Comandes encara que "gairebé tot el que necessita és a comandes".
Exercici 2
"Disponible" té dos significats: a Catàleg, publicat (la fitxa es mostra); a Inventari, amb unitats lliures (quantitat - reservat > 0). Tots dos tenen raó en el seu context. Als contractes, cadascun exposa el seu concepte amb el seu nom: Catàleg, publicat: true/false; Inventari, disponible: 113 (unitats lliures) o hiHaEstoc: true/false. La web (o un BFF, 03-04) compon tots dos: mostra la fitxa perquè està publicada i el botó "comprar" desactivat amb el text "sense estoc" perquè Inventari diu 0. El que no s'ha de fer és afegir a la fitxa del catàleg un camp estoc mantingut a mà: és una dada d'un altre context (si es mostra, s'obté per contracte o per rèplica d'esdeveniments, 02-04).
Exercici 3
- Cupó aplicat: dins de l'agregat Comanda com a dada de valor (
descompteAplicat, codi i quantitat), perquè afecta l'invariant del total; la definició del cupó (validesa, condicions) és d'un altre context (Promocions), referenciat per codi. - Historial d'estats: pot formar part de l'agregat (llista de transicions amb data) si és petit i es fa servir per validar transicions; si creix o només serveix per a auditoria, agregat propi o simplement un registre d'esdeveniments del context Comandes. En cap cas un altre context.
- Valoració de la comanda: fora de l'agregat i probablement fora del context: no afecta cap invariant de la comanda i té cicle de vida propi (arriba dies després, es pot editar). Referència per
comandaId. - Reserva d'estoc: un altre context (Inventari), agregat Reserva amb
comandaId. El seu invariant (quantitat - reservat >= 0) no és de la Comanda, i ficar-la a dins faria que cada comanda bloquegés files compartides per totes.
Conclusió
Amb DDD estratègic hem convertit les sis àrees de TechCorp en sis bounded contexts ben definits: hem classificat els seus subdominis (Comandes i Catàleg com a core; Inventari, Clients i Pagaments de suport; Notificacions i Identitat genèrics, aquesta última delegada a Keycloak), hem comprovat que "producte", "client", "estat", "cancel·lar" i "disponible" signifiquen coses diferents a cada context i que aquesta diferència marca les costures, hem dibuixat el mapa de contextos amb els seus patrons (Catàleg com a OHS + PL amb ACL a Comandes; Clients → Comandes com a Customer-Supplier; Comandes ↔ Inventari com a Partnership; Comandes → Notificacions com a Conformist; ACL davant de la passarel·la; sense shared kernel per decisió) i hem definit l'agregat Comanda amb les seves línies i la seva adreça congelada, deixant fora l'estoc, el client i el pagament per raons d'invariants i contenció. Finalment, hem fixat quins camps desa cada context dels conceptes compartits: el catàleg té la fitxa completa; inventari, producteId i quantitats; comandes, còpies congelades de nom i preu a les seves línies.
Tot això continua sent model. La lliçó següent ho fa físic: una base de dades per servei. Veurem per què la base de dades compartida és l'antipatró principal, com es trenquen les claus foranes creuades de l'esquema de 01-05 (comandes.client_id, linies_comanda.producte_id), quines alternatives hi ha per a les consultes que avui són un JOIN, com es migren les dades sense aturar la botiga i quin esquema concret tindrà cada servei, inclòs el catàleg a MongoDB.
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
