在express.js上使用(并重用)多个mongoose数据库连接

时间:2015-04-12 02:17:59

标签: node.js express mongoose middleware multi-tenant

我正在寻找最简单的&用于管理项目的多租户express.js应用程序的高效方法。

阅读几篇博客和文章,我发现,对于我的应用程序,每个租户架构都有一个数据库会很好。

我的第一次尝试是使用子域来检测租户,然后将子域映射到mongodb数据库。

我想出了这个快速的中间件

var mongoose = require('mongoose');
var debug = require('debug')('app:middleware:mongooseInstance');
var conns [];
function mongooseInstance (req, res, next) {
    var sub = req.sub = req.subdomains[0] || 'app';
    // if the connection is cached on the array, reuse it
    if (conns[sub]) {
        debug('reusing connection', sub, '...');
        req.db = conns[sub];
    } else {
        debug('creating new connection to', sub, '...');
        conns[sub] = mongoose.createConnection('mongodb://localhost:27017/' + sub);
        req.db = conns[sub];
    }
    next();
}
module.exports = mongooseInstance;

然后我在另一个中间件中注册模型:

var fs = require('fs');
var debug = require('debug')('app:middleware:registerModels');
module.exports = registerModels;

var models = [];
var path = __dirname + '/../schemas';

function registerModels (req, res, next) {
    if(models[req.sub]) {
        debug('reusing models');
        req.m = models[req.sub];
    } else {
        var instanceModels = [];
        var schemas = fs.readdirSync(path);
        debug('registering models');
        schemas.forEach(function(schema) {
            var model = schema.split('.').shift();
            instanceModels[model] = req.db.model(model, require([path, schema].join('/')));
        });
        models[req.sub] = instanceModels;
        req.m = models[req.sub];
    }
    next();
}

然后我可以像任何其他express.js app一样正常进行:

var express = require('express');
var app = express();
var mongooseInstance = require('./lib/middleware/mongooseInstance');
var registerModels = require('./lib/middleware/registerModels');

app.use(mongooseInstance);
app.use(registerModels);

app.get('/', function(req, res, next) {
    req.m.Project.find({},function(err, pets) {
        if(err) {
            next(err);
        }
        res.json({ count: pets.length, data: pets });
    });
});

app.get('/create', function (req, res) {
    var p = new req.m.Project({ name: 'Collin', description: 'Sad' });
    p.save(function(err, pet) {
        res.json(pet);
    });
});

app.listen(8000);

该应用程序工作正常,我现在没有更多,我想在继续之前得到一些反馈,所以我的问题是:

这种方法是否有效?考虑到这里会发生很多事情,多个租户,每个都有几个用户,我打算设置webhooks以触发每个实例,电子邮件,等...

我是否缺少任何瓶颈/陷阱?我正试图从一开始就让它具有可扩展性。

模型注册怎么样?我没有找到任何其他方法来实现这一目标。

谢谢!

1 个答案:

答案 0 :(得分:6)

  

这种方法有效吗?   我有什么瓶颈/缺陷吗?

这一切似乎对我来说都是正确的

  

模型注册怎么样?

我同意@ narc88您不需要在中间件中注册模型。

由于缺乏更好的术语,我会使用工厂模式。这个“工厂函数”会占用您的子域,或者您决定检测租户,并返回Models个对象。如果给定的中间件想要使用其可用的Models,则只需执行

var Models = require(/* path to your Model factory */);

...

// later on inside a route, or wherever
var models = Models(req.sub/* or req.tenant ?? */);
models.Project.find(...);

对于“工厂”示例,请原谅复制/粘贴

var mongoose = require('mongoose');
var fs = require('fs');
var debug = require('debug')('app:middleware:registerModels');

var models = [];
var conns = [];
var path = __dirname + '/../schemas';

function factory(tenant) {
    // if the connection is cached on the array, reuse it
    if (conns[tenant]) {
        debug('reusing connection', tenant, '...');
    } else {
        debug('creating new connection to', tenant, '...');
        conns[tenant] = mongoose.createConnection('mongodb://localhost:27017/' + tenant);
    }

    if(models[tenant]) {
        debug('reusing models');
    } else {
        var instanceModels = [];
        var schemas = fs.readdirSync(path);
        debug('registering models');
        schemas.forEach(function(schema) {
            var model = schema.split('.').shift();
            instanceModels[model] = conns[tenant].model(model, require([path, schema].join('/')));
        });
        models[tenant] = instanceModels;
    }
    return models[tenant];
}

module.exports = factory;

除了潜在的(虽然可能很小)性能提升,我认为它还具有以下优势:

  • 不会使请求对象混乱
  • 您不必担心中间件订购
  • 允许更轻松地抽取给定模型集的权限,即模型不是要求所有中间件看到
  • 此方法不会将模型与http请求联系起来,因此您可以灵活地在作业队列中使用相同的工厂,或者其他任何工具。