Skip to content

Architettura — panoramica

Modello mentale

La piattaforma è un framework ETL code-first. Ogni processo è un grafo (DAG) di nodi tipati, eseguito su stream (AsyncIterable) per scalare su dataset grandi. Il grafo è l'unica unità eseguibile: il DSL lineare è stato rimosso (ADR-0036).

flowchart LR
  src1["source"] --> v1["transform<br/>validate"]
  src2["source"] --> v2["transform<br/>validate"]
  v1 -.dead-letter.-> dl["sink di scarto"]
  v1 --> join["join<br/>(unico fan-in)"]
  v2 --> join
  join --> guard["try-catch"]
  guard --> map["transform / lookup"]
  map --> sinkA["sink A"]
  map --> sinkB["sink B<br/>(fan-out)"]
  guard -.catch.-> ramo["ramo di gestione errori"]

Il grafo è pull-based: la domanda parte dai sink e risale verso le source, quindi è il ritmo di scrittura a regolare quello di lettura. Il fan-in passa solo da join; il fan-out è libero. La forma del grafo è validata a runtime su .build() (ADR-0016).

Componenti del core (src/core/)

FileResponsabilità
connector.tsInterfacce Source<T> / Sink<T>, descriptor per la UI, connettori in-memory per i test
transform.tsInterfaccia Transform<I,O> + helper mapTransform / filterTransform / validate
graph-builder.tsDSL graph(name)…build()GraphPipeline
graph.tsTipi dei nodi, validazione della topologia, proiezione in PlanSpec per etl plan/UI
graph-runner.tsEsecuzione del grafo: streaming, dead-letter e scope try/catch, GraphRunReport
join.ts · lookup.ts · tee.tsJoin a chiave, lookup SQL in sola lettura, duplicazione dello stream sul fan-out
context.tsRunContext: env, params, logger, metriche, signal
observability.tsLogger JSON strutturato + contatori di metriche
errors.tsGerarchia EtlError per distinguere errori di dominio da bug
progress.ts · lineage.tsReport di avanzamento per nodo, identità dei record e lineage derivedFrom (opt-in)
schema-columns.ts · plan-column.tsColonne dei nodi ricavate dagli schemi Zod, per la UI del DAG

Gestione degli errori (dentro una run)

Tre percorsi, dal più locale al più drastico:

  • Dead-letter per nodo — su transform, lookup e join: il record che solleva EtlError esce su un sink dedicato e lo stream continua. Per le trasformazioni stateless.
  • Nodo try-catch — protegge i nodi a valle della sua porta out, sink compresi. Di default per-record: ogni errore va sul ramo catch, che è fatto di nodi veri (ADR-0029, ADR-0031).
  • Fail-fast — il default: l'errore aborta la run. L'unica scelta corretta per le trasformazioni stateful (aggregazioni).

Un errore che non è un EtlError aborta sempre la run: un bug va visto, non scartato.

Modalità di invocazione (del servizio)

Riguarda come una pipeline viene lanciata, non come processa i record. Il nucleo di esecuzione è lo stesso e stateless in tutti i casi: cambia solo chi la fa partire e dove finisce il GraphRunReport.

  • Batch (etl run, CLI): un container/processo = una esecuzione, report su stdout — vedi ADR-0005, ADR-0006. Chi la lancia in locale trova le run concluse con etl runs list (ADR-0039).
  • Daemon (etl worker, src/worker/): processo long-running che fa polling della tabella processes di Noeva via noeva-server-api, rivendica i job filtrati per worker_type+workspace_id con CAS atomico (server-side) e dispatcha la pipeline indicata da function al registry — vedi ADR-0019, che supera ADR-0017. Una singola istanza può servire N workspace avviando un poller per workspace (ADR-0021). Il report finisce nel job Noeva, con l'avanzamento per nodo durante la run (ADR-0018).
  • Server + daemon (etl serve, src/server/): il default dell'immagine Docker. Nello stesso processo espone via HTTP la topologia dei grafi e gli eventi delle run per noeva-etl-ui, e avvia i worker (ADR-0024, ADR-0028, ADR-0040). Mai insieme a un etl worker separato nello stesso ambiente.

La UI (noeva-etl-ui, submodule) è un'app desktop Tauri che consuma etl serve e noeva-server-api (ADR-0025).

Layout del repository

src/
  core/                 # framework (non contiene logica di dominio)
  connectors/           # connettori riusabili verso sistemi reali (DB, API, file, Noeva)
  transforms/           # trasformazioni riusabili di dominio
  pipelines/<nome>/     # una cartella per pipeline: schema, definizione, run, test
  pipelines/registry.ts # PIPELINES: ciò che CLI, worker e server possono lanciare
  worker/               # daemon: poller su `processes` via noeva-server-api (claim, dispatch, loop)
  server/               # `etl serve`: HTTP dei grafi e delle run
  runs/                 # storico locale delle run — mai importato da core/
tests/
  core/                 # test del framework
  integration/          # test con sistemi reali (separati dalla CI rapida)
docs/                   # convenzioni, ADR, runbook, schede pipeline
.claude/                # governance dell'agente: settings, comandi, skill
noeva-etl-ui/           # submodule: app desktop Tauri

Principi architetturali

  1. Il core non conosce il dominio. Niente regole di business in src/core.
  2. I confini sono tipati. Schema-contratto Zod su ingresso e uscita di ogni pipeline.
  3. Tutto è iniettabile. Sorgenti/sink concreti entrano dai parametri → testabilità e portabilità tra ambienti.
  4. L'IO sta nei nodi che lo dichiaranosource, sink, lookup — mai dentro una Transform, che resta pura.
  5. Osservabilità non opzionale. Ogni run produce un GraphRunReport: totali, contatori per nodo, durata, metriche — mai payload né segreti.
  6. Le decisioni si registrano. Cambi strutturali → ADR.

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