Динамическое подключение базы данных к mongodb или mongoose от nodejs

Я пытаюсь создать приложение с несколькими арендаторами (saas), где каждый клиент имеет свою собственную базу данных.

Моя ситуация:

Я создал промежуточное программное обеспечение, которое определяло бы, кто клиент основан на субдомене, а затем извлекает информацию о подключении базы данных клиента из общей базы данных. Я не знаю, как установить объект соединения для этого клиента, чтобы иметь возможность использовать в моих контроллерах. И должен ли я делать это в промежуточном программном обеспечении или контроллере? И если это в модели, как передать строку и параметры подключения (я мог бы использовать сеанс, но я не знаю, как получить доступ к сеансу изнутри модели).

Как сделать следующее?

  • Организация: где я могу создать динамическое соединение db для клиента?
  • Параметры ввода/передачи соединения с контроллером или моделью (при определении соединения)
  • После динамического подключения, как мне получить доступ к нему глобально для этого клиента?

Это пример моего промежуточного ПО, и я хотел бы создать соединение mongoose, которое я хотел бы сделать динамическим (передать информацию о подключении клиента):

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.session.Client && req.session.Client.name === req.subdomains[0]) {
          var options = session.Client.options;
          var url = session.Client.url
          var conn = mongoose.createConnection(url, options);
          next();
       }
    }
}

Как мне получить доступ к этому объекту соединения внутри контроллера? Или из модели?

Спасибо.

Ответ 1

Это поможет другим, кто может оказаться в такой же ситуации, как я. Надеюсь, что это можно стандартизировать. Я не думаю, что нам придется изобретать велосипед каждый раз, когда кому-то нужно сделать многопользовательское приложение.

В этом примере описывается структура с несколькими арендаторами, причем каждый клиент имеет свою собственную базу данных. Как я уже сказал, может быть лучший способ сделать это, но поскольку я сам не получил помощь, это было мое решение.

Итак, вот цели, на которые нацелено это решение:

  • каждый клиент идентифицируется субдоменом, например client1.application.com,
  • проверяет, действителен ли субдомен,
  • приложение ищет и получает информацию о соединении (URL-адрес базы данных, учетные данные и т.д.) из основной базы данных,
  • приложение соединяется с клиентской базой данных (довольно много передает клиенту),
  • приложение принимает меры для обеспечения целостности и управления ресурсами (например, использовать одно и то же соединение с базой данных для членов одного и того же клиента, а не создавать новое соединение).

Вот код

в вашем app.js файле

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

Я создал два посредника:

  • clientListener - для идентификации подключения клиента,
  • setclientdb - получает информацию о клиенте из базы данных Master, после идентификации клиента, а затем устанавливает соединение с клиентской базой данных.

промежуточное ПО clientListener

Я проверяю, кто именно клиент, проверив поддомен от объекта запроса. Я делаю кучу проверок, чтобы убедиться, что клиент действительно (я знаю, что код грязный, и его можно сделать чище). Убедившись, что клиент действителен, я храню информацию о клиентах в сеансе. Я также проверяю, что если информация о клиентах уже сохранена в сеансе, нет необходимости снова запрашивать базу данных. Нам просто нужно убедиться, что поддомен запроса совпадает с тем, который уже сохранен в сеансе.

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;

промежуточное программное обеспечение setclientdb:

Я проверяю все снова, убедившись, что клиент действителен. Затем открывается соединение с клиентской базой данных с информацией, полученной из сеанса.

Я также должен хранить все активные соединения в глобальном объекте, чтобы предотвратить новые подключения к базе данных по каждому запросу (мы не хотим перегружать каждый клиентский сервер mongodb соединениями).

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;

Последнее, но не менее

Поскольку я использую комбинацию мангуста и родного монго, мы должны скомпилировать наши модели во время выполнения. См. Ниже

Добавьте это в свой 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();
});

Пояснение:

Как я уже говорил ранее, я создал глобальный объект для хранения активного объекта подключения к базе данных: global.App.activdb

Затем я использую этот объект соединения для создания (компиляции) модели mongoose, после того как я сохраню его в свойстве db объекта req: req.db. Я делаю это так, чтобы я мог получить доступ к моим моделям в моем контроллере, например, например.

Пример контроллера My Users:

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!');
    });

};

Я вернусь и уберу это в конце концов. Если кто-то хочет мне помочь, это будет хорошо.

Ответ 2

Привет всем, вот гораздо более обновленное решение.

Итак, вот цели, на которые нацелено это решение:

  • каждый клиент идентифицируется субдоменом, например client1.application.com,
  • проверяет, действителен ли субдомен,
  • приложение ищет и получает информацию о соединении (URL-адрес базы данных, учетные данные и т.д.) из основной базы данных,
  • приложение соединяется с клиентской базой данных (довольно много передает клиенту),
  • приложение принимает меры для обеспечения целостности и управления ресурсами (например, использовать одно и то же соединение с базой данных для членов одного и того же клиента, а не создавать новое соединение).

Обновления

  • использование promises,
  • автоматический импорт и компиляция моделей
  • Новое промежуточное ПО; modelsinit (используется для автоматического импорта и компиляции моделей мангуста).
  • Очистка посредников (setclientdb, clientlistener, modelsInit)

Ниже приведены некоторые пояснения

**

modelsInit Middleware

** функции

  • если модели уже скомпилированы. Если да, пропустите.
  • проверить, не является ли запрос не запросом арендатора; i.e(запрос на домашнюю страницу приложений, страницу администратора и т.д.)

    'use strict';
    /**
     * Created by moyofalaye on 3/17/14.
     */
    
    var path = require('path');
    var config = require('../../config/config');
    
    // Globbing model files
    
    config.getGlobbedFiles('./app/models/*.js').forEach(function (modelPath) {
        require(path.resolve(modelPath));
    
    });
    
    
    
    function modelsInit() {
        return function (req, res, next) {
    
    //console.log(req.subdomains[0]);
            switch (req.subdomains[0]) {
                case 'www':
                case undefined:
                    return next();
                    break;
                case 'admin':
                    return next();
                    break;
    //            default:
    //              return
            }
            var clientname = req.session.Client.name;
    
        // test if models are not already compiled if so, skip
        if (/*typeof req.db === 'undefined' && */ typeof global.App.clientModel[clientname] === 'undefined') {
            req.db = {};
         //Get files from models directory
                config.getGlobbedFiles('./app/models/clientmodels/**/*.js').forEach(function (modelPath) {
                console.log('the filepath is ' + modelPath);
                //Deduce/ extrapulate model names from the file names
                //Im not very good with regxp but this is what i had to do, to get the names from the filename e.g users.server.models.js (this is my naming convention, so as not to get confused with server side models and client side models
    
                var filename = modelPath.replace(/^.*[\\\/]/, '');
                var fullname = filename.substr(0, filename.lastIndexOf('.'));
                var endname = fullname.indexOf('.');
                var name = fullname.substr(0, endname);
                req.db[name] = require(path.resolve(modelPath))(global.App.activdb);
                console.log('the filename is ' + name);
            });
    
            global.App.clientModel[clientname] = req.db;
    
            console.log(global.App.clients);
    
            return next();
        }
        // since models exist, pass it to request.db for easy consumption in controllers
        req.db = global.App.clientModel[clientname];
        return next();
        };
    }
    
    module.exports = modelsInit;
    

Todo: Дальнейшее объяснение

ClientListener.js

var config = require('../../config/config');
var Clients = require('../models/clients');
var basedomain = config.baseDomain;
var allowedSubs = {'admin': true, 'www': true};
allowedSubs[basedomain] = true;

//console.dir(allowedSubs);

function clientlistener() {
    return function (req, res, next) {
        //check if client has already been recognized
        if (req.subdomains[0] in allowedSubs || typeof req.subdomains[0] == 'undefined' || req.session.Client && req.session.Client.name === req.subdomains[0]) {
            console.log('did not search database for ' + req.subdomains[0]);
            //console.log(JSON.stringify(req.session.Client, null, 4));
            return next();
        }

        //look for client in database
        else {

            Clients.findOne({subdomain: req.subdomains[0]}, function (err, client) {
                if (!err) {
                    //if client not found
                    if (!client) {
                        //res.send(client);
                        res.status(403).send('Sorry! you cant see that.');
                        console.log(client);
                    }
                    // client found, create session and add client
                    else {
                        console.log('searched database for ' + req.subdomains[0]);
                        req.session.Client = client;
                        return next();
                    }
                }
                else {
                    console.log(err);
                    return next(err)
                }

            });
        }

    }
}

module.exports = clientlistener;

setclientdb.js

var client;
            var clientname;
            var activedb;

            var Promise = require("bluebird");
            Promise.promisifyAll(require("mongoose"));
            //mongoose = require('mongoose');


            function setclientdb() {
                return function (req, res, next) {
                    //check if client is not valid
                    if (typeof(req.session.Client) === 'undefined' || req.session.Client && req.session.Client.name !== req.subdomains[0]) {
                        delete req.session.Client;
                        client = false;
                        return next();
                    }
                    //if client already has an existing connection make it active
                    else if (global.App.clients.indexOf(req.session.Client.name) > -1) {
                        global.App.activdb = global.App.clientdbconn[req.session.Client.name]; //global.App.clientdbconnection is an array of or established connections
                        console.log('did not make new connection for ' + req.session.Client.name);
                        return next();
                    }
                    //make new db connection
                    else {
                        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);
                            //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.push(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 and set it as the current active database
                                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);
                                return next();
                            }
                        });
                        // 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);
                            });
                        });
                    }


                }
            }

            module.exports = setclientdb;

Дальнейшие пояснения Прибытие