FireFeed Docs

Tipizzazione dati (TEXT-everywhere)

Perché tutte le colonne dei progetti sono TEXT, e quando potrebbe cambiare.

Versione MDX dell'ADR docs/adr/0002-text-everywhere-data-plane.md. Per il dettaglio della decisione, link e file rilevanti, vedi l'ADR.

I dati prodotto vengono caricati in tabelle Postgres dinamiche per progetto: company_<companyId>.project_<projectId>. Le colonne di queste tabelle corrispondono ai DatabaseField definiti dall'utente in UI (title, price, stock, ean, …). L'utente può aggiungere/rimuovere field a runtime.

Scelta di design: ogni nuova colonna viene creata come TEXT, senza eccezioni. Il rule engine fa cast on-demand via operatori (numeric:, date:).

Perché

  1. Schema dinamico: l'utente cambia field via UI. ALTER COLUMN TYPE su una tabella da milioni di righe è downtime non accettabile.
  2. Source eterogenee: feed marketplace contengono "€ 1.234,56", "N/D", valori che l'engine sa normalizzare ma che CAST rifiuterebbe a MERGE step.
  3. Engine robusto: operatori numeric:, int:, date: con fallback espliciti. Boundary test verdi.
  4. Zero migration runtime: add/remove field è DDL O(1).

Trade-off

  • Niente SQL aggregate diretti su prezzo/stock — uso engine per qualunque operazione tipizzata.
  • Niente constraint DB su range numerici — validazione in engine.
  • Sort lessicografico in SQL diretta su colonne numeriche (cura: usa engine).

Triggers di revisione

Riapri questa decisione se:

  • analytics dashboard richiede SUM(price::numeric) come hot path,
  • più di 3 ticket cliente in un quarter sul tema "ordinamento numerico sbagliato",
  • profiling del rule engine mostra cast come bottleneck.

Strategia se serve typing

Modello opt-in (non implementato): DatabaseField.type enum (TEXT | NUMERIC | DATE), shadow column ${slug}_numeric popolata in MERGE, engine usa shadow se disponibile.

Backward compatible perché TEXT resta default e source originale non cambia.

File rilevanti

  • packages/shared/src/services/schema-service.tsaddColumn() (hardcoded TEXT)
  • packages/worker/src/engine/rule-engine.ts → operatori cast
  • packages/worker/src/__tests__/rules-{to-export-boundary,per-export-scope}.int.test.ts → contract test

In questa pagina