servei-cataleg ja arrenca, però el seu src/config.js és una versió mínima que llegeix quatre variables i confia que estiguin bé. En un sistema amb set serveis, tres entorns (desenvolupament, staging, producció) i diverses rèpliques per servei, la configuració deixa de ser un detall: és el que fa que el mateix artefacte (la mateixa imatge, el mateix codi) es comporti de manera diferent a cada lloc sense recompilar res, i és també la via més habitual per on es filtren contrasenyes. Aquesta lliçó fixa com un servei de TechCorp obté, valida i fa servir la seva configuració: què és configuració i què no, d'on ve i amb quina precedència, un mòdul config.js amb validació fail-fast que reutilitzaran tots els serveis (l'escrivim per a servei-comandes, que a 04-04 el necessita sencer), com es tracten els secrets, quan compensa una configuració centralitzada, i què són i com es llegeixen els feature flags.

Contingut

  1. Configuració versus codi: el principi dels 12 factors
  2. Tipus de configuració: per entorn, per instància i feature flags
  3. Fonts i precedència
  4. src/config.js: carregar i validar en arrencar
  5. .env en desenvolupament i secrets a la resta d'entorns
  6. Configuració centralitzada: quan compensa
  7. Recàrrega en calent versus reinici
  8. Feature flags amb un exemple mínim
  9. La configuració de TechCorp per entorn i el seu enllaç amb Kubernetes

  1. Configuració versus codi: el principi dels 12 factors

La regla del tercer factor de la metodologia Twelve-Factor App és la que seguim: la configuració viu a l'entorn, no al codi. La prova pràctica per distingir-les: aquest valor canvia entre desenvolupament, staging i producció, o entre dues instal·lacions del mateix servei? Si sí, és configuració; si no, és codi.

És configuració (varia per entorn) És codi (no varia)
URL de la base de dades, del broker, d'altres serveis (COMANDES_DB_URL, RABBITMQ_URL, CATALEG_URL) El nom de l'exchange techcorp.esdeveniments i de la cua comandes.saga (formen part del contracte de 03-02, iguals a tots els entorns)
Port d'escolta, nivell de log, timeouts La màquina d'estats de la comanda, les regles de validació
Credencials, claus d'API, certificats Els codis d'error (CLIENT_NO_EXISTEIX) i les rutes (/v1/comandes)
Feature flags (CATALEG_REMOT, PAGAMENT_NOU_PROVEIDOR) El límit de 100 ids per lot (regla del contracte, 03-01)

El corol·lari que més costa d'interioritzar: la mateixa imatge Docker s'executa als tres entorns. Si per passar a producció cal reconstruir amb un altre fitxer de propietats a dins, no s'ha provat a staging el que es desplega. La imatge es construeix un cop (05-01, 05-03) i la configuració s'injecta en arrencar.

  1. Tipus de configuració: per entorn, per instància i feature flags

Tipus Exemples a TechCorp Qui la canvia i amb quina freqüència
Per entorn URLs, credencials, LOG_NIVELL, TIMEOUT_HTTP_MS Plataforma, en crear o canviar l'entorn; poques vegades
Per instància PORT (en local, si s'executen dos serveis alhora), HOSTNAME/nom del pod per als logs, nombre de workers L'orquestrador, a cada rèplica; el servei la llegeix, no la decideix
Feature flags CATALEG_REMOT (llegir productes del nou servei o del monòlit, 02-04), PAGAMENT_NOU_PROVEIDOR Els equips de producte/desenvolupament, sovint i sense desplegar

Els tres tipus entren per la mateixa porta (variables d'entorn) al principi; la diferència és a la freqüència de canvi, i això és el que més endavant justifica un servei de flags separat (apartat 8).

  1. Fonts i precedència

Un servei sol poder rebre la configuració de diversos llocs alhora. Perquè el comportament sigui previsible cal fixar una precedència i no sortir-se'n:

flowchart LR
    A[1. Valors per defecte<br/>a config.js] --> B[2. Fitxer .env<br/>només en desenvolupament]
    B --> C[3. Variables d'entorn<br/>del procés]
    C --> D[4. Secrets muntats<br/>com a fitxer o variable]
    D --> E[Objecte config<br/>validat i immutable]
    style E fill:#dfd,stroke:#393
  • Valors per defecte: només per al que és segur en qualsevol entorn (PORT=3002, LOG_NIVELL=info, TIMEOUT_HTTP_MS=2000). Mai una URL de base de dades per defecte que apunti a algun lloc real, i mai una credencial.
  • Fitxer .env: comoditat de desenvolupament (04-02). En producció no existeix.
  • Variables d'entorn: la interfície universal. Docker, Docker Compose, Kubernetes, systemd, GitHub Actions... tots saben posar-les.
  • Secrets: tècnicament també arriben com a variables o com a fitxers muntats; els distingim perquè el seu cicle de vida (qui els veu, com roten) és diferent (apartat 5).

El que no és a la llista: arguments de línia d'ordres (barregen configuració amb arrencada) i fitxers de configuració per entorn dins de la imatge (config/produccio.json), que violen l'apartat 1.

  1. src/config.js: carregar i validar en arrencar

L'objectiu és fallar de pressa i amb un missatge clar si falta alguna cosa o té un tipus incorrecte, en lloc d'arrencar i descobrir a les tres de la matinada que TIMEOUT_HTTP_MS era la cadena "2s" i que fetch la va interpretar com a NaN. Fem servir zod, ja present al servei des de 04-02. Aquest és el config.js complet de servei-comandes, amb les variables acordades a 03-05:

// src/config.js (servei-comandes)
const { z } = require('zod');

// 1. L'esquema: nom, tipus, obligatorietat i valor per defecte de CADA variable que el servei fa servir.
//    És també la seva documentació: si no és aquí, el servei no la llegeix.
const esquema = z.object({
  NODE_ENV: z.enum(['development', 'test', 'production']).default('development'),
  PORT: z.coerce.number().int().min(1).max(65535).default(3002),        // coerce: les variables d'entorn sempre són text
  LOG_NIVELL: z.enum(['fatal', 'error', 'warn', 'info', 'debug', 'trace']).default('info'),

  // Dependències pròpies (sense valor per defecte: obligatòries)
  COMANDES_DB_URL: z.string().url().startsWith('postgres'),             // postgres://usuari:clau@host:5432/comandes
  RABBITMQ_URL: z.string().url().startsWith('amqp'),                    // amqp://rabbitmq:5672

  // Serveis que Comandes crida de manera síncrona (03-01, 03-05)
  CATALEG_URL: z.string().url(),                                        // http://servei-cataleg:3001
  CLIENTS_URL: z.string().url(),                                        // http://servei-clients:3004
  TIMEOUT_HTTP_MS: z.coerce.number().int().min(100).max(30000).default(2000),

  // Relay de l'outbox (04-04)
  OUTBOX_INTERVAL_MS: z.coerce.number().int().min(50).default(500),

  // Feature flag heretat del branch by abstraction de 02-04:
  // true = llegir productes de servei-cataleg; false = del monòlit (via CATALEG_URL apuntant-hi)
  CATALEG_REMOT: z.enum(['true', 'false']).default('true').transform((v) => v === 'true')
});

// 2. Carregar i validar. safeParse no llança: ens deixa construir un missatge llegible.
function carregarConfig(entorn = process.env) {
  const resultat = esquema.safeParse(entorn);
  if (!resultat.success) {
    const problemes = resultat.error.issues.map((i) => `  - ${i.path.join('.')}: ${i.message}`).join('\n');
    // Fail-fast: sense configuració vàlida no hi ha servei. Kubernetes veurà el pod en CrashLoopBackOff amb aquest missatge.
    throw new Error(`Configuració invàlida:\n${problemes}`);
  }
  // 3. Immutable: ningú no pot canviar config.PORT en temps d'execució "per provar"
  return Object.freeze(resultat.data);
}

// 4. Es carrega UN cop en importar el mòdul. Els tests criden carregarConfig() amb un objecte propi.
const config = carregarConfig();

// 5. Versió segura per a logs i per a /health: els secrets, emmascarats
function configPerALog(c = config) {
  const ocultar = (url) => url.replace(/\/\/([^:]+):([^@]+)@/, '//$1:***@');   // postgres://svc:***@host/comandes
  return { ...c, COMANDES_DB_URL: ocultar(c.COMANDES_DB_URL), RABBITMQ_URL: ocultar(c.RABBITMQ_URL) };
}

module.exports = { config, carregarConfig, configPerALog };

Com es fa servir des de servidor.js (i només des d'allà i des dels mòduls d'infraestructura: les rutes i el domini no llegeixen process.env mai):

// src/servidor.js (servei-comandes, fragment)
const { config, configPerALog } = require('./config');    // si la config és invàlida, aquest require llança i el procés mor aquí
const logger = crearLogger({ servei: 'servei-comandes', nivell: config.LOG_NIVELL });
logger.info({ config: configPerALog() }, 'configuració carregada');

const pool = crearPoolPostgres({ url: config.COMANDES_DB_URL });
const catalegClient = crearCatalegClient({ urlBase: config.CATALEG_URL, timeoutMs: config.TIMEOUT_HTTP_MS });

I el que passa si algú desplega sense RABBITMQ_URL i amb un port impossible:

Error: Configuració invàlida:
  - RABBITMQ_URL: Required
  - PORT: Number must be less than or equal to 65535

Cinc decisions que convé entendre:

  1. z.coerce perquè totes les variables d'entorn són cadenes: PORT="3002" s'ha de convertir en el número 3002, i CATALEG_REMOT="true" en el booleà true. Sense conversió explícita, if (process.env.CATALEG_REMOT) és cert també per a "false".
  2. Obligatori sense valor per defecte per a tot el que apunta a alguna cosa real. Un COMANDES_DB_URL per defecte que apunti a localhost "funciona" al portàtil i arrenca en producció contra enlloc, amb un error confús minuts després.
  3. Validació a l'arrencada, no al primer ús: és preferible un pod que no arrenca (i que Kubernetes marca com a fallit immediatament) a un que arrenca, passa el readiness i falla a la primera comanda.
  4. Object.freeze perquè la configuració sigui un valor, no un estat mutable global.
  5. Les dependències reben valors, no llegeixen process.env: crearCatalegClient({ urlBase, timeoutMs }). És el que permet, a 04-05, provar el client contra un servidor fals en un altre port sense manipular l'entorn del procés.

  1. .env en desenvolupament i secrets a la resta d'entorns

En desenvolupament, el fitxer .env (carregat per dotenv a npm run dev, 04-02) conté els valors locals. Dues regles fixes:

  • .env és a .gitignore. Sempre. Encara que "només tingui contrasenyes de desenvolupament": el que avui és dev-comandes demà és una còpia de la de producció enganxada amb presses.
  • .env.exemple que es versiona: mateixes claus, valors d'exemple o buits, comentaris. És la documentació viva del que necessita el servei i el primer que copia un desenvolupador nou.
# .env.exemple (servei-comandes) — copiar a .env i omplir. MAI posar valors reals aquí.
NODE_ENV=development
PORT=3002
LOG_NIVELL=debug
COMANDES_DB_URL=postgres://svc_comandes:dev-comandes@localhost:5432/comandes
RABBITMQ_URL=amqp://localhost:5672
CATALEG_URL=http://localhost:3001
CLIENTS_URL=http://localhost:3004
TIMEOUT_HTTP_MS=2000
OUTBOX_INTERVAL_MS=500
CATALEG_REMOT=true

A staging i producció no hi ha .env. Els valors no sensibles els posa l'orquestrador com a variables d'entorn; els secrets (contrasenyes de BD, credencials del broker, claus de la passarel·la de pagament) segueixen tres regles:

Regla Què significa a la pràctica
Mai al repositori Ni a .env, ni en YAML de Kubernetes en clar, ni a l'historial de git (un secret que es va pujar i es va esborrar continua a l'historial: cal rotar-lo)
Mai a la imatge Ni COPY .env, ni ENV PASSWORD=... al Dockerfile; qualsevol amb accés al registre d'imatges el llegiria
Injectats per l'entorn en temps d'execució El servei els rep com a variable d'entorn o com a fitxer muntat en una ruta coneguda; no sap ni li importa d'on surten

Aquest últim punt és la interfície entre el servei i la gestió de secrets, i és tot el que un desenvolupador de Comandes necessita saber: COMANDES_DB_URL arribarà com a variable d'entorn. Qui la posa pot ser un Secret de Kubernetes (05-02), Vault amb injecció al pod, o el gestor de secrets del núvol (07-04 hi aprofundeix). Si en algun cas el secret arriba com a fitxer (pràctica habitual amb Vault: /vault/secrets/comandes-db), el config.js s'amplia amb una regla senzilla: si existeix COMANDES_DB_URL_FILE, es llegeix el fitxer i el seu contingut passa a ser COMANDES_DB_URL. Aquesta convenció <VARIABLE>_FILE és la que fan servir les imatges oficials de PostgreSQL o RabbitMQ, i l'adopta la plantilla de TechCorp.

  1. Configuració centralitzada: quan compensa

Quan hi ha desenes de serveis i molts valors compartits (per exemple, la URL del broker, que és la mateixa per a tots), algunes organitzacions munten un servidor de configuració del qual cada servei llegeix en arrencar (o al qual se subscriu):

Eina Model Punt fort Cost
Spring Cloud Config Servidor HTTP que serveix propietats des de git Integració perfecta amb Spring; historial a git Només té sentit en ecosistemes Java/Spring
Consul KV Magatzem clau-valor distribuït amb watch Canvis en calent; es combina amb el descobriment de 03-05 Una peça més a operar i assegurar
etcd Clau-valor amb consistència forta És el que fa servir el mateix Kubernetes És rar fer-lo servir directament des d'aplicacions
AWS Parameter Store / Secrets Manager, Azure App Configuration, GCP Secret Manager Servei gestionat Res a operar; auditoria i rotació incloses Acoblament al núvol; latència i quotes de lectura
ConfigMap + Secret de Kubernetes Objectes del clúster injectats com a variables o fitxers Ja hi és; declaratiu; per namespace Sense recàrrega (apartat 7); no és un gestor de secrets "de debò" sense xifratge addicional

Quan compensa un servidor de configuració: quan canvien valors sovint i en molts serveis alhora (llindars, feature flags, límits de tarifa), o quan el mateix valor ha d'estar sincronitzat en desenes de llocs i editar vint ConfigMaps és un risc. Quan no: amb set serveis, tres entorns i valors que canvien poques vegades l'any, és una peça més que pot caure i que tots els serveis necessiten en arrencar (si el servidor de configuració no respon, res no arrenca).

Decisió de TechCorp: variables d'entorn com a interfície única del servei; a Kubernetes, ConfigMap per a valors no sensibles i Secret per a secrets (05-02). Els feature flags, si creixen, aniran a un servei de flags (apartat 8), no a un servidor de configuració general.

  1. Recàrrega en calent versus reinici

Un servidor de configuració o un ConfigMap muntat com a fitxer permeten que un servei detecti canvis i els apliqui sense reiniciar. Sona atractiu, però té un cost:

Aspecte Recàrrega en calent Reinici (nova configuració = nou desplegament)
Complexitat al codi Alta: cal gestionar quines parts recarregar (es pot canviar COMANDES_DB_URL amb un pool obert?), atomicitat, valors a mig aplicar Cap: el procés arrenca amb la config nova i punt
Traçabilitat Difícil saber quina configuració té cada rèplica en cada moment Cada desplegament queda registrat; totes les rèpliques convergeixen
Validació Cal validar en calent i decidir què fer si el nou valor és invàlid El fail-fast de l'apartat 4: si és invàlida, el nou pod no arrenca i l'antic continua
Interrupció Cap Cap, si el desplegament és rolling (05-04): les rèpliques se substitueixen una a una

Per això a Kubernetes la pràctica habitual és reiniciar: un canvi de configuració és un rollout més, amb la mateixa seguretat i el mateix rollback que un canvi de codi. L'excepció són els feature flags, el valor dels quals ha de canviar en segons i sense desplegament: per a ells, i només per a ells, s'accepta la lectura dinàmica.

  1. Feature flags amb un exemple mínim

Un feature flag és un valor de configuració que activa o desactiva un comportament en temps d'execució. Serveix per desplegar codi apagat, encendre'l gradualment i apagar-lo en segons si alguna cosa va malament (05-04 ho relacionarà amb els desplegaments canary). Ja en fem servir un a 02-04, CATALEG_REMOT, i aquí afegim el que necessitarà Pagaments: PAGAMENT_NOU_PROVEIDOR.

Versió mínima, llegida de la configuració (canviar-lo requereix reinici):

// src/flags.js (servei-pagaments) — versió 1: flags com a configuració
function crearFlags(config) {
  return {
    // Retorna una funció per no acoblar la resta del codi a "d'on surt" el flag
    estaActiu: (nom) => config.FLAGS[nom] === true
  };
}
// Ús al cas d'ús de cobrament (Pagaments): el codi nou i el vell conviuen; el flag decideix
async function cobrar(comanda, { flags, passarellaActual, passarellaNova }) {
  const passarella = flags.estaActiu('PAGAMENT_NOU_PROVEIDOR') ? passarellaNova : passarellaActual;
  return passarella.cobrar({ import: comanda.total, clauIdempotencia: comanda.comandaId });   // 02-05
}

Quan els flags es multipliquen, es canvien diverses vegades al dia o es volen activar només per a un percentatge d'usuaris, es passa a un servei de flags (Unleash, Flagsmith, LaunchDarkly, o el mòdul de flags de GitLab). El servei consulta l'estat per SDK, amb memòria cau local i valor per defecte si el servei de flags no respon. L'important és que la interfície estaActiu(nom) no canvia: només canvia la implementació de crearFlags, i el cas d'ús cobrar ni se n'assabenta:

// src/flags.js — versió 2: flags des d'Unleash (concepte; SDK real: unleash-client)
function crearFlags({ clientUnleash, perDefecte = {} }) {
  return { estaActiu: (nom, context) => clientUnleash.isEnabled(nom, context, perDefecte[nom] ?? false) };
}

Dues advertències: els flags han de tenir data de caducitat (un flag que fa un any que és a true és codi mort disfressat), i el seu valor per defecte en producció ha de ser el comportament segur (el proveïdor de pagament actual, no el nou).

  1. La configuració de TechCorp per entorn i el seu enllaç amb Kubernetes

Amb tot l'anterior, la taula que l'equip de Plataforma manté per a servei-comandes (valors ficticis; en producció els secrets ni tan sols són en aquesta taula, només el nom del Secret que els conté):

Variable Desenvolupament (.env) Staging Producció Font a Kubernetes
NODE_ENV development production production ConfigMap
PORT 3002 3002 3002 ConfigMap
LOG_NIVELL debug info info ConfigMap
COMANDES_DB_URL postgres://svc_comandes:dev-comandes@localhost:5432/comandes postgres://svc_comandes:stg-Xk3…@pg-staging:5432/comandes (al Secret comandes-db) Secret
RABBITMQ_URL amqp://localhost:5672 amqp://comandes:stg-Rq7…@rabbitmq:5672 (al Secret comandes-rabbitmq) Secret
CATALEG_URL http://localhost:3001 http://servei-cataleg:3001 http://servei-cataleg:3001 ConfigMap
CLIENTS_URL http://localhost:3004 http://servei-clients:3004 http://servei-clients:3004 ConfigMap
TIMEOUT_HTTP_MS 2000 2000 2000 ConfigMap
OUTBOX_INTERVAL_MS 500 500 250 ConfigMap
CATALEG_REMOT true true falsetrue durant la migració ConfigMap (o servei de flags)

Fixa't que CATALEG_URL val el mateix a staging i producció: és el nom DNS del Service de Kubernetes (03-05), resolt dins de cada namespace. Només canvien les credencials i algun ajust de rendiment.

Com es connecta això amb Kubernetes, sense escriure encara els YAML (05-02): un ConfigMap anomenat servei-comandes-config conté les claus no sensibles; un Secret anomenat comandes-db conté COMANDES_DB_URL; el Deployment de Comandes declara "injecta totes les claus d'aquest ConfigMap i d'aquest Secret com a variables d'entorn" (envFrom). Des de dins del contenidor, process.env.COMANDES_DB_URL existeix i config.js la valida exactament igual que al portàtil. El servei no distingeix l'origen, i aquesta indiferència és l'objectiu de tota la lliçó.

Errors Comuns i Consells

  • Configuració al codi (const CATALEG_URL = 'http://servei-cataleg:3001'). Funciona fins que cal apuntar a un altre lloc en proves o en un segon entorn. Tot el que varia, per variable d'entorn; tot, per config.js.
  • Valors per defecte perillosos: COMANDES_DB_URL per defecte a localhost, NODE_ENV per defecte a development amb logs verbosos i stack traces al client en producció. Per defecte, només l'innocu.
  • process.env repartit per tot el codi. Cada process.env.X fora de config.js és una variable sense validar, sense documentar i sense valor per defecte clar. Una sola porta d'entrada.
  • Secrets als logs. logger.info({ config }) amb la URL completa de PostgreSQL envia la contrasenya a Loki. configPerALog() sempre.
  • Secrets a git "només aquesta vegada". Rotació obligatòria; esborrar-los de l'últim commit no n'hi ha prou.
  • Booleans com a cadenes. if (process.env.CATALEG_REMOT) és sempre cert. transform((v) => v === 'true').
  • Recarregar configuració en calent per si de cas. Complexitat sense necessitat; un rollout és més segur i auditable. Dinàmic només per a flags.
  • Flags eterns. Cada flag amb propietari, propòsit i data de retirada a la mateixa definició.

Exercicis

Exercici 1. Escriu l'esquema zod de src/config.js per a servei-cataleg (04-02) amb les variables PORT (per defecte 3001), MONGO_URL (obligatòria, ha de començar per mongodb), MONGO_BD (per defecte cataleg), LOG_NIVELL i NODE_ENV. Afegeix la variable CACHE_MAX_AGE_S (segons del Cache-Control, enter entre 0 i 3600, per defecte 30) i explica en una frase per què aquesta sí que és configuració i no codi.

Exercici 2. Un desenvolupador de Comandes proposa que, si CATALEG_URL no està definida, el servei faci servir http://servei-cataleg:3001 per defecte "perquè sempre és aquesta". Dona un argument a favor, dos en contra, i decideix.

Exercici 3. Implementa la convenció <VARIABLE>_FILE de l'apartat 5 a carregarConfig: abans de validar, per a cada variable de l'esquema, si existeix NOM_FILE i no existeix NOM, llegeix el fitxer (síncron, UTF-8, sense salt de línia final) i fes servir el seu contingut com a valor.

Solucions

Solució 1.

const esquema = z.object({
  NODE_ENV: z.enum(['development', 'test', 'production']).default('development'),
  PORT: z.coerce.number().int().min(1).max(65535).default(3001),
  LOG_NIVELL: z.enum(['fatal', 'error', 'warn', 'info', 'debug', 'trace']).default('info'),
  MONGO_URL: z.string().url().startsWith('mongodb'),          // obligatòria: sense valor per defecte
  MONGO_BD: z.string().min(1).default('cataleg'),
  CACHE_MAX_AGE_S: z.coerce.number().int().min(0).max(3600).default(30)
});

CACHE_MAX_AGE_S és configuració perquè el seu valor òptim depèn de l'entorn i del moment (en una campanya amb canvis de preu freqüents s'abaixaria a 5 s sense desplegar codi); el fet que el lot porti Cache-Control sí que és contracte i, per tant, codi.

Solució 2. A favor: comoditat; a Kubernetes el nom és estable (03-05) i evita una variable a cada ConfigMap. En contra: (1) en desenvolupament local aquest nom no resol, i l'error ("ENOTFOUND servei-cataleg") apareix a la primera petició, no en arrencar; (2) durant la migració amb CATALEG_REMOT=false, CATALEG_URL ha d'apuntar al monòlit, i un valor per defecte "correcte" amaga que algú va oblidar configurar-la en un entorn. Decisió: obligatòria, sense valor per defecte; el ConfigMap la porta explícita. És una línia més de YAML a canvi d'una fallada clara a l'arrencada.

Solució 3.

const fs = require('node:fs');

function resoldreFitxersDeSecrets(entorn) {
  const resultat = { ...entorn };
  for (const nom of Object.keys(esquema.shape)) {                 // només les variables que l'esquema coneix
    const rutaFitxer = entorn[`${nom}_FILE`];
    if (rutaFitxer && resultat[nom] === undefined) {
      resultat[nom] = fs.readFileSync(rutaFitxer, 'utf8').replace(/\r?\n$/, '');
    }
  }
  return resultat;
}

function carregarConfig(entorn = process.env) {
  const resultat = esquema.safeParse(resoldreFitxersDeSecrets(entorn));
  // ... igual que abans
}

Amb això, muntar el secret de Vault a /vault/secrets/comandes-db i definir COMANDES_DB_URL_FILE=/vault/secrets/comandes-db funciona sense tocar la resta del servei; i si el fitxer no existeix, readFileSync llança en arrencar, que és el que volem.

Conclusió

La configuració d'un microservei de TechCorp queda així: viu a l'entorn, no al codi ni a la imatge; arriba per variables d'entorn (o fitxers muntats, amb la convenció _FILE) amb una precedència clara (valors per defecte innocus → .env només en desenvolupament → entorn → secrets); entra per una única porta, src/config.js, que la valida amb zod en arrencar i falla de pressa amb un missatge llegible, la congela i la reparteix a les dependències com a valors; els secrets mai toquen el repositori ni la imatge i el servei no sap d'on surten (Kubernetes Secret, Vault: 05-02 i 07-04); la configuració centralitzada es reserva per a quan el volum de canvis ho justifiqui; els canvis s'apliquen reiniciant llevat dels feature flags, que tenen la seva interfície estaActiu(nom) i, si creixen, el seu servei; i la taula per entorn de servei-comandes fixa els valors concrets de PORT, COMANDES_DB_URL, RABBITMQ_URL, CATALEG_URL, CLIENTS_URL, TIMEOUT_HTTP_MS, OUTBOX_INTERVAL_MS i CATALEG_REMOT.

Aquest config.js no és un exercici teòric: és el primer fitxer de servei-comandes, el servei més complex de TechCorp. A la lliçó següent construïm la resta sobre la plantilla de 04-02: el pool de PostgreSQL i les migracions, els clients HTTP cap a Catàleg i Clients amb el seu timeout i la seva ACL, el cas d'ús crearComanda amb Idempotency-Key i outbox a la mateixa transacció, el relay que publica a RabbitMQ i els consumidors de la saga que mouen la comanda de PENDENT a CONFIRMADA. És la lliçó en què tot el que s'ha dissenyat des del mòdul 2 es converteix en codi que s'executa de cap a cap.

Curs de Microserveis

Mòdul 1: Introducció als Microserveis

Mòdul 2: Disseny de Microserveis

Mòdul 3: Comunicació entre Microserveis

Mòdul 4: Implementació de Microserveis

Mòdul 5: Desplegament i Orquestració

Mòdul 6: Monitoratge i Manteniment

Mòdul 7: Seguretat en Microserveis

Mòdul 8: Casos d'Estudi i Exemples Pràctics

© Copyright 2026. Tots els drets reservats