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
- Configuració versus codi: el principi dels 12 factors
- Tipus de configuració: per entorn, per instància i feature flags
- Fonts i precedència
src/config.js: carregar i validar en arrencar.enven desenvolupament i secrets a la resta d'entorns- Configuració centralitzada: quan compensa
- Recàrrega en calent versus reinici
- Feature flags amb un exemple mínim
- La configuració de TechCorp per entorn i el seu enllaç amb Kubernetes
- 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.
- 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).
- 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.
src/config.js: carregar i validar en arrencar
src/config.js: carregar i validar en arrencarL'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:
z.coerceperquè totes les variables d'entorn són cadenes:PORT="3002"s'ha de convertir en el número3002, iCATALEG_REMOT="true"en el booleàtrue. Sense conversió explícita,if (process.env.CATALEG_REMOT)és cert també per a"false".- Obligatori sense valor per defecte per a tot el que apunta a alguna cosa real. Un
COMANDES_DB_URLper defecte que apunti alocalhost"funciona" al portàtil i arrenca en producció contra enlloc, amb un error confús minuts després. - 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.
Object.freezeperquè la configuració sigui un valor, no un estat mutable global.- 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.
.env en desenvolupament i secrets a la resta d'entorns
.env en desenvolupament i secrets a la resta d'entornsEn 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 ésdev-comandesdemà és una còpia de la de producció enganxada amb presses..env.exemplesí 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=trueA 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.
- 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.
- 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.
- 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).
- 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 |
false → true 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, perconfig.js. - Valors per defecte perillosos:
COMANDES_DB_URLper defecte alocalhost,NODE_ENVper defecte adevelopmentamb logs verbosos i stack traces al client en producció. Per defecte, només l'innocu. process.envrepartit per tot el codi. Cadaprocess.env.Xfora deconfig.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
- Conceptes Bàsics de Microserveis
- Avantatges i Desavantatges dels Microserveis
- Comparació amb l'Arquitectura Monolítica
- Quan Adoptar Microserveis: Criteris de Decisió
- El Cas Pràctic del Curs: la Botiga Online de TechCorp
Mòdul 2: Disseny de Microserveis
- Principis de Disseny de Microserveis
- Descomposició d'Aplicacions Monolítiques
- Definició de Bounded Contexts
- Gestió de Dades: una Base de Dades per Servei
- Consistència Distribuïda: Sagues, CQRS i Event Sourcing
Mòdul 3: Comunicació entre Microserveis
- APIs RESTful
- Missatgeria Asíncrona
- Protocols de Comunicació: gRPC, GraphQL
- API Gateway i Backend for Frontend
- Descobriment de Serveis i Balanceig de Càrrega
- Contractes i Versionat d'APIs
Mòdul 4: Implementació de Microserveis
- Elecció de Tecnologies i Eines
- Desenvolupament d'un Microservei Simple
- Gestió de Configuració
- Integració Pràctica: Consumir APIs i Publicar Esdeveniments
- Proves en Microserveis: Unitàries, d'Integració i de Contracte
Mòdul 5: Desplegament i Orquestració
- Contenidors i Docker
- Orquestració amb Kubernetes
- CI/CD per a Microserveis
- Estratègies de Desplegament: Rolling, Blue-Green i Canary
- Service Mesh: Istio i Linkerd
Mòdul 6: Monitoratge i Manteniment
- Monitoratge i Logging
- Traçabilitat Distribuïda amb OpenTelemetry
- Gestió d'Errors i Recuperació
- Escalabilitat i Rendiment
- SLOs, Alertes i Gestió d'Incidents
Mòdul 7: Seguretat en Microserveis
- Autenticació i Autorització
- Seguretat en la Comunicació
- Pràctiques de Seguretat
- Seguretat en Contenidors i Kubernetes
