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:
-
DDL SQL (Data Definition Language). Un foglio percorre gli elementi
tableecolumned emetteCREATE TABLE, tipi e vincoliPRIMARY 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. -
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
varcharcorto diventa uninput, un testo lungo unatextarea, unafkun menù a tendina riempito dalla tabella riferita. -
Codice di accesso CGI. Un terzo foglio emette le funzioni di
SELECT,INSERTeUPDATEcon 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