In gran parte dei gestionali che scrivo la stessa descrizione di una tabella ricompare in almeno quattro punti: lo script CREATE TABLE, le query di lettura, il form HTML di inserimento e il codice CGI (Common Gateway Interface) che valida e salva i dati. Tenere allineate queste copie a mano è l’errore che vedo tornare più spesso. Una colonna aggiunta allo schema ma dimenticata nel form, un vincolo NOT NULL presente nel database ma non controllato dall’applicazione: difetti banali, frequenti, che vengono fuori solo quando il programma gira.

Da qualche tempo procedo in un altro modo: descrivo ogni tabella una volta sola, in un file di metadati, e gli altri artefatti li genero da lì. Qui racconto come è fatto l’impianto, dove è debole e dove la generazione si ferma.

Contesto

Generare codice a partire da una descrizione dei dati non è un’idea nuova. Gli strumenti CASE (Computer-Aided Software Engineering) e i 4GL sfornano schermate e procedure da un dizionario dati da anni. È cambiata la disponibilità di pezzi liberi e documentati con cui ci si costruisce un generatore su misura, senza dover adottare un ambiente proprietario chiuso.

XML 1.0 è Raccomandazione W3C dal 10 febbraio 1998 e dà una sintassi neutra per descrivere strutture ad albero, con la DTD (Document Type Definition) come grammatica di validazione interna alla specifica stessa. XSLT 1.0 è Raccomandazione dal 16 novembre 1999 ed è un linguaggio che trasforma documenti XML in altro XML, in HTML o in testo. Le due cose insieme bastano a costruire un generatore di codice senza scrivere un parser: la descrizione delle tabelle sta in un documento XML validato da DTD, e ogni artefatto da produrre è il risultato di un foglio di stile XSLT applicato a quel documento.

Architettura

Il file sorgente descrive le tabelle in modo dichiarativo. Un frammento realistico:

<schema>
  <table name="cliente">
    <column name="id"       type="integer" pk="yes"/>
    <column name="ragione"  type="varchar" size="120" null="no"/>
    <column name="piva"     type="char"    size="11"  null="no"/>
    <column name="agente"   type="integer" fk="agente.id"/>
  </table>
</schema>

Da questo unico documento ricavo tre famiglie di output, ognuna con il suo foglio XSLT:

  1. DDL SQL (Data Definition Language). Un foglio percorre gli elementi table e column ed emette CREATE TABLE, tipi e vincoli PRIMARY KEY, NOT NULL, FOREIGN KEY. La corrispondenza tra i tipi astratti (integer, varchar) e i tipi del database concreto è una tabella di mappatura dentro al foglio: così lo stesso schema produce DDL per dialetti diversi cambiando solo il foglio.

  2. Form e liste HTML. Un secondo foglio genera, per ogni tabella, il form di inserimento e modifica e la lista paginabile. Il tipo e la dimensione della colonna scelgono il controllo HTML: un varchar corto diventa un input, un testo lungo una textarea, una fk un menù a tendina riempito dalla tabella riferita.

  3. Codice di accesso CGI. Un terzo foglio emette le funzioni di SELECT, INSERT e UPDATE con i parametri già tipizzati, più il blocco di validazione lato server ricavato dai vincoli (NOT NULL, lunghezza massima, presenza della chiave esterna).

È una separazione tra livelli di astrazione: lo schema dice il cosa, ogni foglio XSLT dice il come per un bersaglio. Per cambiare la convenzione di tutte le liste — mettere ovunque l’ordinamento per colonna, per dire — tocco un foglio, non N pagine già generate.

Punto critico

La generazione integrale tiene finché il problema è regolare. La parte CRUD (Create, Read, Update, Delete) di un gestionale lo è: mostra righe, le inserisce, le modifica. Le regole di business quasi mai. Un calcolo di scadenza che dipende dal tipo di cliente, una validazione che incrocia tre tabelle, un flusso di approvazione: niente di tutto questo si ricava dallo schema dei dati, perché lo schema non lo contiene.

Da qui la decisione di progetto che pesa di più: tenere separato il codice generato dal codice scritto a mano. Se li mescolo nello stesso file, alla prima rigenerazione perdo le modifiche manuali e in pratica smetto di rigenerare — il generatore si riduce a uno scaffolding usa-e-getta. Le due strade che ho visto reggere sono l’ereditarietà, dove il codice scritto estende una classe base generata, e l’inclusione, dove il file generato fa include di un file manuale per i punti di estensione. In tutti e due i casi la regola è che il file generato non si tocca a mano: si modifica lo schema o il foglio, e si rigenera.

Un secondo punto critico è la migrazione. Generare il CREATE TABLE del primo giorno è facile; produrre l’ALTER TABLE che porta uno schema dalla versione vecchia a quella nuova vuol dire confrontare due versioni del documento, non trasformarne una. È un calcolo di differenza, fuori dalla portata di un singolo foglio XSLT applicato a un solo input, e nei miei progetti resta lavoro a mano da rivedere a ogni rilascio.

Implicazioni

Quello che ci si guadagna non è tanto la velocità quanto la coerenza. Se form, query e DDL vengono dallo stesso documento, una colonna aggiunta allo schema compare in tutti e tre dopo una rigenerazione, e la classe di difetti “c’è nel database, manca nel form” sparisce per costruzione. È la stessa coerenza che la normalizzazione dà a un modello dei dati, portata però agli artefatti che da quel modello discendono.

Cambia anche dove vive la descrizione del dominio. Lo schema in XML si legge, si versiona con un normale sistema di controllo di versione, si diffa riga per riga ed è indipendente dal database concreto. Smette di stare nascosta dentro il codice e diventa un documento a sé, che si cita e si revisiona.

Limiti

La generazione da schema risolve il livello strutturale e si ferma lì. Nello schema dichiarativo non saprei scrivere una regola di business non banale, e non credo serva provarci: oltre una certa soglia, un linguaggio di regole potente abbastanza da coprire i casi reali si legge peggio del codice che vorrebbe sostituire. La generazione rende dove a poca specificità corrisponde molto codice — i layer CRUD e di accesso — e smette di rendere dove quel rapporto si rovescia.

Resta il fatto che il codice generato vale quanto i fogli XSLT. Un foglio scritto male produce codice scritto male a tappeto, su tutte le tabelle, in silenzio. Il foglio di trasformazione diventa così l’artefatto più critico del progetto, e va trattato con lo stesso riguardo del codice che genera: revisionato, versionato e provato su uno schema di riferimento prima di applicarlo a quelli veri.


https://www.w3.org/TR/1998/REC-xml-19980210 https://www.w3.org/TR/xslt-10/ https://lists.w3.org/Archives/Public/w3c-news/1999OctDec/0003.html

Immagine di copertina: Diagramma di flusso: un documento XML e un foglio XSLT entrano in un processore XSLT che produce un documento di output — diagramma di Dreftymac, CC BY-SA 3.0 — https://commons.wikimedia.org/wiki/File:XSLT_en.svg