MongoDB
 sql >> Database >  >> NoSQL >> MongoDB

Connessione dinamica al database a mongodb o mongoose da nodejs

Questo per aiutare gli altri che potrebbero trovarsi in una situazione simile alla mia. Spero che possa essere standardizzato. Non penso che dovremmo reinventare la ruota ogni volta che qualcuno ha bisogno di creare un'applicazione multi-tenant.

Questo esempio descrive una struttura multi-tenant con ogni cliente che ha il proprio database. Come ho detto, potrebbe esserci un modo migliore per farlo, ma poiché non ho ricevuto aiuto da solo, questa era la mia soluzione.

Quindi, ecco gli obiettivi di questa soluzione:

  • ogni client è identificato dal sottodominio, ad esempio client1.application.com,
  • l'applicazione verifica se il sottodominio è valido,
  • l'applicazione cerca e ottiene le informazioni di connessione (URL del database, credenziali, ecc.) dal database principale,
  • l'applicazione si connette al database del client (praticamente passa al client),
  • l'applicazione adotta misure per garantire l'integrità e la gestione delle risorse (ad es. utilizzare la stessa connessione al database per i membri dello stesso client, anziché creare una nuova connessione).

Ecco il codice

nel tuo app.js file

app.use(clientListener()); // checks and identify valid clients
app.use(setclientdb());// sets db for valid clients

Ho creato due middleware:

  • clientListener - per identificare il client che si connette,
  • setclientdb - ottiene i dettagli del client dal database principale, dopo che il client è stato identificato, e quindi stabilisce la connessione al database del client.

Middleware client Listener

Controllo chi è il cliente controllando il sottodominio dall'oggetto richiesta. Eseguo un sacco di controlli per assicurarmi che il client sia valido (so che il codice è disordinato e può essere reso più pulito). Dopo essermi assicurato che il client sia valido, memorizzo le informazioni sui clienti nella sessione. Controllo anche che se le informazioni sui client sono già memorizzate nella sessione, non è necessario interrogare nuovamente il database. Dobbiamo solo assicurarci che il sottodominio della richiesta corrisponda a quello che è già memorizzato nella sessione.

var Clients = require('../models/clients');
var basedomain = dbConfig.baseDomain;
var allowedSubs = {'admin':true, 'www':true };
allowedSubs[basedomain] = true;
function clientlistener() {
return function(req, res, next) {
    //console.dir('look at my sub domain  ' + req.subdomains[0]);
    // console.log(req.session.Client.name);

    if( req.subdomains[0] in allowedSubs ||  typeof req.subdomains[0] === 'undefined' || req.session.Client && req.session.Client.name === req.subdomains[0] ){
        //console.dir('look at the sub domain  ' + req.subdomains[0]);
        //console.dir('testing Session ' + req.session.Client);
        console.log('did not search database for '+ req.subdomains[0]);
        //console.log(JSON.stringify(req.session.Client, null, 4));
        next();
    }
    else{

        Clients.findOne({subdomain: req.subdomains[0]}, function (err, client) {
            if(!err){
                if(!client){
                    //res.send(client);
                    res.send(403, 'Sorry! you cant see that.');
                }
                else{
                    console.log('searched database for '+ req.subdomains[0]);
                    //console.log(JSON.stringify(client, null, 4));
                    //console.log(client);
                   // req.session.tester = "moyo cow";
                    req.session.Client = client;
                    return next();

                }
            }
            else{
                console.log(err);
                return next(err)
            }

        });
    }

   }
 }

module.exports = clientlistener;

Middleware setclientdb:

Controllo tutto di nuovo assicurandomi che il client sia valido. Quindi viene aperta la connessione al database del cliente con le informazioni recuperate dalla sessione.

Mi assicuro inoltre di archiviare tutte le connessioni attive in un oggetto globale, in modo da impedire nuove connessioni al database su ogni richiesta (non vogliamo sovraccaricare ogni server mongodb dei client con connessioni).

var mongoose = require('mongoose');
//var dynamicConnection = require('../models/dynamicMongoose');
function setclientdb() {
    return function(req, res, next){
        //check if client has an existing db connection                                                               /*** Check if client db is connected and pooled *****/
    if(/*typeof global.App.clientdbconn === 'undefined' && */ typeof(req.session.Client) !== 'undefined' && global.App.clients[req.session.Client.name] !== req.subdomains[0])
    {
        //check if client session, matches current client if it matches, establish new connection for client
        if(req.session.Client && req.session.Client.name === req.subdomains[0] )
        {
            console.log('setting db for client ' + req.subdomains[0]+ ' and '+ req.session.Client.dbUrl);
            client = mongoose.createConnection(req.session.Client.dbUrl /*, dbconfigoptions*/);


            client.on('connected', function () {
                console.log('Mongoose default connection open to  ' + req.session.Client.name);
            });
            // When the connection is disconnected
            client.on('disconnected', function () {
                console.log('Mongoose '+ req.session.Client.name +' connection disconnected');
            });

            // If the Node process ends, close the Mongoose connection
            process.on('SIGINT', function() {
                client.close(function () {
                    console.log(req.session.Client.name +' connection disconnected through app termination');
                    process.exit(0);
                });
            });

            //If pool has not been created, create it and Add new connection to the pool and set it as active connection

            if(typeof(global.App.clients) === 'undefined' || typeof(global.App.clients[req.session.Client.name]) === 'undefined' && typeof(global.App.clientdbconn[req.session.Client.name]) === 'undefined')
            {
                clientname = req.session.Client.name;
                global.App.clients[clientname] = req.session.Client.name;// Store name of client in the global clients array
                activedb = global.App.clientdbconn[clientname] = client; //Store connection in the global connection array
                console.log('I am now in the list of active clients  ' + global.App.clients[clientname]);
            }
            global.App.activdb = activedb;
            console.log('client connection established, and saved ' + req.session.Client.name);
            next();
        }
        //if current client, does not match session client, then do not establish connection
        else
        {
            delete req.session.Client;
            client = false;
            next();
        }
    }
    else
    {
        if(typeof(req.session.Client) === 'undefined')
        {
           next();
        }
        //if client already has a connection make it active
        else{
            global.App.activdb = global.App.clientdbconn[req.session.Client.name];
            console.log('did not make new connection for ' + req.session.Client.name);
            return next();
        }

    }
    }
}

module.exports = setclientdb;

Ultimo ma non meno importante

Dal momento che sto usando una combinazione di mangusta e mongo nativo, dobbiamo compilare i nostri modelli in fase di esecuzione. Si prega di vedere sotto

Aggiungilo al tuo app.js

// require your models directory
var models = require('./models');

// Create models using mongoose connection for use in controllers
app.use(function db(req, res, next) {
    req.db = {
        User: global.App.activdb.model('User', models.agency_user, 'users')
        //Post: global.App.activdb.model('Post', models.Post, 'posts')
    };
    return next();
});

Spiegazione:

Come ho detto in precedenza, ho creato un oggetto globale per memorizzare l'oggetto di connessione al database attivo:global.App.activdb

Quindi utilizzo questo oggetto di connessione per creare (compilare) il modello mangusta, dopo averlo memorizzato nella proprietà db dell'oggetto req:req.db . Lo faccio in modo da poter accedere ai miei modelli nel mio controller come questo, ad esempio.

Esempio del controller dei miei Utenti:

exports.list = function (req, res) {
    req.db.User.find(function (err, users) {

        res.send("respond with a resource" + users + 'and connections  ' + JSON.stringify(global.App.clients, null, 4));
        console.log('Worker ' + cluster.worker.id + ' running!');
    });

};

Tornerò e pulirò questo alla fine. Se qualcuno vuole aiutarmi, sia gentile.