La lliçó anterior va tancar amb un patró: el que sosté un sistema amb moltes versions vives és un contracte explícit i una convivència llarga entre elles. Allà hi havia una app client i una API, i la taula de versions es vigilava a mà. Ara multipliquem el problema en una altra direcció. Reservalia ha crescut: 1.900 negocis de pagament, quatre equips, i l'apps/api que va començar sent un Fastify amb quinze rutes s'ha partit en cinc serveis —cites, negocis, notificacions, pagaments i disponibilitat— que despleguen pel seu compte. La promesa és coneguda: equips autònoms que lliuren sense esperar-se. El preu també, i aquesta lliçó el cobra sencer. El pipeline es multiplica per cinc i amb ell cada decisió que abans es prenia una vegada. Cada servei és alhora client antic i proveïdor d'un altre, així que el contracte deixa de ser una taula i passa a ser una prova executable amb una porta de desplegament. Apareix un entorn d'integració compartit que, si el deixes, es converteix en el coll d'ampolla que anul·la tota l'autonomia guanyada. I apareix un cost organitzatiu del qual gairebé ningú no parla fins que el paga. Al final de la lliçó estarem en condicions de respondre la pregunta que més diners estalvia de tot el mòdul: quan no partir el monòlit.
Contingut
- Què té aquest context que Reservalia no tenia
- La partició i el graf de dependències
- Monorepo o polyrepo, amb l'execució selectiva
- El pipeline plantilla: estandarditzar sense ofegar
- Contract testing i la porta
can-i-deploy - L'entorn d'integració compartit i per què s'encalla
- Versionat i desplegament independents
- Compatibilitat en APIs i en esdeveniments
- Desplegament progressiu i GitOps
- Observabilitat distribuïda i correlació de desplegaments
- El cost organitzatiu i quan no partir
- Fitxa del cas
- Errors Comuns i Consells
- Exercicis
- Conclusió
- Què té aquest context que Reservalia no tenia
| Monorepo de dues apps (mòduls 2-4) | Cinc serveis, quatre equips | |
|---|---|---|
| Unitats desplegables | 2 | 5, i creixent |
| Qui decideix desplegar | L'equip, tots d'acord | Cada equip, sense avisar |
| Un canvi incompatible | Es veu al mateix PR | Es veu en producció, si no ho evites |
| Proves d'integració | Un docker compose amb Postgres |
Contra quina versió dels altres quatre? |
| Rollback | Un digest, 4 min | Un per servei, i a vegades cal revertir-ne dos |
| Depuració d'una fallada | Un log, un servei | Una petició que travessa quatre processos |
| Cost d'una decisió de pipeline | Editar un fitxer | Editar-ne cinc, o tenir un mecanisme |
L'última fila és la que enllaça amb la 04-05 i explica per què aquella feina es va fer. La tercera és el cor de la lliçó: al monorepo, si en Diego trencava la signatura d'una funció que feia servir apps/web, tsc fallava al mateix PR i ningú no se'n va arribar a assabentar. Amb cinc repositoris desplegant per separat, aquest mateix error compila, passa les proves, es desplega i trenca un altre equip. Tot l'aparell de contractes de l'apartat 5 existeix per recuperar el senyal que el monorepo donava de franc.
I el que no canvia, que continua sent gairebé tot: CI i les seves sis pràctiques (02-01), artefacte immutable per digest (02-06), OIDC i mínim privilegi (04-03), IaC (03-03), migracions amb expand and contract (04-06), estratègies de desplegament (03-04) i observabilitat (03-06). Cap d'aquestes peces no se substitueix; totes es repliquen i cal governar-les.
- La partició i el graf de dependències
flowchart TD
W["web / mobil"] --> GW["API gateway"]
GW --> C["cites"]
GW --> N["negocis"]
GW --> P["pagaments"]
C --> D["disponibilitat"]
C -.->|"esdeveniment CitaCreada"| NT["notificacions"]
P -.->|"esdeveniment PagamentConfirmat"| C
N --> D
C --> BD1[("bd cites")]
N --> BD2[("bd negocis")]
P --> BD3[("bd pagaments")]
Dos tipus d'aresta, i la diferència decideix gairebé tota la resta. Les fletxes contínues són crides síncrones: cites pregunta a disponibilitat i espera resposta, així que una fallada o una lentitud de disponibilitat es propaga cap amunt de seguida. Les discontínues són esdeveniments: cites publica CitaCreada i continua; notificacions el consumeix quan pot. Un consumidor caigut no trenca el productor, però introdueix un problema nou: els esdeveniments també són contractes, amb l'agreujant que un esdeveniment publicat ja no es pot canviar i es pot consumir hores després (apartat 8).
Fixa't també en les bases de dades: una per servei i sense accessos creuats. És la regla que fa que el desplegament sigui realment independent; en el moment en què dos serveis comparteixen taules, tens un monòlit distribuït amb tota la complexitat dels microserveis i cap dels seus avantatges.
- Monorepo o polyrepo, amb l'execució selectiva
La primera pregunta que l'equip es va fer, i no té resposta universal:
| Monorepo (els 5 en un repo) | Polyrepo (un repo per servei) | |
|---|---|---|
| Canvi que toca dos serveis | Un PR atòmic, revisió conjunta | Dos PR coordinats a mà |
| Refactor d'una llibreria comuna | S'actualitzen tots els usos alhora | Publicar versió i esperar adopció |
| Aïllament entre equips | Feble: tothom veu i toca tot | Fort: permisos per repositori |
| Pipeline | Un, amb execució selectiva obligatòria | Cinc, amb plantilla compartida |
| Temps de CI | Creix amb el repo si no filtres | Naturalment acotat |
Historial i git blame |
Sorollós, barreja cinc productes | Net per servei |
| Eines necessàries | Graf d'afectació, paths, memòria cau remota |
Registre de paquets, gestió de versions |
| Encaixa bé amb | Equips que col·laboren molt | Equips amb autonomia i ritmes diferents |
Reservalia tria monorepo, per una raó concreta: els cinc serveis comparteixen packages/compartit i encara hi ha refactors que creuen fronteres cada dos per tres. Amb polyrepo, cadascun d'aquests canvis seria publicar un paquet, esperar i obrir cinc PR. La decisió es revisarà quan els equips deixin de tocar-se entre ells, i no és cap derrota canviar d'opinió: és l'indicador funcionant.
El preu del monorepo és que l'execució selectiva passa d'optimització a requisit. Ja la vam veure a la 04-04 amb paths; amb cinc serveis i dependències entre ells cal un graf d'afectació de debò:
detectar:
runs-on: ubuntu-22.04
outputs:
serveis: ${{ steps.afectats.outputs.llista }} # 1
steps:
- uses: actions/checkout@v4
with: { fetch-depth: 0 } # 2
- id: afectats
run: |
BASE=$(git merge-base origin/main HEAD)
LLISTA=$(npx turbo run build --filter="...[$BASE]" --dry=json \
| jq -c '[.tasks[].package] | unique') # 3
echo "llista=$LLISTA" >> "$GITHUB_OUTPUT"
verificar:
needs: [detectar]
if: needs.detectar.outputs.serveis != '[]'
strategy:
matrix:
servei: ${{ fromJson(needs.detectar.outputs.serveis) }} # 4
fail-fast: false # 5
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
- uses: ./.github/actions/preparar-node
- run: npm run verificar --workspace services/${{ matrix.servei }}- El job de detecció produeix una llista que el següent consumeix com a matriu. És el pas d'informació entre jobs de la 04-01, aplicat a decidir què s'executa.
fetch-depth: 0és imprescindible: sense l'historial complet no hi hamerge-basei el càlcul del diff falla silenciosament executant-ho tot....[$BASE]inclou els dependents, no només el que s'ha modificat. Si el PR tocapackages/compartit, el filtre retorna els cinc serveis perquè tots en depenen. Aquest és el matís que separa un graf d'afectació d'un simple filtre per carpeta, i és exactament la trampa del «verd fals» que la 04-04 va advertir: filtrar perpathssense seguir les dependències deixa passar canvis que trenquen un consumidor.- La matriu es construeix en temps d'execució, així que un PR que toca només
notificacionsllança un job i no pas cinc. A Reservalia això va baixar la mitjana de 14 a 4,5 minuts. fail-fast: falseperquè un servei trencat no cancel·li la informació dels altres: qui obre el PR vol veure tots els vermells de cop.
- El pipeline plantilla: estandarditzar sense ofegar
Aquí es cobra la feina de la 04-05. Amb un servei, un workflow duplicat és una molèstia; amb cinc i creixent, és la garantia que la meitat no tindrà escaneig de dependències ni fixarà les accions per SHA. La forma que pren és un workflow reutilitzable al qual cada servei crida amb els seus paràmetres:
# .github/workflows/servei.yml — la plantilla, mantinguda per l'equip de plataforma
on:
workflow_call:
inputs:
servei: { required: true, type: string }
port: { required: false, type: number, default: 3000 }
publicar_pact: { required: false, type: boolean, default: true } # 1
secrets:
ROL_AWS: { required: true }
jobs:
verificar:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
- uses: ./.github/actions/preparar-node
- run: npm run lint --workspace services/${{ inputs.servei }}
- run: npm test --workspace services/${{ inputs.servei }}
- if: inputs.publicar_pact
run: npm run pact:publicar --workspace services/${{ inputs.servei }}
seguretat: # 2 · no és opcional
uses: ./.github/workflows/seguretat.yml
construir-publicar:
needs: [verificar, seguretat]
uses: ./.github/workflows/reusable-build-publicar.yml # 3
with: { servei: '${{ inputs.servei }}' }# services/cites/.github/workflows/ci.yml — el que escriu cada equip
name: cites
on: { pull_request: {}, push: { branches: [main] } }
jobs:
ci:
uses: reservalia/plataforma/.github/workflows/servei.yml@v3 # 4
with: { servei: cites, port: 3010 }
secrets: { ROL_AWS: '${{ secrets.ROL_CITES }}' }- Els paràmetres són pocs i amb valors per defecte sensats. Una plantilla amb vint entrades no és una plantilla, és un llenguatge de programació mal dissenyat; cada paràmetre nou és superfície a mantenir per sempre.
- El que no es pot desactivar: el job de seguretat de la 04-03. Un equip pot triar el seu gestor de proves, no pas si escaneja dependències. Aquesta llista curta d'innegociables és el nucli de la política.
- La plantilla reutilitza el reusable de la 04-05 en lloc de reimplementar-lo: composició, no duplicació.
- La referència va ancorada a un tag (
@v3), mai amain. Un canvi a la plantilla no ha de trencar cinc equips alhora sense que ells ho decideixin; publicantv3.1i deixant que cada equip hi pugi, la migració és progressiva. És la mateixa disciplina de fixar les accions per SHA de la 04-03, amb motivació d'estabilitat a més de seguretat.
La tensió de fons és real i no es resol amb YAML. Estandarditzar de més produeix una plantilla plena d'excepcions i equips que copien el workflow per «arreglar-lo»; estandarditzar de menys produeix cinc pipelines diferents, cap amb les pràctiques del mòdul 4 completes. La regla que li funciona a Reservalia:
| S'estandarditza (obligatori) | Es deixa a cada equip |
|---|---|
| Escaneig de seguretat i política de severitats | Framework de proves i estil |
| Signatura i publicació de l'artefacte a ECR | Quines proves i quantes |
| Nomenclatura d'imatges i tags | Estructura interna del servei |
Publicació de contractes i can-i-deploy |
Quan desplegar i amb quina estratègia |
| Emissió d'esdeveniments de desplegament per a DORA | Eines de desenvolupament local |
- Contract testing i la porta
can-i-deploy
can-i-deployEl problema, plantejat amb precisió: cites crida GET /disponibilitat/:negociId?data=... i espera un array de forats amb el camp inici. L'equip de disponibilitat decideix que ara es digui desDe. Les seves proves passen, les de cites passen —fan servir un mock escrit fa mesos—, tots dos despleguen i producció es trenca. El mock era la mentida: verificava que cites funciona contra el que cites es creu que retorna l'altre, no pas contra el que retorna de debò.
Els contractes dirigits pel consumidor inverteixen la direcció. El consumidor declara què necessita, aquesta declaració es publica en un intermediari (un broker), i el proveïdor verifica al seu propi CI que la compleix.
// services/cites/test/contractes/disponibilitat.pact.ts (costat consumidor)
describe('cites → disponibilitat', () => {
it('retorna els forats d\'un negoci en una data', async () => {
await proveidor.addInteraction({
state: 'el negoci 42 té agenda el 25 d\'octubre', // 1
uponReceiving: 'una consulta de forats',
withRequest: { method: 'GET', path: '/disponibilitat/42', query: { data: '2026-10-25' } },
willRespondWith: {
status: 200,
body: eachLike({ // 2
inici: iso8601DateTimeWithMillis('2026-10-25T08:30:00.000Z'),
duracioMin: like(30),
}),
},
});
const forats = await clientDisponibilitat.consultar(42, '2026-10-25');
expect(forats[0].inici).toBeDefined(); // 3
});
});- L'
stateés una precondició que el proveïdor haurà de muntar a la seva verificació. És el punt d'acord entre els dos equips i per això es redacta en llenguatge de negoci. - Es declaren tipus i forma, no valors.
like(30)significa «un número»; fixar el 30 faria fallar el contracte cada vegada que canviïn les dades de prova del proveïdor, que és la manera més ràpida que l'equip abandoni els contractes. - La prova del consumidor serveix doblement: valida el seu client contra un servidor simulat pel contracte i, en passar, publica el contracte al broker amb la versió i les etiquetes de branca.
Al CI de disponibilitat, la verificació:
- name: Verificar contractes dels meus consumidors
run: npm run pact:verificar
env:
PACT_BROKER_URL: https://pact.reservalia.internal
PACT_PROVIDER_VERSION: ${{ github.sha }}
PACT_CONSUMER_VERSION_SELECTORS: '[{"deployedOrReleased": true}]' # 1- El selector és la clau i gairebé ningú no el configura bé: es verifica contra els contractes de les versions de consumidors realment desplegades, no pas contra tots els contractes històrics. Sense això, el proveïdor arrossega per sempre expectatives de versions mortes i no pot evolucionar mai.
I la porta, que és el que converteix tot això d'un informe en un mecanisme:
pot-desplegar:
needs: [construir-publicar]
runs-on: ubuntu-22.04
steps:
- name: can-i-deploy
run: |
pact-broker can-i-deploy \
--pacticipant disponibilitat \
--version "$GITHUB_SHA" \
--to-environment prod \
--retry-while-unknown 30 --retry-interval 10 # 1Retorna verd només si tots els consumidors desplegats a prod tenen el seu contracte verificat contra aquesta versió concreta del proveïdor. Si cites espera inici i aquesta versió retorna desDe, el desplegament s'atura abans de sortir. --retry-while-unknown cobreix el cas que una verificació estigui en curs: espera en comptes de fallar. És la mateixa classe de porta que la de qualitat de la 04-01, però amb un objecte nou: no comprova el teu codi, comprova la teva compatibilitat amb qui depèn de tu.
- L'entorn d'integració compartit i per què s'encalla
La reacció instintiva de tot equip que es parteix en serveis és muntar un entorn on estiguin els cinc desplegats i provar-hi. Sona raonable i es degrada sempre igual:
| Entorn d'integració compartit | Contractes + un entorn per servei | |
|---|---|---|
| Què prova | El sistema complet, versions reals | La compatibilitat entre parells |
| Quan dóna el senyal | Després de desplegar-hi | Al CI, abans de publicar |
| Si alguna cosa està trencada | Bloqueja els cinc equips | Bloqueja només qui ho ha trencat |
| Diagnòstic d'una fallada | De qui és? Reunió | Del parell concret, amb nom |
| Cost d'infraestructura | Cinc serveis sempre encesos | Un més simulacions |
| Escala a 15 serveis | No | Sí |
El mecanisme de degradació és predictible: a partir de tres o quatre serveis, la probabilitat que alguna cosa estigui trencada a l'entorn compartit en un moment donat s'acosta a u. Aleshores les proves fallen per motius aliens, l'equip aprèn a ignorar els vermells, i l'entorn deixa de donar senyal justament quan hi ha més serveis. És la mateixa dinàmica de les proves inestables de la 02-04, però a escala d'organització.
Això no significa que no s'hagi de provar el sistema junt mai. Significa que l'entorn compartit deixa de ser una porta i passa a ser un detector:
- Les portes són els contractes i el
can-i-deploy: ràpids, deterministes, amb amo clar, i bloquegen el desplegament. - El detector és un conjunt petit de recorreguts d'extrem a extrem executats contra staging després de desplegar, i contra producció com a smoke test (03-02). Si falla, és un incident, no un check vermell d'un PR.
- Versionat i desplegament independents
La regla, en una frase: si desplegar cites obliga a desplegar disponibilitat alhora, no tens microserveis. Un «release coordinat de tots els serveis» reintrodueix tots els costos de la partició i n'elimina l'únic avantatge, perquè el lot torna a ser gran, el lead time se sincronitza amb el servei més lent i una fallada obliga a revertir cinc coses.
| Desplegament independent | Desplegament coordinat | |
|---|---|---|
| Mida del lot | Un canvi d'un equip | La suma de cinc equips |
| Una fallada | Es reverteix un servei | Cal esbrinar quin dels cinc |
| Cadència | La de cada equip | La del més lent |
| Requereix | Compatibilitat cap enrere sempre | Res: per això resulta temptador |
L'última fila és l'honesta: el desplegament coordinat és temptador perquè permet fer canvis incompatibles. Renunciar-hi obliga a la disciplina de l'apartat següent. I cada servei versiona pel seu compte, amb semantic-release sobre els seus propis commits (02-06); no existeix «la versió de Reservalia» i no cal que existeixi. El que sí que existeix és un registre de quina versió de cada servei hi ha a cada entorn, que és precisament el que el broker de contractes ja sap i el que fa possible el can-i-deploy.
- Compatibilitat en APIs i en esdeveniments
Tota la disciplina d'expand and contract (04-06) i de convivència de versions (05-02) s'aplica aquí, amb un afegit propi: els esdeveniments són pitjors que les APIs. Una API síncrona es consumeix ara i saps qui truca; un esdeveniment es consumeix després, potser per un servei nou que encara no existeix, i els esdeveniments ja publicats són a la cua i no es poden canviar.
Reservalia registra els esquemes dels seus esdeveniments en un registre central, i el CI valida la compatibilitat abans de deixar-ne publicar un de nou:
{
"type": "record",
"name": "CitaCreada",
"fields": [
{ "name": "citaId", "type": "string" },
{ "name": "negociId", "type": "int" },
{ "name": "iniciUtc", "type": { "type": "long", "logicalType": "timestamp-millis" } },
{ "name": "canal", "type": ["null", "string"], "default": null }
]
} - name: Comprovar compatibilitat de l'esquema
run: |
curl -sf -X POST "$REGISTRE/compatibility/subjects/CitaCreada/versions/latest" \
-H 'Content-Type: application/json' \
--data-binary @esquemes/CitaCreada.avsc | jq -e '.is_compatible == true' # 1- Falla el CI si l'esquema nou no és compatible amb l'anterior segons la política configurada. És una porta més, del mateix tipus que
can-i-deploy, però per al canal asíncron.
Què és compatible i què no, amb la regla pràctica:
| Canvi a l'esdeveniment | És segur? | Per què |
|---|---|---|
| Afegir camp amb valor per defecte | ✅ | Els consumidors vells l'ignoren |
| Afegir camp obligatori | ❌ | Un consumidor vell no el sap llegir… i un de nou no pot llegir els antics |
| Eliminar un camp amb default | ✅ (amb espera) | Només si cap consumidor no el fa servir; verificar abans |
| Reanomenar un camp | ❌ | És eliminar i afegir alhora |
| Canviar el tipus d'un camp | ❌ | Llevat d'ampliacions molt concretes |
| Canviar el significat sense canviar el tipus | ❌❌ | El pitjor de tots: cap eina no ho detecta |
L'última fila mereix un avís. Passar iniciUtc d'hora local a UTC sense canviar el nom ni el tipus és compatible per al validador i catastròfic per als consumidors, que continuaran interpretant el número com abans. Quan canvia el significat, canvia el nom del camp o la versió de l'esdeveniment; el registre comprova estructura, no semàntica. I una recomanació que estalvia incidents: els consumidors han de ser tolerants —ignorar camps que no coneixen en comptes de fallar—, perquè és el que permet al productor afegir coses sense coordinar res.
- Desplegament progressiu i GitOps
Les estratègies de la 03-04 continuen sent les mateixes i no es tornen a explicar; el que canvia és que ara hi ha cinc desplegaments progressius simultanis i ningú no els anirà a vigilar a mà. Amb el pas a Kubernetes —el detall del qual correspon a la 06-05—, el desplegament progressiu es declara igual que la resta:
apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata: { name: disponibilitat }
spec:
strategy:
canary:
steps:
- setWeight: 10 # 1
- pause: { duration: 5m }
- analysis: # 2
templates: [{ templateName: taxa-error-5xx }]
- setWeight: 50
- pause: { duration: 10m }- Els passos del canari són declaratius: pesos i esperes escrits al costat del servei, no un script al pipeline. Amb cinc serveis, això és el que evita cinc implementacions diferents del mateix canari.
- L'anàlisi automàtica consulta una mètrica i avorta el desplegament si empitjora. És el rollback automàtic per mètriques de la 03-05, ara com a part de l'objecte desplegat en lloc de com un job del CD.
I aquí és on GitOps apareix per necessitat i no per moda. Amb dues apps, el cd.yml empenyia el desplegament des del pipeline (03-02) i funcionava. Amb cinc serveis i diversos entorns, aquest model obliga que el CI tingui credencials d'escriptura sobre tots els clústers —justament el que la 04-03 vol minimitzar— i fa difícil respondre a «què hi ha desplegat ara mateix a prod?». El model pull inverteix la direcció:
flowchart LR
CI["CI del servei<br/>publica imatge per digest"] --> PR["PR automatic al repo<br/>de desplegaments"]
PR --> RV["Revisio i merge"]
RV --> G["Repositori d estat desitjat<br/>git"]
G --> AG["Agent al cluster<br/>sincronitza"]
AG --> K["Cluster"]
K -.->|"deriva detectada"| AG
Tres conseqüències pràctiques que justifiquen el canvi. L'estat desitjat és a git, així que la pregunta de què hi ha desplegat es respon amb git log i no consultant el clúster. El CI ja no necessita credencials del clúster: només permís per obrir un PR, amb la qual cosa la superfície de la 04-03 es redueix molt. I la deriva es detecta i es corregeix sola: si algú canvia alguna cosa a mà al clúster, l'agent el torna a l'estat declarat, que és la mateixa promesa que Terraform donava per a la infraestructura a la 03-03, ara per a les aplicacions.
- Observabilitat distribuïda i correlació de desplegaments
Els tres pilars i les quatre senyals d'or de la 03-06 es mantenen; el que canvia és que una petició ja no viu en un servei. Sense traces distribuïdes, la investigació de «la reserva triga 4 segons» és una reunió de quatre equips dient que la seva part va bé.
El mínim imprescindible són tres coses, i totes tres s'estandarditzen a la plantilla de l'apartat 4 perquè no depenguin de la bona voluntat de cada equip:
- Propagar el context de traça entre serveis i a través dels esdeveniments. A les crides HTTP viatja a la capçalera
traceparent; als esdeveniments cal copiar-lo dins del missatge a mà, i és el que gairebé tothom oblida: la traça es talla justament en creuar la cua, que és on més falta fa. - Etiquetar-ho tot amb
serveiiversio, per poder comparar el comportament de dues versions durant un canari. - Emetre una marca de desplegament per servei al panell comú, amb servei, versió, digest i hora.
La marca és la que resol el problema específic d'aquest context. El cas típic: a les 11:20, cites comença a retornar errors 500 i el seu equip revisa els seus canvis sense trobar res, perquè el seu últim desplegament va ser abans-d'ahir. La resposta és al panell de desplegaments comú, on a les 11:18 apareix una marca de disponibilitat. Sense aquest panell, aquesta correlació triga una hora de reunió; amb ell, trenta segons.
-- Què es va desplegar en els 30 minuts previs a un incident?
SELECT servei, versio, desplegat_el
FROM desplegaments
WHERE desplegat_el BETWEEN '2026-08-14 10:50' AND '2026-08-14 11:20'
ORDER BY desplegat_el DESC;Amb aquesta taula —la mateixa que alimentava les mètriques DORA a la 03-06, ara amb una columna servei— la pregunta «què ha canviat?» té resposta objectiva, que és la primera pregunta de qualsevol incident i la que més temps consumeix quan no es pot contestar.
- El cost organitzatiu i quan no partir
El que ningú no posa a la proposta inicial: algú ha de mantenir la plantilla de pipeline, el broker de contractes, el registre d'esquemes, el repositori de desplegaments, el clúster, el panell comú i les convencions. A Reservalia això són dues persones a temps complet que no desenvolupen producte. És una decisió legítima amb 1.900 negocis i quatre equips; amb 340 negocis i tres persones hauria estat absurda.
La llista de comprovació abans de partir un monòlit, en ordre d'importància:
| Senyal | Justifica partir? |
|---|---|
| Diversos equips es bloquegen entre ells en desplegar | ✅ La raó bona, i gairebé l'única |
| Parts amb necessitats d'escalat molt diferents | ✅ Si la diferència és d'un ordre de magnitud |
| Un domini amb requisits d'aïllament o compliment propis | ✅ Per exemple, pagaments |
| El pipeline triga 40 minuts | ❌ Això s'arregla amb la 04-04 |
| «El codi està fet un embolic» | ❌ Els mòduls distribuïts s'emboliquen igual, més la xarxa |
| «És el que fa tothom» | ❌ Gairebé ningú no té el teu problema |
| Un equip de tres persones | ❌❌ Pagaràs la coordinació sense necessitar-la |
Diego: «Quant ens costa això al mes?» Amb números: la infraestructura puja un 35 % —cinc desplegaments, més xarxa, més observabilitat—, el CI es multiplica però l'execució selectiva ho compensa gairebé del tot, i el cost real són les dues persones de plataforma. A canvi, quatre equips despleguen sense esperar-se, que era el bloqueig que va motivar tot.
I l'alternativa que gairebé mai no es considera: el monòlit modular. Fronteres internes estrictes, mòduls amb interfícies explícites, prohibició d'accessos creuats a taules i un pipeline amb execució selectiva per mòdul. Dóna la major part del benefici d'organització sense cap dels costos de xarxa, i té una propietat valuosíssima: és el pas previ natural, perquè un monòlit ja modularitzat es parteix després amb un esforç moltíssim menor que un d'embolicat. Si dubtes, comença per aquí.
- Fitxa del cas
| Context | 5 serveis, 4 equips, 1.900 negocis; crides síncrones i esdeveniments; una BD per servei |
| Què continua valent tal qual | CI (02), artefacte per digest (02-06), OIDC i mínim privilegi (04-03), IaC (03-03), estratègies (03-04), expand and contract (04-06), observabilitat (03-06) |
| Decisió 1 | Monorepo amb graf d'afectació que inclou dependents; mitjana de CI de 14 a 4,5 min |
| Decisió 2 | Workflow plantilla ancorat a tag, amb una llista curta d'innegociables |
| Decisió 3 | Contractes dirigits pel consumidor + can-i-deploy com a porta prèvia al desplegament |
| Decisió 4 | L'entorn compartit deixa de ser porta i passa a ser detector |
| Decisió 5 | Registre d'esquemes d'esdeveniments amb validació de compatibilitat a CI |
| Decisió 6 | GitOps: el CI obre un PR al repositori d'estat; el clúster fa pull |
| Cost | +35 % d'infraestructura i 2 persones de plataforma a temps complet |
| Efecte en DORA | Freqüència per equip ×3; lead time igual o millor; el CFR puja al 5,1 % els tres primers mesos i torna al 3,9 % amb els contractes madurs |
| Què t'endús a qualsevol projecte | El que el compilador et donava de franc dins d'un procés, entre serveis s'ha de comprar amb contractes executables |
Errors Comuns i Consells
Error 1: compartir base de dades entre serveis. És el monòlit distribuït: tots els costos, cap avantatge, i el desplegament independent deixa d'existir. Error 2: filtrar per paths sense seguir el graf de dependències, amb la qual cosa un canvi a packages/compartit no executa les proves de qui el fa servir: el verd fals de la 04-04.
Error 3: confiar en mocks escrits a mà com si fossin contractes; verifiquen el que tu et creus, no el que l'altre retorna. Error 4: verificar els contractes contra totes les versions històriques en comptes de contra les desplegades, amb la qual cosa el proveïdor no pot evolucionar mai. Error 5: convertir l'entorn compartit en la porta de desplegament, que a partir de quatre serveis està trencat permanentment i entrena l'equip a ignorar els vermells.
Error 6: releases coordinats de tots els serveis, que retornen el lot gran i anul·len la raó d'haver partit. Error 7: canviar el significat d'un camp sense canviar-ne el nom; cap registre d'esquemes no ho detecta. Error 8: no propagar el context de traça a través dels esdeveniments, tallant la traça justament on més falta fa. Error 9: partir el monòlit per problemes de codi i no d'organització.
Consell 1: ancora la plantilla del pipeline a un tag i migra els equips un a un. Consell 2: mantén curta la llista d'innegociables —seguretat, publicació de l'artefacte, contractes, esdeveniments DORA— i deixa la resta a cada equip. Consell 3: fes que els consumidors siguin tolerants i ignorin camps desconeguts. Consell 4: si dubtes, monòlit modular primer; és el pas previ i sempre surt més barat.
Exercicis
Exercici 1
L'equip de disponibilitat necessita canviar el camp inici per desDe a la resposta de GET /disponibilitat/:negociId, i cites i negocis en són consumidors. Dissenya la seqüència completa de canvis i desplegaments, indicant què passa a cada pas amb can-i-deploy i en quin moment és segur eliminar el camp antic. Explica a més per què l'ordre invers —canviar primer els consumidors— tampoc no funciona sense la fase de convivència.
Exercici 2
Un PR modifica únicament packages/compartit/src/disponibilitat.ts. El workflow fa servir paths: ['packages/compartit/**'] i executa un job que compila i prova aquest paquet; surt verd i es fusiona. Dues hores després, cites falla en producció amb un error de tipus en temps d'execució. Explica la fallada del disseny del pipeline, proposa la correcció concreta i digues quina altra porta del sistema ho hauria hagut d'atrapar encara que el filtre hagués estat bé.
Exercici 3
La Marta planteja a la reunió d'arquitectura: «Reservalia té ara 1.900 negocis i quatre equips; hauríem de partir també negocis, que és el servei més gran, en perfils, horaris i facturacio?». Construeix una anàlisi amb els criteris de la lliçó i una recomanació argumentada, incloent-hi quines dades demanaries abans de decidir.
Solucions
Solució 1. És expand and contract (04-06) sobre un contracte HTTP, amb la particularitat que qui s'ha de moure són altres equips. La seqüència:
Pas 1 — Expandir el proveïdor. disponibilitat retorna els dos camps, inici i desDe, amb el mateix valor. Les seves pròpies proves s'actualitzen, però els contractes publicats per cites i negocis continuen exigint inici, que continua present: la verificació passa i can-i-deploy dóna verd. Es desplega sense cap coordinació. Pas 2 — Migrar els consumidors, cadascun al seu ritme. L'equip de cites canvia el seu client per llegir desDe i, amb això, el seu contracte publicat canvia: ara exigeix desDe. Al seu CI, abans de fusionar, la verificació s'executa contra la versió de disponibilitat desplegada a prod, que ja retorna tots dos camps, així que passa. El seu can-i-deploy dóna verd i desplega. negocis fa el mateix la setmana següent, o al cap d'un mes: no cal cap sincronització, i aquesta és exactament la propietat que es va comprar amb tot l'aparell. Pas 3 — Contraure. disponibilitat elimina inici. Aquí passa el més interessant: si negocis encara no ha migrat, el seu contracte continua exigint inici, la verificació falla i can-i-deploy dóna vermell, aturant el desplegament abans de publicar-lo. El proveïdor no necessita preguntar a ningú ni mantenir un full de càlcul de qui ha migrat: la porta ho sap. Quan tots dos consumidors han migrat i les seves versions desplegades estan registrades, el vermell es torna verd tot sol.
El moment segur d'eliminar és, per tant, «quan can-i-deploy ho permeti», que és una resposta operativa i no una data en un calendari. Convé afegir dues cauteles: que els consumidors desplegats a prod siguin els que compten (el selector de l'apartat 5), perquè un consumidor que va migrar a la seva branca però encara no ha desplegat no protegeix res; i que existeixin consumidors fora del broker —un script intern, un client gran amb accés directe— que la porta no veu i cal buscar a mà.
Per què l'ordre invers no funciona: si cites canvia primer per llegir desDe, el seu contracte exigeix un camp que el proveïdor desplegat no té, la verificació falla i can-i-deploy li dóna vermell a ell. Encara que desactivessis la porta, en producció cites llegiria undefined. La fase de convivència no és burocràcia: és l'únic estat en què totes dues parts són vàlides alhora, i per això cap de les dues direccions no funciona sense ella.
Solució 2. La fallada té dues capes i convé separar-les. La capa del filtre: paths és una condició sobre quins fitxers han canviat, no sobre què queda afectat. Un canvi a packages/compartit afecta els cinc serveis que l'importen, però el filtre només dispara el job del paquet. Aquest job va compilar i provar el paquet de manera aïllada —on el canvi és coherent amb si mateix— i va sortir verd. El «verd fals» de la 04-04 en la seva forma més pura: el check no diu «el sistema funciona», diu «el que he executat funciona», i el que va executar era una fracció.
La correcció concreta: substituir el filtre per carpeta pel graf d'afectació de l'apartat 3, amb un filtre que inclogui els dependents (...[BASE] a Turborepo, --affected a Nx, bazel query rdeps a Bazel). Un PR que toca packages/compartit passa a executar els cinc serveis; un que toca només services/notificacions continua executant-ne un. També convé comprovar dos detalls que fan fallar el graf en silenci: que el checkout porti historial suficient per calcular el merge-base, i que la dependència estigui declarada de debò al package.json del servei —si s'importa per ruta relativa saltant-se el workspace, cap eina no la veu—.
Quina altra porta ho hauria d'haver atrapat: els contractes. Si el canvi va alterar la forma del que disponibilitat retorna a cites, la verificació de contractes del proveïdor hauria fallat, i can-i-deploy hauria bloquejat el desplegament encara que el filtre estigués malament. Que no ho atrapés indica que el canvi afectava una cosa que els contractes no cobreixen —una funció de càlcul interna compartida, no la forma d'una resposta—, i aquí la defensa correcta és la primera. És una lliçó general del mòdul 4 que aquí es veu molt bé: les portes se solapen expressament, i un incident que en travessa diverses assenyala quina capa faltava. Un tercer reforç barat: publicar @reservalia/compartit com a paquet versionat en lloc de consumir-lo per workspace obligaria a una actualització explícita a cada servei, convertint un canvi invisible en un PR visible per servei, amb el cost de perdre el refactor atòmic.
Solució 3. L'anàlisi, criteri per criteri. (1) Hi ha bloqueig entre equips? És la pregunta decisiva. Si perfils, horaris i facturacio els manté el mateix equip, partir no elimina cap bloqueig i afegeix tres pipelines, tres desplegaments, tres bases de dades i contractes entre peces que avui comparteixen procés. La resposta per defecte seria no. Si en canvi hi ha dos equips diferents que es trepitgen al mateix repositori i s'esperen per desplegar, la conversa canvia. (2) Escalat diferent? horaris probablement rebi molt més trànsit de lectura que facturacio, però la pregunta correcta és si la diferència és d'un ordre de magnitud i si avui està causant un problema real de cost o de saturació. Si el servei sencer cap en tres tasques d'ECS, la resposta és no. (3) Aïllament o compliment? facturacio és l'única candidata seriosa: si maneja dades fiscals o de pagament amb requisits d'auditoria o retenció propis, aïllar-la té un valor que no és tècnic. (4) Acoblament de dades? El punt que sol matar la proposta: si horaris i facturacio consulten les mateixes taules de negoci, partir-los exigeix duplicar dades o introduir crides síncrones en un camí crític, i el resultat és més lent i més fràgil que l'original.
Les dades que demanaria abans de decidir, totes obtenibles en una setmana: quants PR al mes toca cada àrea i si vénen de persones diferents; quantes vegades en els últims tres mesos un desplegament de negocis es va endarrerir per esperar un canvi d'una altra part; el repartiment de trànsit i de cost entre les tres àrees; el graf real d'importacions internes i d'accessos a taules dins del servei, que és el que diu si les fronteres existeixen o són un desig; i el lead time i el change failure rate de negocis comparats amb els dels altres quatre serveis, per saber si hi ha un problema mesurable o només una sensació.
Recomanació: no partir encara, i en lloc d'això fer el monòlit modular dins de negocis —tres mòduls amb interfícies explícites, prohibició d'accessos creuats a taules verificada a CI amb una regla d'importació, i execució selectiva per mòdul al pipeline—. Això dóna avui la major part del benefici organitzatiu, no afegeix ni una crida de xarxa, i deixa el servei en l'estat en què partir-lo demà és gairebé mecànic si el criteri (1) arriba a complir-se. L'única excepció que consideraria immediatament és facturacio, i només si existeix un requisit de compliment concret que avui no es pugui satisfer. És exactament el consell 4 de la lliçó: quan dubtes, l'opció reversible guanya, i modularitzar és reversible mentre que partir no ho és.
Conclusió
Els microserveis han sotmès el pipeline a la prova més dura del mòdul, i el resultat no és que s'hagi de llençar res, sinó que tot es multiplica i necessita govern. L'execució selectiva va deixar de ser una optimització de la 04-04 per convertir-se en un requisit, i amb un matís que separa un pipeline correcte d'un que enganya: cal seguir el graf de dependències, no filtrar per carpeta, o el verd deixa de significar res. La feina de pipeline as code de la 04-05 es va cobrar íntegra en una plantilla ancorada a tag, amb una llista curta d'innegociables i llibertat en tota la resta. I on el monòlit donava senyal de franc —el compilador impedint que en Diego trenqués apps/web—, es va haver de comprar amb contractes executables: contractes dirigits pel consumidor, verificats pel proveïdor contra les versions realment desplegades, i una porta can-i-deploy que atura el desplegament abans de publicar-lo. Aquest mecanisme és el que va permetre degradar l'entorn d'integració compartit de porta a detector, evitant el coll d'ampolla que anul·la l'autonomia a partir de quatre serveis. Al voltant, la mateixa disciplina de compatibilitat de sempre, estesa als esdeveniments amb un registre d'esquemes i amb l'advertiment que cap validador no detecta un canvi de significat; el desplegament progressiu declarat al costat del servei amb anàlisi automàtica de mètriques; GitOps per necessitat, perquè el model pull redueix les credencials del CI i respon amb git log a què hi ha desplegat; i traces propagades també a través de les cues, amb marques de desplegament per servei en un panell comú que converteix una hora de reunió en trenta segons de consulta. El cost, dit sense adorns: un 35 % més d'infraestructura, dues persones de plataforma a temps complet, i un change failure rate que va pujar al 5,1 % durant tres mesos abans de tornar a baixar. Per això la lliçó acaba amb la llista de quan no partir, i amb el recordatori que el monòlit modular dóna gairebé tot el benefici organitzatiu sense cap dels costos de xarxa.
Els tres casos vistos fins aquí compartien una condició de partida: eren sistemes moderns, amb proves, amb control de versions sa, amb equips que podien decidir com treballar. L'última lliçó del mòdul treu aquesta xarxa. Gestor Citas 4 és el producte anterior de l'empresa —un monòlit Java/JSP sobre Tomcat del 2011, sense ni una sola prova automatitzada, desplegat per FTP els dissabtes a la nit, amb la configuració editada a mà al servidor i tres clients grans que encara paguen per ell— i no es pot reescriure. Allà no es tracta de triar entre monorepo i polyrepo ni d'afinar un canari: es tracta de decidir per on es comença quan no hi ha res, en quin ordre es construeixen els increments perquè cadascun aporti valor per si sol, i fins on val la pena arribar en un producte que només està en manteniment.
Curs de CI/CD: Integració i Desplegament Continu
Mòdul 1: Introducció al CI/CD
- Conceptes Bàsics de CI/CD
- Beneficis del CI/CD
- Eines Populars de CI/CD
- El Projecte del Curs: l'Aplicació que Automatitzarem
- Mètriques DORA: Com es Mesura el Lliurament de Programari
Mòdul 2: Integració Contínua (CI)
- Introducció a la Integració Contínua
- Configuració d'un Entorn de CI
- Automatització de la Construcció
- Proves Automatitzades
- Qualitat de Codi i Anàlisi Estàtica
- Artefactes, Versionat i Promoció
- Integració amb el Control de Versions
Mòdul 3: Desplegament Continu (CD)
- Introducció al Desplegament Continu
- Automatització del Desplegament
- Infraestructura com a Codi i Entorns Reproduïbles
- Estratègies de Desplegament
- Feature Flags, Rollback i Recuperació davant Errors
- Monitoratge i Retroalimentació
Mòdul 4: Pràctiques Avançades de CI/CD
- Pipelines de CI/CD
- Gestió de Dependències
- Seguretat en CI/CD
- Escalabilitat i Rendiment
- Pipeline as Code: Plantilles, Reutilització i Proves del Pipeline
- Bases de Dades al Pipeline: Migracions Segures
Mòdul 5: Implementació de CI/CD en Projectes Reals
- Cas d'Estudi: Projecte Web
- Cas d'Estudi: Aplicació Mòbil
- Cas d'Estudi: Microserveis
- Cas d'Estudi: Modernitzar un Projecte Legacy
Mòdul 6: Eines i Tecnologies
- Jenkins
- GitLab CI/CD
- CircleCI
- Travis CI
- Docker i Kubernetes
- GitHub Actions a Fons
- Comparativa i Criteris per Triar Eina
Mòdul 7: Exercicis Pràctics
- Exercici 1: Configuració d'un Pipeline Bàsic
- Exercici 2: Integració de Proves Automatitzades
- Exercici 3: Desplegament en un Entorn de Producció
- Exercici 4: Monitoratge i Retroalimentació
- Exercici 5: Enfortir el Pipeline amb Seguretat i Secrets
- Projecte Final: Pipeline Complet d'Extrem a Extrem
