Nessun risultato. Prova con un altro termine.
Guide
Notizie
Software
Tutorial

Un esempio completo

Un esempio pratico che mostra un tipico caso d'uso in cui MongoDB è utilizzato insieme a Node.js per gestire i dati di un'app di messagistica.
Un esempio pratico che mostra un tipico caso d'uso in cui MongoDB è utilizzato insieme a Node.js per gestire i dati di un'app di messagistica.
Link copiato negli appunti

In questo capitolo vedremo un esempio pratico di utilizzo di MongoDB. Premettiamo che la documentazione ufficiale fornisce, in inglese, una lista di casi d'uso tipici di MongoDB, riportando per ognuno alcuni esempi di implementazione in Python.

Come abbiamo già accennato, MongoDB si rivela molto utile, rispetto ad altre soluzioni di storage, quando si ha la necessità di memorizzare una grande mole di dati su cui si faranno molte scritture atomiche in parallelo, e a maggior ragione quando questi dati non hanno una struttura omogenea definita a priori. Ma, come abbiamo visto nell’undicesima lezione, può essere utilizzato anche in situazioni in cui dati senza uno schema fisso convivono con dati multimediali, magari di grandi dimensioni. Se a ciò aggiungiamo il supporto alla geolocalizzazione, e il fatto che è molto più semplice scalare orizzontalmente (lezione 15), MongoDB è adatto anche per raccogliere dati provenienti da applicazioni sviluppate per dispositivi mobili.

Prendiamo ad esempio un’app pensata per scambiare messaggi e contenuti multimediali fra utenti. Inizialmente potrebbe avere un numero molto limitato di
utenti e quindi un bisogno minimo di storage, ma potrebbe avere anche presto un gran numero di accessi – quindi molte scritture – in concorrenza. Lo
sharding potrebbe risolvere il problema della scalabilità dell’applicazione, mentre l’utilizzo di GridFS faciliterebbe la fruizione dei contenuti multimediali. Inoltre, la caratteristica di essere un database documentale schema-less sarebbe utilissima per fronteggiare la dinamicità tipica delle applicazioni per dispositivi mobili, ovvero aggiornamenti frequenti e il problema che non tutti gli utenti potrebbero utilizzare contemporaneamente la stessa versione dell'app.

Inizialmente, per lo sviluppo, non ci serve un’architettura con repliche, né tanto meno con sharding. Abiliteremo queste funzioni avanzate quando saremo pronti per il deploy.

Per l’ambiente di sviluppo è superfluo anche impostare profili di autenticazioni e di permessi, possiamo utilizzare la Localhost Exception, oppure, come abbiamo visto nella tredicesima lezione, impostare una password di amministratore e utilizzare quella.

Quindi la nostra app avrà un back-end esposto su internet, sotto forma di web service per l’utilizzo della piattaforma attraverso i dispositivi mobili. Il back-end, in prima istanza, permetterà all'utente di autenticarsi e, in caso di successo, gli consentirà di:

  • leggere i messaggi in arrivo;
  • leggere i contenuti multimediali connessi ai messaggi;
  • inviare nuovi messaggi;
  • inviare contenuti multimediali.

Svilupperemo il back-end in JavaScript su Node.js, utilizzando il driver ufficiale di MongoDB.

Schema dei dati e indici

Per quanto riguarda lo schema dei dati, possiamo utilizzare un database che chiameremo htmlitMessenger, contenente due collezioni:

  1. users;
  2. messages.

I contenuti multimediali possono essere salvati in GridFS, come vedremo in questo esempio. Una strategia alternativa è di incapsularli direttamente in un documento sotto forma di campo BinData, considerando che le dimensioni delle immagini e dei video generati da un dispositivo mobile sono generalmente inferiori a 16 MB. Dipende ovviamente dal contesto dell’applicazione e dal tipo di utilizzo che pensiamo per l’utente. Si potrebbe anche scegliere un approccio misto: lasciare nel messaggio i contenuti più piccoli e utilizzare GridFS per video molto grandi.

Andiamo quindi a creare le collezioni e, soprattutto, gli indici che useremo. Dopo aver messo in esecuzione il server mongod e lanciato la shell mongo, come abbiamo visto nella seconda e terza lezione, possiamo lanciare i comandi:

> use htmlitmessenger
> db.users.ensureIndex({ email: 1 }, {unique: true})
> db.users.ensureIndex({ name: "text" })
> db.messages.ensureIndex({ userDest: 1})

Con questi comandi abbiamo creato il database contestualmente alla definizione di due indici sulla collezione degli utenti: il primo sul campo email, dove abbiamo specificato che le e-mail sono valori unici (non accettiamo, cioè, due utenti con la stessa e-mail); il secondo sul campo name, di tipo testuale, ci sarà utile per cercare gli utenti. Nella collezione dei messaggi abbiamo creato un indice sul campo userDest che verrà usato per memorizzare l’utente destinatario dei messaggi. Ovviamente potremo creare altri indici anche dopo aver messo in esercizio la nostra applicazione, nel caso osservassimo un degrado delle performance durante l’esecuzione di alcune query.

Realizzazione dei servizi

Supponiamo che l’utente inizialmente debba registrarsi sulla nostra piattaforma. L’applicazione Android o iOS mostrerà un form di inserimento per le credenziali dell’utente e poi invocherà un web service, ad esempio /register, inserendo come payload i dati inseriti dall’utente in formato JSON, magari con la password criptata. Ad esempio:

{ "email":"onofrio.panzarino@gmail.com", "password": "27c749230e8f93b76fa0a4b9dc3cc450" }

Per prima cosa creiamo un progetto Node.js, quindi installiamo MongoDB come abbiamo visto nella quarta lezione, utilizzando npm:

npm install mongodb

Il nostro codice comincerà con le seguenti righe, per inizializzare l’utilizzo del client MongoDB nel programma:

var MongoClient = require('mongodb').MongoClient
var dbUrl = 'mongodb://localhost:27017/htmlitmessenger';

Ci sono vari modi di realizzare web service JSON con Node.js. Qui utilizziamo il più naïve, ossia analizziamo testualmente la URL della chiamata ed
eseguiamo un “handler“. Il codice per eseguire tutto il programma è allegato a questa lezione e disponibile su GitHub. Potremo effetuare le chiamate di prova al servizio attraverso un semplice programma per testare i servizi REST, ad esempio Advanced Rest Client, dopo aver avviato il programma con il comando:

$> node index.js

Ignorando tutto il codice infrastrutturale, la funzione per gestire la registrazione, al netto di controlli di coerenza (ad esempio la validità della e-mail fornita) potrebbe essere simile a questa:

var register = function(request, response) {
	var checkData = function(postedData, onError) {
		if(! postedData.email) return onError("e-mail is required");
		return true;
	};
	withData(request, response, function(postedData) {
		if(checkData(postedData, showError)) {
			MongoClient.connect(dbUrl, function(err, db) {
				var collection = db.collection('users');
				collection.insertOne({
					name: postedData.name,
					email: postedData.email,
					password: postedData.password },
				function(error, result) {
					response.writeHead(error ? 400 : 200,
                                                                            {'Content-Type': 'application/json'});
					response.end(JSON.stringify(error || result));
				});
			});
		}
	});
}

La funzione checkData controlla la validità dei dati in ingresso, in questo caso verificando solo che sia stata specificata una e-mail. Essa verrà invocata nella successiva callback alla funzione withData, che si occupa di recuperare i dati inviati come payload al server. Se checkData non trova nessun errore, la callback invoca il client MongoDB per aprire una connessione. Una volta aperta, tramite insertOne viene richiesto di inserire un nuovo documento contenente i dati dell’utente. Qui è stato scelto di inserire solamente nome, e-mail e password.

In caso di fallimento verrà restituito un errore 400 e tutto il messaggio inviato da MongoDB verrà inviato al client sotto forma di oggetto JSON serializzato (funzione JSON.stringify).

Altrimenti verrà inoltrata la risposta affermativa di MongoDB, che ci dice che è stato inserito esattamente un elemento:

{ok: 1,n: 1}

La funzione di autenticazione dovrà interrogare il database per verificare le credenziali dell’utente, e in caso negativo, restituire un errore. In caso affermativo, invece, verrà generato un token attraverso un qualche algoritmo di crittografia.

var auth = function(request, response) {
	withData(request, response, function(postedData) {
		MongoClient.connect(dbUrl, function(err, db) {
			var collection = db.collection('users');
			collection.find({email: postedData.email, password: postedData.password})
                                               .toArray(function(err, docs) {
				if(docs.length == 1) {
					response.writeHead(200, {'Content-Type': 'text/plain'});
					response.end(Token.generate(docs[0]));
				}
				else {
					response.writeHead(401, {'Content-Type': 'text/plain'});
					response.end("Invalid user");
				}
			});
		});
	});
};

Il servizio poi si aspetterà che il token sia presente nelle successive chiamate ai servizi. Ad esempio, la chiamata per inviare un messaggio testuale conterrà il token di autenticazione e i dati del messaggio. Vediamo di seguito un estratto del comando sendPhoto per inviare un’immagine:

var buffer = new Buffer(postedData.data, "base64");
var gs = new GridStore(db, uniqueName(postedData) + ".png", "w", {
	content_type: "image/png",
	metadata: {
		userDest: postedData.userDest,
		author: postedData.author
	},
	chunk_size: 1024
});
gs.open(function(err, file) {
if(! err) {
		file.write(buffer, function(err, store) {
			store.close();
			response.writeHead(err ? 400 : 200, {'Content-Type': 'application/json'});
			response.end(JSON.stringify(err));
		});
	} else { sendError(err); }
});

Come si vede, il dato inviato dal dispositivo, cifrato in Base64 in una proprietà JSON, viene letto da un Buffer, scritto su file con la funzione write tramite new GridStore, e aperto con la funzione open.

Ti consigliamo anche