Skip to content

noeva-etl — Technical Overview

Documento condivisibile. Spiega cos'è, per cosa è pensato, cosa può fare, come si espande e qual è la direzione del prodotto. Versione: prodotto 0.1.x.


1. In una frase

noeva-etl è una piattaforma BI/ETL code-first in TypeScript: si costruiscono pipeline di integrazione dati (estrazione → trasformazione → caricamento) come software versionato, testato e documentato, non come job grafici opachi. Il nucleo di esecuzione di ogni pipeline è semplice, stateless e portabile: esegue una pipeline e restituisce un risultato completo (report di run), senza possedere database né stato proprio.

Il servizio si invoca in tre modi (vedi §5): batch (etl run, un container = una esecuzione, esito su stdout), daemon (etl worker, poller sulla tabella processes di Noeva, per l'integrazione con l'orchestrazione job del prodotto) e server + daemon (etl serve, il default dell'immagine: espone i grafi e le run all'app desktop noeva-etl-ui e avvia i worker nello stesso processo).

2. Per cosa è stato pensato

Sostituire l'approccio "ETL a scatola nera" (es. Talend) con uno code-first:

  • Versionabile — ogni pipeline è codice in git, rivedibile in Pull Request.
  • Testabile — connettori, trasformazioni e pipeline hanno test automatici (gate in CI).
  • Documentato — ogni pipeline/decisione ha la sua scheda/ADR, aggiornati nello stesso commit.
  • Portabile — un'unica immagine container gira sull'infra del cliente (Docker locale, ECS proprio o gestito da noi).
  • Agnostico — nessun lock-in: sorgenti e destinazioni sono intercambiabili; il servizio non impone un DB o un cloud.

Target d'uso: integrazioni dati ricorrenti e affidabili (sincronizzazioni, fatti per la BI, normalizzazioni cross-database) dove correttezza, ripetibilità e tracciabilità contano.

3. Cosa può fare oggi

CapacitàDettaglio
Primitive componibiliSource<T> (estrazione), Transform<I,O> (trasformazione), Sink<T> (caricamento), GraphPipeline (DSL a grafo graph()...build())
Nodi del grafosource, transform, lookup (query SQL in sola lettura a metà grafo, ADR-0041), join (unico fan-in), try-catch (ramo di gestione errori), sink
StreamingI connettori lavorano su AsyncIterable: dataset grandi senza caricarli in memoria
Contratti dati (Zod)Validazione di schema su input e output di ogni pipeline (validate)
Trasformazionimap, filter, e aggregazione stateful riusabile groupBy (init/reduce/finalize)
Gestione erroridead-letter per nodo (record invalidi instradati altrove, la run prosegue), ramo catch di un nodo try-catch, o fail-fast esplicito — mai scarti silenziosi
IdempotenzaSink con upsert/MERGE su chiave naturale: rieseguire non duplica
Connettori realiPostgreSQL (cursor streaming + upsert ON CONFLICT + lookup), SQL Server (streaming + MERGE + lookup), file (filesystem/S3/SFTP/FTP × CSV/JSON/XML/XLSX/Parquet), Noeva (file e cartelle dei workspace)
Cross-databaseUna pipeline può leggere da un DB e scrivere su un altro (es. PG → SQL Server)
GraphRunReportRisultato auto-descrittivo di ogni run (vedi §6)
CLI & containeretl list / etl run <pipeline> / etl serve / etl runs list; immagine Docker multi-arch su ECR
App desktopnoeva-etl-ui (Tauri): DAG delle pipeline, avvio manuale, storico ed esito delle run, nodo per nodo — mostra, non costruisce
Worker daemonetl worker — poller su processes via noeva-server-api (claim/progress HTTP, mai Supabase diretto), dispatch verso il registry pipeline; una istanza serve N workspace (ADR-0021)

Esempio reale incluso: langfuse-sessions-daily legge le sessioni da PostgreSQL, le aggrega per progetto/giorno e carica i fatti su SQL Server (cross-DB, idempotente).

4. Come funziona (architettura)

Ogni pipeline è un grafo di nodi (DAG), costruito in codice col DSL graph()…build():

flowchart LR
  ordini["source<br/>ordini"] --> valida["transform<br/>validate (contratto)"]
  clienti["source<br/>clienti"] --> join["join<br/>(a chiave)"]
  valida -.record invalido.-> scarti["dead-letter"]
  valida --> join
  join --> lookup["lookup<br/>(query SQL read-only)"]
  lookup --> guard["try-catch"]
  guard --> dwh["sink<br/>DWH"]
  guard --> file["sink<br/>file"]
  guard -.catch.-> errori["ramo errori"]
  • Core (src/core) — primitive + runner; non contiene logica di dominio.
  • Dependency Injection — la definizione della pipeline è agnostica: sorgente e sink concreti si iniettano all'avvio. Per questo lo stesso codice gira su DB diversi e si testa con connettori in-memory.
  • Streaming pull-based — i record scorrono dai source ai sink senza essere caricati tutti in memoria; è il ritmo di scrittura a regolare quello di lettura.
  • Tre percorsi per un record che fallisce: dead-letter sul nodo (il record va altrove, la run prosegue), ramo catch di un nodo try-catch (gli errori di un intero tratto del grafo finiscono su nodi di gestione dedicati), fail-fast (la run si ferma — obbligatorio per le aggregazioni stateful).
  • Osservabilità — logger JSON strutturato + metriche; ogni run produce un GraphRunReport con i conteggi di ogni nodo.

Stack: TypeScript ESM strict, Node ≥ 20, pnpm, tsup, vitest, ESLint/Prettier, Zod. Dipendenze runtime: i driver dei database (pg, mssql), le librerie per storage e formati dei file (AWS SDK, SFTP, FTP, CSV, XLSX, XML, Parquet), express per il server locale di etl serve — ciascuna introdotta con un ADR. Nessuna dipendenza Supabase: il claim/report sulla tabella processes passa da noeva-server-api via HTTP (fetch nativo).

5. Come si usa e si distribuisce

bash
# in sviluppo
pnpm run etl list
pnpm run etl run langfuse-sessions-daily

# build & push immagine multi-arch su ECR (latest + tag temporale)
./deploy-ecr.sh

# pull da ECR ed esegui in locale (Docker Desktop)
./run-ecr.sh langfuse-sessions-daily

# server + daemon: grafi e run via HTTP per l'app desktop, più i worker
pnpm run etl:serve

# solo daemon: polling continuo su processes (worker_type da env; uno o più
# workspace — NOEVA_WORKSPACE_IDS per il multi-workspace, ADR-0021)
pnpm run etl worker
  • Configurazione solo da variabili d'ambiente (PG*, MSSQL_*, ETL_ENV, …); nessun segreto nel codice o nell'immagine.
  • Tre modalità di invocazione: batch (etl run, un container = una esecuzione, poi termina — vedi ADR-0005 e runbooks/deployment.md); daemon (etl worker, resta in polling su processes finché non riceve SIGTERM/SIGINT — vedi ADR-0019 e runbooks/etl-worker.md); server + daemon (etl serve, il CMD di default dell'immagine: un solo container per tutta la piattaforma — vedi ADR-0028 e runbooks/graph-server.md).
  • App desktop noeva-etl-ui: si distribuisce come applicazione Tauri, non da ECR (ADR-0025).
  • Modello di deploy (attuale): immagine su ECR, scaricata ed eseguita in locale su Docker Desktop. È pronta anche per ECS (nostro o del cliente) quando servirà.

6. Il report di run è il "prodotto"

Il nucleo di esecuzione non persiste nulla: esegue e restituisce un risultato completo. È il chiamante a conservarlo: la CLI tiene le ultime run in locale (etl runs list, ADR-0039), il worker lo scrive nel job Noeva.

  • stdout = report in JSON (un oggetto, parsabile con jq); stderr = log.
  • exit code: 0 se success/partial, ≠0 se failed.
  • Anche in errore emette un report (status: failed) con dettagli e conteggi parziali.
  • Contiene: stato, tempi, versione, topologia reale del grafo (nodi e archi, con i conteggi e i tempi di ogni nodo), parametri, volumi (extracted/written/rejected), categorie di scarto, metriche, errore — senza segreti né payload/PII.

Chi invoca decide cosa farne (archiviarlo, mandarlo in alert, ignorarlo). Vedi run-report.md.

7. Come si espande

Crescita per composizione, senza toccare il core. Comandi di scaffold coerenti generano struttura + test + doc:

  • Nuova pipeline/new-pipeline <nome> (schema, definizione, run, test, scheda doc).
  • Nuovo connettore/new-connector <nome> [source|sink] (verso un nuovo sistema: API, file/CSV, altri DB, code…).
  • Nuova trasformazione/new-transform <nome> (logica riusabile, pura e testabile).

Aggiungere un sistema = scrivere un Source/Sink che rispetta l'interfaccia; tutte le pipeline esistenti possono usarlo subito (DI). La logica di dominio vive nelle trasformazioni, riusabili tra pipeline.

8. Perché ci si può fidare (governance)

  • Test obbligatori con soglie di coverage come gate di CI (GitHub Actions).
  • Documentazione nello stesso commit del codice (schede pipeline, ADR, runbook).
  • Decisioni tracciate in un registro di ADR (es. scelta code-first, connettori, distribuzione, report di run, worker daemon, modello a grafo, gestione errori).
  • Assistenti di qualità versionati (.claude/agents/): review ETL, audit dei contratti, sync della doc, autore dei test.
  • Regole operative vincolanti in CLAUDE.md.

9. Direzione del prodotto (chiara e semplice)

Un motore ETL piccolo, code-first e agnostico, che esegue pipeline affidabili e restituisce un risultato completo — e cresce per connettori e pipeline, non per complessità.

Principi guida (stabili):

  1. Semplice e stateless — il servizio è una funzione pipeline + config → report.
  2. Agnostico — nessun DB interno, nessun cloud obbligato, nessun push integrato.
  3. Code-first — niente builder grafici; le pipeline restano codice testato e documentato.
  4. Portabile — un'immagine, eseguibile ovunque, configurata da env.
  5. Espandibile per composizione — nuovi connettori/trasformazioni/pipeline, core stabile.

Cosa NON è (non-obiettivi, per scelta)

  • ❌ Non è un pipeline builder grafico: l'app desktop noeva-etl-ui mostra i DAG e le run, ma le pipeline si scrivono in codice.
  • ❌ Non è uno strumento di visualizzazione BI (per i grafici si collega Metabase/Grafana/Power BI alle tabelle prodotte).
  • ❌ Non possiede un database di stato (storico/audit = archiviazione dei report, a cura del chiamante).

Evoluzioni possibili (senza tradire i principi)

  • Scheduling esterno (cron / ECS Scheduled Task) — l'idempotenza rende sicure retry e sovrapposizioni.
  • Nuovi connettori (API REST, data warehouse, code di messaggi) e nuovi resolver per il nodo lookup (oggi PostgreSQL e SQL Server; un nuovo DBMS riusa connectors/shared/sql-lookup.ts).
  • Orchestrazione per dipendenze/backfill complessi (es. Temporal TS), solo se e quando servirà.
  • Persistenza opzionale dei report come adapter lato chiamante, mai dentro il servizio.

Riferimenti: README · architettura · ADR · connettori · schede pipeline · report di run · deployment · etl worker · etl serve.

Noeva è un marchio registrato di 4D S.R.L.