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

  1. Què té aquest context que Reservalia no tenia
  2. La partició i el graf de dependències
  3. Monorepo o polyrepo, amb l'execució selectiva
  4. El pipeline plantilla: estandarditzar sense ofegar
  5. Contract testing i la porta can-i-deploy
  6. L'entorn d'integració compartit i per què s'encalla
  7. Versionat i desplegament independents
  8. Compatibilitat en APIs i en esdeveniments
  9. Desplegament progressiu i GitOps
  10. Observabilitat distribuïda i correlació de desplegaments
  11. El cost organitzatiu i quan no partir
  12. Fitxa del cas
  13. Errors Comuns i Consells
  14. Exercicis
  15. Conclusió

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

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

  1. 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 }}
  1. 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.
  2. fetch-depth: 0 és imprescindible: sense l'historial complet no hi ha merge-base i el càlcul del diff falla silenciosament executant-ho tot.
  3. ...[$BASE] inclou els dependents, no només el que s'ha modificat. Si el PR toca packages/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 per paths sense seguir les dependències deixa passar canvis que trenquen un consumidor.
  4. La matriu es construeix en temps d'execució, així que un PR que toca només notificacions llança un job i no pas cinc. A Reservalia això va baixar la mitjana de 14 a 4,5 minuts.
  5. fail-fast: false perquè un servei trencat no cancel·li la informació dels altres: qui obre el PR vol veure tots els vermells de cop.

  1. 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 }}' }
  1. 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.
  2. 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.
  3. La plantilla reutilitza el reusable de la 04-05 en lloc de reimplementar-lo: composició, no duplicació.
  4. La referència va ancorada a un tag (@v3), mai a main. Un canvi a la plantilla no ha de trencar cinc equips alhora sense que ells ho decideixin; publicant v3.1 i 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

  1. Contract testing i la porta can-i-deploy

El 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
  });
});
  1. 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.
  2. 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.
  3. 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
  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        # 1

Retorna 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.

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

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.

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

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

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

  1. 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:

  1. 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.
  2. Etiquetar-ho tot amb servei i versio, per poder comparar el comportament de dues versions durant un canari.
  3. 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.

  1. 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í.

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

Mòdul 2: Integració Contínua (CI)

Mòdul 3: Desplegament Continu (CD)

Mòdul 4: Pràctiques Avançades de CI/CD

Mòdul 5: Implementació de CI/CD en Projectes Reals

Mòdul 6: Eines i Tecnologies

Mòdul 7: Exercicis Pràctics

Mòdul 8: Recursos Addicionals

© Copyright 2026. Tots els drets reservats