0030. Registry istanze connettore: N istanze per tipo, via lista env + registry generico
- Stato: Accettata
- Data: 2026-07-24
- Decisori: Team BI/ETL
Contesto
Un'istanza ETL può realisticamente dover parlare con N istanze dello stesso tipo di connettore: più DB Postgres (sorgente + data warehouse), più workspace Noeva, più server FTP/SFTP, più bucket S3. Fino a oggi questo è risolto solo per Noeva: NOEVA_WORKSPACE_IDS elenca i workspace ammessi e buildWorkspaceRegistry (ADR-0022) li valida con fail-fast. Per Postgres, SQL Server, FTP, SFTP e S3, invece, la molteplicità esiste solo come convenzione: load*Config(prefix) legge una singola istanza da env e "più istanze" significa "chiama il loader con un prefisso diverso" (es. loadPgPoolConfig("DWH_")), senza alcun registro né validazione di quali istanze l'ETL può toccare.
ADR-0021 aveva esplicitamente rimandato la config per-istanza dei connettori sorgente/destinazione "a un ADR dedicato": questo è quell'ADR.
Un'alternativa discussa era definire le connessioni in un file JSON montato (vedi issue #63, poi superseded): scartata perché introduce un artefatto di segreti da mettere in sicurezza — che ADR-0021 aveva già rifiutato per i workspace — e una strategia segreti dedicata, mentre l'approccio a lista env resta dentro il modello .env già in uso e la regola "niente segreti in chiaro versionati" (CLAUDE.md).
Decisione
Ogni tipo di connettore dichiara le sue istanze con una lista env e le risolve attraverso un registry generico validato.
- Core generico (
src/connectors/shared/registry.ts):buildConnectorRegistry<T>(entries)costruisce una mappaid → T;get(id)lanciaConfigErrorfail-fast se l'id non è configurato — il messaggio elenca gli id ammessi, mai i segreti.buildWorkspaceRegistrydiventa un wrapper sottile sopra questo core, a comportamento Noeva invariato. - Loader per-connettore
load<Kind>Registry(env): parsa<PREFIX>_INSTANCES(lista separata da virgole; trim, no vuoti, no duplicati → fail-fast, comeloadEtlWorkerConfigs) e per ogni id chiamaload<Kind>Config("<PREFIX>_<ID>_"). Convenzione:PG_INSTANCES=src,dwh→PG_SRC_*/PG_DWH_*; idemMSSQL_INSTANCES,FTP_INSTANCES,SFTP_INSTANCES,S3_INSTANCES. Noeva mantieneNOEVA_WORKSPACE_IDS(istanze a credenziali condivise: una sola API key, varia solo l'id). - Default implicito: se
<PREFIX>_INSTANCESnon è impostata ma esistono le variabili<PREFIX>_*, il loader registra un'unica istanza con iddefault. Il caso a istanza singola resta quindi ergonomico, ma passa comunque dal registry. - Registry obbligatorio (breaking): le pipeline non chiamano più
load*Config(prefix)direttamente; risolvono i connettori conregistry.get(id).load*Configresta come mattone per-istanza usato dal registry, non come API di composizione. - Selezione istanza: ogni pipeline dichiara l'id come costante nel proprio codice (es.
const PG_INSTANCE = "langfuse"), risolto viaregistry.get(id). Un override via env per-pipeline resta un'aggiunta futura non necessaria ora. - Lifecycle: il registry è su config (
get(id): TConfig); l'apertura/chiusura dei pool resta nellarun()della pipeline (try/finally), come oggi. Per client stateless (Noeva/S3) il registry può esporre un factoryclient(id). - Descriptor: source/sink espongono l'id di istanza nel descriptor del nodo, così la UI (issue #64) può mostrarlo e mascherarne i valori sensibili.
Alternative considerate
- File JSON di config (issue #63) — scartata: artefatto di segreti da proteggere e strategia segreti dedicata, contro la semplicità del modello
.envesistente. - Config dal server via
RunContext/API — rimandata: è il seam predisposto da ADR-0021 per il futuro (gestione centrale, override per-workspace), ma cross-repo e fuori dallo scope. - Mantenere la convenzione a prefisso singolo come fallback — scartata: lascerebbe due percorsi (registry per Noeva, prefisso ad-hoc per gli altri) e nessuna guardia uniforme su "quali istanze sono ammesse".
Conseguenze
- Uniformità: qualunque connettore usa lo stesso registry e la stessa validazione fail-fast.
- Breaking: le pipeline che usavano
load*Config(prefix)diretto vanno migrate aregistry.get(id)(oggi: sololangfuse-sessions-daily)..env.example, schede pipeline etests/integration/*vanno aggiornati alla convenzione<PREFIX>_INSTANCES. - Nessun nuovo segreto e nessun nuovo artefatto: le credenziali restano in
.env; i messaggi d'errore elencano solo gli id ammessi. - Abilita le issue #64 (masking della config nel dettaglio nodo) e chiude/supersede #63 nell'epic #66.

