In questa lezione, progetteremo un database cercando di sfruttare i principi discussi nella lezione precedente. In particolare, ci riferiremo ad un sistema informativo basato su collegamenti tra informazioni, la classica problematica che - solitamente - tenderebbe a portare verso altri tipi di database NoSQL. Per l'implementazione pratica, useremo il DBMS Redis, predisponendone un'installazione seguendo una delle varie alternative possibili. Noi lo useremo su una macchina Linux con sistema Ubuntu 14.04, e per l'invio dei comandi useremo il tool classico da riga di comando redis-cli ed il prompt che vedremo negli esempi di default mostrerà indirizzo IP e porta su cui contatteremo il server.
Quello che vogliamo realizzare è un semplice sistema di messaggistica, in cui il database funzionerà da sistema di scambio delle comunicazioni, e di gestione degli utenti. Questi i requisiti:
- per accedere al sistema, ogni utente deve effettuare login: ci accontenteremo che ognuno possieda una coppia di credenziali username e password. Per semplicità, quest'ultima verrà custodita in chiaro, sebben in casi reali conviene salvarla codificata con un meccanismo di hash come MD5 o SHA1;
- ogni utente disporrà di una propria rubrica di contatti dove potrà custodire un elenco dei suoi utenti preferiti;
- per ogni utente verrà memorizzato l'elenco dei messaggi ricevuti. Anche in questo caso, per semplicità non memorizzeremo l'elenco dei messaggi inviati da ognuno;
- ogni messaggio sarà contraddistinto da un proprio ID composto da una sequenza di numeri, e sarà costituito da un testo (il suo contenuto), la data e l'ora di inoltro e lo username del mittente.
Ricapitolando, l'esito finale sarà un database costituito da un'unica hashmap in cui verranno inserite tutte queste informazioni. Un'applicazione che vorrà sfruttarlo potrà interrogarlo mediante i vari driver messi a disposizione.
Per ogni settore di informazioni, definiremo cosa usare come chiave, quale prefisso applicarvi e di che tipo sarà il valore che useremo, se consisterà in una semplice stringa o avrà una struttura più articolata (lista, mappa, eccetera). Soprattutto - sempre in merito ai valori - dovremo decidere se il valore rappresenterà un'informazione o, a sua volta, la chiave di un altro valore per stabilire una relazione.
Utenti e contatti
Per iniziare definiamo gli utenti. Abbiamo detto che per ogni utente ci accontenteremo di controllare username e password. Useremo quindi il nome utente come chiave e la password come valore, adottando "users" come prefisso per le chiavi. All'inizio di una sessione di lavoro, l'utente dovrà loggarsi, e pertanto il software che scriveremo potrà controllare se la password inserita corrisponde all'utente che si è identificato. In redis-cli, creeremo degli utenti (pippo, topolino, paperino, minnie) in possesso ognuno della propria password (rispettivamente: fragola, banana, pesca, prugna):
127.0.0.1:6379> set users:pippo fragola
OK
127.0.0.1:6379> set users:topolino banana
OK
127.0.0.1:6379> set users:paperino pesca
OK
127.0.0.1:6379> set users:minnie prugna
OK
Per comodità, ricordiamo che se volessimo vedere quali chiavi sono presenti nel database, potremmo usare il comando KEYS, che richiede un parametro definibile come pattern. Poco fa, invece, abbiamo visto l'inserimento più comune in Redis: l'associazione tra una chiave ed un valore, entrambi stringa con SET. Per il recupero del valore, in maniera analoga potremmo usare GET.
Ad esempio, potremo ottenere la lista di tutti gli utenti inseriti sfruttando il prefisso che abbiamo applicato loro:
127.0.0.1:6379> keys users*
1) "users:paperino"
2) "users:topolino"
3) "users:pippo"
4) "users:minnie"
Per i contatti, adotteremo - anche in questo caso - lo username come chiave, mentre il valore sarà un elenco di altri ID utente, strutturato quindi come lista. Inseriamo, tanto per provare, i contatti dell'utente topolino che saranno, supponiamo, pippo e minnie:
127.0.0.1:6379> rpush contacts:topolino minnie
(integer) 1
127.0.0.1:6379> rpush contacts:topolino pippo
(integer) 2
Anche in questo caso abbiamo scelto un prefisso che indentifica i contatti, contacts. Ciò appare ancora più utile se si pensa che, in sè stessa, la stringa "topolino" sarebbe chiave sia dei contatti che della lista di utenti, e ciò è possibile solo grazie all'uso del prefisso. L'inserimento con RPUSH accoda il valore passato alla lista corrispondente alla chiave fornita.
Gestione dei messaggi
Per quanto riguarda i messaggi, articoleremo la struttura in due passi. Consideriamo che:
- ad ogni utente corrisponde una lista di messaggi;
- ogni messaggio viene confezionato come una hashmap contenente testo, data/ora e mittente, ed è inoltre contraddistinto da un ID.
Scegliamo quindi di far corrispondere ad ogni utente una lista di ID-messaggio (nello specifico: 100, 103, 107):
127.0.0.1:6379> rpush messages:topolino 100
(integer) 1
127.0.0.1:6379> rpush messages:topolino 103
(integer) 2
127.0.0.1:6379> rpush messages:topolino 107
(integer) 3
Inoltre, associamo una hashmap con i dettagli del messaggio all'ID dello stesso (usato come chiave)
127.0.0.1:6379> hmset data:100 testo "Che fai?" data "2016-10-08 11:34" mittente pippo
OK
127.0.0.1:6379> hmset data:103 testo "Domani ci vediamo?" data "2016-10-08 14:05" mittente minnie
OK
127.0.0.1:6379> hmset data:107 testo "Sono tornato. Passa pure a casa mia" data "2016-10-08 19:21" mittente pippo
OK
In questo caso abbiamo usato il prefisso "data", sebbene avremmo potuto usare lo stesso prefisso messages visto che, a seconda del tipo di informazione che dobbiamo immagazzinare, vi associeremo uno username o un id-messaggio. I messaggi associati all'utente verranno disposti in un elenco con RPUSH
, comando già visto prima, mentre la sintassi per HMSET per l'inserimento in hashmap può apparire poco leggibile. In pratica, dopo il comando HMSET
verrà passata la chiave e una sequenza di chiavi e valori alternati, dove ogni chiave servirà nella hashmap a recuperare il valore espresso subito dopo.
Note conclusive
Abbiamo costruito una rete di collegamenti tra informazioni. Un software potrà svolgere una o più interrogazioni in base alle chiavi ed usare i valori ottenuti per effettuare nuove ricerche.
Ricordiamo che Redis e gli altri database key/value sono ideali per compiti di cache e dizionario, ma possono offrire alte prestazioni anche in altri ambiti purchè si sfruttino bene i loro principali punti di forza: chiavi e strutture dati da utilizzare come valori.