In tempi abbastanza recenti si è affermato il cosiddetto movimento NoSQL, che raggruppa una gran varietà di tecnologie alternative ai tradizionali database relazionali: tra di essi i graph db, document db e key-value db sono probabilmente i più utilizzati.
Questa sigla inizialmente fu coniata per identificare una classe emergente di database la cui caratteristica comune era di non utilizzare SQL come linguaggio di accesso ai dati. Molti di essi in realtà supportano anche SQL, pur prediligendo altri linguaggi, per questo motivo il significato più corretto dovrebbe essere, come molti illustri esponenti suggeriscono, Not Only SQL.
In questo articolo e nei successivi di questa serie mostreremo alcune caratteristiche dei database NoSql al momento più utilizzati, e per il primo esempio utilizzeremo OrientDb: un prodotto italiano, scritto in java, e che racchiude in sè diverse caratteristiche del mondo NoSQL.
Superare i limiti del modello relazionale
Il modello relazionale ha ormai più di quarant'anni di vita e, sebbene sia ancora il più usato, comincia a mostrare dei limiti. I problemi principali riguardano la gestione di grandi moli di dati e la natura sempre più dinamica dei domini. In particolare la grande quantità e varietà di dati disponibili e scambiati attualmente sui social network e più in generale tra le applicazioni web sta sempre più mettendo in evidenza la natura flessibile delle rappresentazioni di dominio per il "web of data": sempre meno "normalizzabili" all'intero delle entità del modello relazionale classico.
connettere grandi quantità di dati
Per quanto riguarda il problema indotto dalla gestione di enormi quantità dei dati, ricordiamo che uno dei meccanismi di base per la strutturazione dei dati relazionale è l'operazione di JOIN. Quando abbiamo bisogno di recuperare tutta la struttura di un'entità il database utilizza le operazioni di JOIN per leggere i dati dalle tabelle (accessorie) necessarie; purtroppo però è raro che queste operazioni abbiano un costo operazionale costante (ovvero prevedibile e comunque contenuto), tendono infatti ad aumentare linearmente o esponenzialmente con la dimensione dei dati, fino a diventare nei casi peggiori inutilizzabili.
utilizzare rappresentazioni di dominio meno rigide
Riguardo invece al problema delle rappresentazioni di dominio in continua evoluzione, un esempio illuminante è sicuramente quello della gestione delle anagrafiche, un compito per cui i database stessi sono nati e che rappresenta un vero e proprio banco di prova per i sistemi gestionali. Le classiche tabelle anagrafica (o user, ecc.) hanno quasi sempre lo stesso elenco di campi, chi più chi meno:
- obbligatori: nome, cognome, indirizzo, telefono fisso;
- opzionali: occupazione, cellulare.
Tuttavia, con il passare del tempo e a seconda delle necessità vengono aggiunti sempre nuovi campi come: email, SkypeID, account Facebook, eccetera. Ad un certo punto hanno cominciato a manifestarsi alcuni problemi piuttosto interessanti e, in un certo senso, inaspettati. Ad esempio molti individui (compreso l'autore di questo articolo!) hanno smesso di avere un numero di telefono fisso. Con la capillare diffusione dei telefoni cellulari, Skype e altri mezzi di comunicazione infatti la tradizionale linea telefonica, dispendiosa e inutilizzata, è stata accantonata. Non avete idea di quante battaglie per tentare di spiegare al malcapitato operatore di turno del call center che purtroppo no, non ho il numero del "fisso"... un incubo per il povero impiegato, bombardato dagli implacabili messaggi d'errore del programma gestionale!
Documenti, grafi, big table...
Serve a questo punto un nuovo tipo di database in grado di crescere organicamente, ovvero seguendo il naturale evolversi dei dati, adattandosi insomma alle evoluzioni di volta in volta necessarie ad una utile rappresentazione del dominio. Ed occorre certamente potercene mettere tanti, di dati: incompleti, non perfettamente strutturati, e senza preoccuparsi di integrità referenziale, migrazioni, costo delle join. Il tutto possibilmente offrendo la maggior parte delle garanzie di integrità ACID (Atomicità, Coerenza, Isolamento e Durabilità).
categorie NoSQL
Ad oggi le tipologie di database NoSql si stanno stabilizzando in alcune macro-categorie:
- tabulari: BigTable, Hadoop;
- chiave-valore: MongoDB, Redis, BigTable;
- document-oriented: OrientDB, CouchDB, MongoDB;
- graph-oriented: OrientDB, Neo4j;
- object database: db4o, ObjectDB, OrientDB.
Come avrete notato alcuni nomi compaiono in più categorie, perché sono difficili da classificare (BigTable) oppure perché implementano diverse modalità di storage e accesso (OrientDB).
Ogni tipologia nasce da esigenze molto specifiche:
- i key-value store nascono per gestire con la massima efficienza semplici coppie chiave-valore, ad esempio per essere utilizzati come cache;
- i document store sono usati per archiviare grandi quantità di dati poco strutturati ed eterogenei;
- i grafi sono perfetti per modellare le relazioni fra entità diverse o individui, ad esempio nei social network.
Uno sguardo a OrientDB
OrientDB è un progetto particolarmente interessante, che riunisce in un unico database moltissime funzionalità:
- multiple modalità di storage: document, graph (con supporto allo stack Tinkerpop), object, key/value;
- funzionamento embedded, in memory, client/server;
- fornisce le proprietà ACID di consistenza delle transazioni;
- supporta nativamente JSON e REST;
- è scritto in Java e può quindi girare su moltissime piattaforme.
Grazie a questa sua poliedricità ci permetterà di cominciare la nostra esplorazione nel vasto e affascinante mondo NoSQL, fra documenti, grafi, relazioni e tanti, tantissimi dati!
Nei prossimi esempi inizieremo pertanto ad utilizzare OrientDb in modalità DocumentDb, GraphDb ed ObjectDb, per ora in modo molto semplice ed introduttivo. In seguito verranno proposti invece esempi più orientati ad applicazioni reali, che ci consentiranno qualche approfondimento in più.
La modalità Document è una delle più importanti di OrientDB, su di essa infatti si basano sia la modalità Graph database che Object database.
La definizione di documento è molto generica, in sostanza è un'entità che raggruppa diversi campi, di qualsiasi tipo: stringhe, date, numeri, dati binari, riferimenti ad altri documenti. A pensarci bene, in questa definizione potrebbero ricadere anche le tradizionali tabelle di un database relazionale! C'è una grossa differenza però: un documento non ha necessariamente una struttura fissa ma può contenere un numero variabile di campi. Questa estrema libertà permette di utilizzare questa struttura dati in molti modi, ed è proprio ciò che succede dietro le quinte di OrientDB:
- nella modalità Object database ogni oggetto viene salvato in un documento, e le sue proprietà in altrettanti campi;
- della modalità Graph database i nodi sono documenti che memorizzano nei propri campi i collegamenti verso gli altri nodi.
Il problema principale che si potrebbe presentare con tutta questa libertà è il proliferare dei campi senza una logica precisa, con mille variazioni ed eccezioni. Un vero incubo per chi deve poi trattare i dati! Per rafforzare il modello dei dati e superare questo problema, OrientDB prende a prestito dall' Object Oriented Programming (OOP) il concetto di Classe applicandolo ai documenti; questa funzionalità, chiamata Schema, è usata principalmente come supporto all'Object Database, ma si rivela molto utile anche per gli altri paradigmi.
I primi esempi che vedremo ora utilizzeranno la versione "estesa" di SQL supportata da OrientDB, rendendo molto più morbida la transizione verso il paradigma NoSQL per chi viene dal mondo relazionale. Per eseguire i comandi è sufficiente un'installazione standard di OrientDB:
- requisiti: Java 6 o superiore;
- scaricare il pacchetto completo (mentre scriviamo questo articolo siamo alla versione 1.0rc9);
- estrarre l'archivio;
- eseguire lo script server.bat (Windows) oppure server.sh (Linux/Unix);
- aprire il browser all'indirizzo http://localhost:2480/ per accedere all'OrientDB Studio, un'applicazione utile per amministrare il database, eseguire query e molto altro.
Creeremo ora un piccolo database utilizzabile per un blog o un sito. Per farlo, cliccare su Create new database inserendo questi dati:
- Database name: blog;
- Type: document;
- Storage type: per ora utilizzeremo memory, in questo modo i dati non verranno salvati su disco.
Per creare un database in questo modo è necessario accedere come utente "root" la cui password predefinita è memorizzata nel file
config/orientdb-server-config.xml
Una volta creato il database, scollegarsi (tasto Disconnect) e ricollegarsi come admin / admin scegliendo il neo-creato database blog dal menu a tendina. Siamo pronti per eseguire di qualche query interessante!
Per prima cosa, creiamo le due principali classi di documenti: post e comment, per farlo è sufficiente cliccare su Query (in alto, a forma di tabella) e inserire questi comandi:
create class post create class comment
Nota bene: i comandi vanno eseguiti uno alla volta e devono stare su una sola riga.
Inseriamo qualche documento di tipo post:
insert into post(title, body, author) values( 'Il mio primo post', 'bla bla bla', 'Dimitri De Franciscis') insert into post(title, body) values('Il mio secondo post', 'bla bla bla') insert into post(title, body, author, create_date) values( 'Il mio terzo post', 'bla bla bla', 'Dimitri De Franciscis', date('27-04-2012', 'dd-MM-yyyy'))
Alcuni elementi importanti da notare:
- essendo lo schema libero, bisogna ogni volta specificare l'elenco dei campi;
- è possibile specificare sempre nuovi campi, o ometterne altri senza che venga mostrato alcun messaggio d'errore;
- le date possono essere inserite utilizzando le specifiche della classe java.text.SimpleDateFormat.
Procediamo allo stesso modo, inserendo dei commenti:
insert into comment(author, text) values('user1', 'commento 1') insert into comment(text) values('commento 2')
Ogni documento in OrientDB ha un identificatore @rid, dotato di un formato particolare (#tipo:istanza); ad esempio sulla nostra istanza ai documenti di tipo post sono stati assegnati questi @rid: #6:0, #6:1, #6:2, mentre i commenti hanno @rid #7:0, #7:1, eccetera. Per leggere questi dati è sufficiente eseguire delle familiari SELECT in SQL:
select from post select from comment
L'ultimo passo che manca è l'associazione dei commenti ai post, operazione possibile tramite l'estensione ADD, che permette di aggiungere elementi ad un campo di tipo lista. Queste istruzioni aggiungono i commenti #7:0 e #7:1 al post #6:0, inserendoli in un campo comments:
update post add comments = #7:0 where @rid = #6:0 update post add comments = #7:1 where @rid = #6:0
Alle liste si possono aggiungere anche campi di tipo valore, non solo riferimenti @rid. Ad esempio con queste istruzioni possiamo aggiungere dei tag ai post:
update post add tags = 'informatica' where @rid = #6:0 update post add tags = 'OrientDB' where @rid = #6:1 update post add tags = 'NoSQL' where @rid = #6:2
Dopo aver visto come creare un semplice database di prova (basandoci sull'interfaccia web di OrientDb), vedremo nella prossima parte quanto sia facile ed intuitivo l'utilizzo del DocumentDb in java , e come gli altri paradigmi (a grafo e ad oggetti) siano implementati sopra di esso, senza costituire quindi un elemento di difficoltà ulteriore.
OrientDb: Document database in Java
Dopo aver visto come creare un database di esempio sull'interfaccia web di OrientDb, ed aver iniziato ad introdurre alcuni concetti di base (l'uso di tipi di dati anche più complessi, come le liste, e l'utilità dei @rid), vediamo ora con degli esempi molto semplici come utilizzare in java il database Document di prova. Subito dopo introdurremo in maniera analoga anche gli esempi di utilizzo in modalità Graph ed Object
Utilizzare le API Java per lavorare con database di tipo Document è ancora più semplice di quanto visto finora, ecco il classico esempio di fattura:
ODocument invoice = new ODocument("Invoice");
invoice.field("anno", 2012);
invoice.field("numero", 1);
invoice.field("id_cliente", 23);
invoice.field("oggetto", "Fattura per assistenza software");
invoice.field("totale", 2400.0);
List<ODocument> rows = new ArrayList<ODocument>();
ODocument row;
row = new ODocument("InvoiceRow");
row.field("oggetto", "Installazione gestionale");
row.field("totale", 1500.0);
rows.add(row);
row = new ODocument("InvoiceRow");
row.field("oggetto", "Installazione CRM");
row.field("totale", 900.0);
rows.add(row);
// aggiungi ad Invoice
invoice.field("rows", rows);
// salva tutto
invoice.save();
Come potete notare per salvare le righe è stato sufficiente inserirle in una List!
Capirete quindi come mai il Document Database è la struttura di basso livello su cui vengono costruiti i Graph e Object Database: i nodi sono i documenti, i collegamenti sono i riferimenti @rid. Molto semplice!
La conferma che sia Graph che Object database si basano su documenti arriva curiosando nel codice di OrientDB (il bello dell'open source...). Scopriamo infatti che:
- le proprietà di un oggetto sono salvati nei campi di un documento;
- l'utilizzo di Schema permette di definire con più precisione le classi e le loro proprietà;
- è supportata l'ereditarietà delle classi (e quindi dei documenti): le classi figlie hanno semplicemente dei campi in più rispetto alle classi padre;
- vertici e archi in un grafo non sono altro che specializzazioni della classe ODocumentWrapper, che come il nome suggerisce è un oggetto che incapsula un documento.
In sostanza OrientDB riesce a essere così flessibile e offire tutte queste modalità operative grazie ad una progettazione Object-Oriented molto ben fatta, ma lasciando la libertà di scegliere a quale livello di complessità operare: direttamente sui documenti, su strutture a grafo, con oggetti Java POJO (acronimo di Plain Old Java Object).
Graph database
Vediamo quindi un esempio di utilizzo delle API Java per i grafi, implementando una specie di "rete di amicizie":
// crea il nodo radice
ODocument root = db.createVertex().field(OGraphDatabase.LABEL, "root");
db.setRoot("root", root);
// crea alcuni nodi
ODocument tizio = db.createVertex().field(OGraphDatabase.LABEL, "Tizio");
ODocument caio = db.createVertex().field(OGraphDatabase.LABEL, "Caio");
ODocument pincoPallino = db.createVertex().field(OGraphDatabase.LABEL,
"Pinco Pallino");
// collega root a questi nodi
db.createEdge(root, tizio);
db.createEdge(root, caio);
db.createEdge(root, pincoPallino);
// crea collegamenti fra i nodi:
// l'attributo "friend" [0, 1] indica il "grado di amicizia"
db.createEdge(tizio, caio).field("friend", 1.0);
db.createEdge(tizio, pincoPallino).field("friend", 0.5);
db.createEdge(caio, tizio).field("friend", -0.5);
db.createEdge(caio, pincoPallino).field("friend", -1.0);
db.createEdge(pincoPallino, tizio).field("friend", 0.5);
db.createEdge(pincoPallino, caio).field("friend", 1.0);
// salva
root.save();
tizio.save();
caio.save();
pincoPallino.save();
Queste API sono denominate Raw Graph Database API per differenziarle dalle API compatibili con lo stack Tinkerpop. TinkerPop in particolare è un set di interfacce e specifiche per lavorare con grafi, che OrientDB supporta dalla versione 0.9.22, e che sta conoscendo una rapida adozione presso moltissime implementazioni di Graph Database, come ad esempio anche Neo4j, e verrà trattato più avanti in ulteriori articoli di questa serie.
Object database
L'ultimo esempio che vedremo farà sicuramente la gioia degli sviluppatori Java che hanno avuto a che fare con framework di persistenza come JPA, Hibernate etc. Riscriveremo infatti l'esempio visto con i Document, ma rivisto in chiave pure-Java, ovvero utilizzando dei banali oggetti POJO. Ecco come siamo riusciti a riscrivere con pochissime righe di codice l'esempio delle fatture (abbiamo omesso il codice delle classi Invoice e InvoiceRow):
ODatabaseObjectTx db =
new ODatabaseObjectTx("remote:localhost/test_object");
db.open("admin", "admin");
// ispeziona le classi per creare lo schema sul database
db.getEntityManager()
.registerEntityClasses("it.html.orientdb.samples.model");
Invoice invoice =
new Invoice(2012, 1, 23, "Fattura per assistenza software");
invoice.addRow("Installazione gestionale", 1500.0);
invoice.addRow("Installazione CRM", 900.0);
db.save(invoice);
Controllando il database potremo verificare facilmente che OrientDB ha eseguito questi passi:
- ha creato le classi Invoice e InvoiceRow;
- ha inserito le property in campi omonimi;
- ha inserito i riferimenti fra oggetti (nel nostro caso la lista di InvoiceRow) come riferimenti fra documenti, come visto sopra.
Sviluppi
Grazie a OrientDB siamo riusciti ad avere un breve assaggio di ciò di cui sono capaci i database NoSQL: sfruttando i diversi paradigmi supportati, ma abbiamo ovviamente soltanto scalfito la superficie del mondo NoSQL. Prossimamente introdurremo altri database NoSQL, e vi mostreremo come utilizzare questi database nelle vostre applicazioni.
Links
- OrientDB;
- NoSQL (Wikipedia);
- ACID - Atomicità, Coerenza, Isolamento e Durabilità (Wikipedia);
- Tinkerpop.
- java.text.SimpleDateFormat;
- Neo4j.