Nonostante la persistenza dei dati rappresenti quasi sempre un elemento fondamentale delle applicazioni, NodeJS non presenta nessuna integrazione nativa con i database. Tutto viene delegato a librerie di terze parti da includere manualmente, oltre alle API standard.
In questo articolo analizzeremo una delle principali librerie per interfacciarsi con un database MySQL e approfondiremo il concetto di ORM.
Node-db
Node-db è un progetto di che permette di utilizzare un framework per gestire la persistenza dei dati, delegando ad esso la gestione diretta delle query al database. Il concetto che sta alla base è quello del disaccoppiamento. Il codice esposto dalla libreria non dipende dal database scelto, permettendone anche un cambiamento rapido senza necessità di riscrivere il codice.
Attualmente il progetto supporta solamente due engine: MySQL e Drizzle. Ciascun engine presenta un suo modulo hostato sul repository npm: db-myqsl
e db-drizzle
.
Possiamo scaricare il progetto direttamente dalla home page oppure dal relativo repository github.
La libreria è sviluppata per nodeJS, quindi eredita la logica asincrona di Javascript: qualsiasi query eseguita sul database sarà asincrona e sarà gestita con delle callback ad hoc.
Altra caratteristica della libreria sono i metodi chiamati chain che permettono di costruire query anche complesse direttamente da codice, senza dover scrivere la query manualmente, ma sfruttando le API del framework.
Guardiamo qualche esempio:
var mysql = require('db-mysql');
new mysql.Database({
hostname: 'localhost',
user: 'utentedb',
password: 'passwdb',
database: 'testdb'
}).connect(function(error) {
if(error) return console.log("Connection error");
this.query().select("*").from("libri")
.where("letto = ?", [true])
.order({ titolo: true })
.execute(function(error, rows, cols) {
if(error) return console.log("Query error");
for(var i in rows) console.log(rows[i]);
});
});
In questo frammento di codice possiamo analizzare le caratteristiche descritte prima. Innanzitutto il meccanismo asincrono che prevede di avere una callback legata alla connessione al database (metodo connect
) e una callback legata all'esecuzione di una query (metodo query
). Inoltre sono facilmente identificabili i metodi chain come ad esempio select
, from
, where
e order
che se chiamati nel giusto ordine permettono di creare una vera e propria query completamente da codice in modo da essere il più sganciati possibile dall'engine scelto (engine diversi potrebbero avere linguaggi SQL leggermente diversi tra di loro).
Da notare infine il meccanismo di auto-escaping utilizzato nel metodo where dove la variabile booleana true non viene impostata manualmente all'interno della condizione ma viene utilizzato un replace interno alla libreria che si occupa di sostituire i punti di domanda con i valori presenti nell'array passato come secondo parametro. Quest'approccio verrà molto utilizzato nelle query di insert e update che vedremo tra poco.
Al metodo query
è possibile anche passare una query scritta manualmente grazie ai metodi di utilità name
ed escape
:
this.query('SELECT * FROM ' + this.name('book') + ' WHERE ' + this.name('letto') + ' = ' +
this.escape(true)).execute(function(error, result) { … } );
Questa soluzione è da preferire rispetto a quello precedente nei casi in cui le performance dell'applicazione richiedono tempi di elaborazione minori oppure in caso di query personalizzate (per esempio JOIN o GROUP BY complessi o quando è presenta la clausola LIKE).
Le query di inserimento, aggiornamento e rimozione seguono sempre questo approccio. Se vogliamo eseguire una INSERT
possiamo scrivere:
this.query().insert('book', ['titolo', 'letto'], ['I pilastri della Terra', true], ['1984',false])
.execute(function(error, rows, cols) { … } );
Per una UPDATE
invece:
this.query().update('book').set({ letto: true })
.where("id = ?", [10])
.execute(function(error, result)
{ … } );
Infine la DELETE
:
this.query().delete().from('book').where("id = ?", [10])
.execute(function(error, result) { … } );
Gli ORM e NodeJS
ORM sta per Object Relational Mapping, ovvero uno strato che serve a mappare, ovvero a rappresentare le entità relazionali come oggetti e vice versa a persistere gli oggetti con strumenti relazionali.
In altre parole gli ORM offrono la possibilità di pensare ai dati semplicemente come ad un insieme di oggetti, sarà l'ORM poi a creare tutte le tabelle necessarie a rappresentare quegli oggetti sul database. Questa astrazione è tale che nella maggior parte dei casi possiamo addirittura fare a meno di sapere quale tipo di database utilizziamo. Questo almeno in teoria...
Node-db non è un ORM, ma già ci aiuta a compiere una prima astrazione dal DBMS. Al momento tra le librerie presenti nel principale repository NPM non sono presenti ORM particolarmente affidabili o interessanti da dedicargli una parte dell'articolo. Questa può essere vista come una mancanza del ancora giovane nodeJS ma anche come un'opportunità per far nascere nuove idee o possibilità.