Solución:
Esto es para ayudar a otros que pueden encontrarse en una situación similar a la mía. Espero que se pueda estandarizar. No creo que debamos tener que reinventar la rueda cada vez que alguien necesita hacer una solicitud para múltiples inquilinos.
Este ejemplo describe una estructura de múltiples inquilinos en la que cada cliente tiene su propia base de datos. Como dije, podría haber una mejor manera de hacer esto, pero como no obtuve ayuda, esta fue mi solución.
Así que estos son los objetivos a los que se dirige esta solución:
- cada cliente se identifica por subdominio, por ejemplo, client1.application.com,
- la aplicación comprueba si el subdominio es válido,
- la aplicación busca y obtiene información de conexión (URL de la base de datos, credenciales, etc.) de la base de datos maestra,
- la aplicación se conecta a la base de datos del cliente (prácticamente se entrega al cliente),
- La aplicación toma medidas para garantizar la integridad y la gestión de recursos (por ejemplo, utilizar la misma conexión de base de datos para miembros del mismo cliente, en lugar de realizar una nueva conexión).
Aqui esta el codigo
en tus app.js
expediente
app.use(clientListener()); // checks and identify valid clients
app.use(setclientdb());// sets db for valid clients
He creado dos middlewares:
-
clientListener
– identificar al cliente que se conecta, -
setclientdb
– obtiene los detalles del cliente de la base de datos maestra, una vez identificado el cliente, y luego establece la conexión con la base de datos del cliente.
middleware clientListener
Verifico quién es el cliente verificando el subdominio del objeto de solicitud. Hago un montón de comprobaciones para asegurarme de que el cliente sea válido (sé que el código está desordenado y se puede limpiar). Después de asegurarme de que el cliente es válido, almaceno la información del cliente en sesión. También verifico que si la información del cliente ya está almacenada en la sesión, no es necesario volver a consultar la base de datos. Solo necesitamos asegurarnos de que el subdominio de la solicitud coincida con el que ya está almacenado en la sesión.
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:
Reviso todo nuevamente asegurándome de que el cliente sea válido. Luego se abre la conexión a la base de datos del cliente con la información recuperada de la sesión.
También me aseguro de almacenar todas las conexiones activas en un objeto global, para evitar nuevas conexiones a la base de datos en cada solicitud (no queremos sobrecargar el servidor mongodb de cada cliente con conexiones).
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;
Por último pero no menos importante
Como estoy usando una combinación de mongoose y mongo nativo, tenemos que compilar nuestros modelos en tiempo de ejecución. Por favor ver más abajo
Agregue esto a su 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();
});
Explicación:
Como dije anteriormente, creé un objeto global para almacenar el objeto de conexión de la base de datos activa: global.App.activdb
Luego uso este objeto de conexión para crear (compilar) el modelo de mangosta, después de almacenarlo en la propiedad db del objeto req: req.db
. Hago esto para poder acceder a mis modelos en mi controlador de esta manera, por ejemplo.
Ejemplo de mi controlador de usuarios:
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!');
});
};
Volveré y limpiaré esto eventualmente. Si alguien quiere ayudarme, sea bueno.
Hola a todos, aquí hay una solución mucho más actualizada.
Así que estos son los objetivos a los que se dirige esta solución:
- cada cliente se identifica por subdominio, por ejemplo, client1.application.com,
- la aplicación comprueba si el subdominio es válido,
- la aplicación busca y obtiene información de conexión (URL de la base de datos, credenciales, etc.) de la base de datos maestra,
- la aplicación se conecta a la base de datos del cliente (prácticamente se entrega al cliente),
- La aplicación toma medidas para garantizar la integridad y la gestión de recursos (por ejemplo, utilizar la misma conexión de base de datos para miembros del mismo cliente, en lugar de realizar una nueva conexión).
actualizaciones
- uso de promesas,
- importación y compilación automática de modelos
- Nuevo middleware; modelsinit (utilizado para importar y compilar automáticamente modelos de mangosta)
- Limpieza de middlewares (setclientdb, clientlistener, modelsInit)
Consulte a continuación algunas explicaciones.
**
modelosInit Middleware
** características
- prueba si los modelos ya están compilados. Si es así, omita.
-
pruebas para ver si la solicitud no es una solicitud de inquilino; es decir (solicitud a la página de inicio de las aplicaciones, la página de administración, etc.)
'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: Explicación adicional
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;