【发布时间】:2015-01-23 07:51:25
【问题描述】:
我已经为我的环境定义了几个使用数据源“db”(mysql)的模型。
有什么方法可以将多个数据源附加到这些模型,以便我能够对不同的数据库执行 REST 操作?
即: 获取 /api/Things?ds="db"
GET /api/Things?ds="anotherdb"
GET /api/Things(将使用默认 ds)
【问题讨论】:
标签: node.js loopbackjs strongloop
我已经为我的环境定义了几个使用数据源“db”(mysql)的模型。
有什么方法可以将多个数据源附加到这些模型,以便我能够对不同的数据库执行 REST 操作?
即: 获取 /api/Things?ds="db"
GET /api/Things?ds="anotherdb"
GET /api/Things(将使用默认 ds)
【问题讨论】:
标签: node.js loopbackjs strongloop
正如@superkau 上面指出的,每个 LoopBack 模型只能附加到单个数据源。
您可以为要使用的每个数据源创建(子类化)新模型。然后,您可以通过唯一的 REST URL 公开这些每个数据源的模型,或者您可以实现一个包装器模型,将方法分派到正确的数据源特定模型。
在我的示例中,我将展示如何为附加到db 和anotherdb 的Car 模型公开每个数据源模型。 Car 模型通过common/models/car.json 和common/models/car.js 以通常的方式定义。
现在您需要定义每个数据源的模型:
// common/models/car-db.js
{
"name": "Car-db",
"base": "Car",
"http": {
"path": "/cars:db"
}
}
// common/models/car-anotherdb.js
{
"name": "Car-anotherdb",
"base": "Car",
"http": {
"path": "/cars:anotherdb"
}
}
// server/model-config.json
{
"Car": {
"dataSource": "default"
},
"Car-db": {
"dataSource": "db"
},
"Car-anotherdb": {
"dataSource": "anotherdb"
}
}
现在您可以使用以下 URL:
GET /api/Cars:db
GET /api/Cars:anotherdb
GET /api/Cars
上述解决方案有两个限制:您必须为每个数据源定义一个新模型,并且无法使用查询参数选择数据源。
要解决这个问题,您需要一种不同的方法。我再次假设已经定义了一个 Car 模型。
现在您需要创建一个“调度程序”。
// common/models/car-dispatcher.json
{
"name": "CarDispatcher",
"base": "Model", //< important!
"http": {
"path": "/cars"
}
}
// common/models/car-dispatcher.js
var loopback = require('loopback').PersistedModel;
module.exports = function(CarDispatcher) {
Car.find = function(ds, filter, cb) {
var model = this.findModelForDataSource(ds);
model.find(filter, cb);
};
// a modified copy of remoting metadata from loopback/lib/persisted-model.js
Car.remoteMethod('find', {
isStatic: true,
description: 'Find all instances of the model matched by filter from the data source',
accessType: 'READ',
accepts: [
{arg: 'ds', type: 'string', description: 'Name of the datasource to use' },
{arg: 'filter', type: 'object', description: 'Filter defining fields, where, orderBy, offset, and limit'}
],
returns: {arg: 'data', type: [typeName], root: true},
http: {verb: 'get', path: '/'}
});
// TODO: repeat the above for all methods you want to expose this way
Car.findModelForDataSource = function(ds) {
var app = this.app;
var ds = ds && app.dataSources[ds] || app.dataSources.default;
var modelName = this.modelName + '-' + ds;
var model = loopback.findModel(modelName);
if (!model) {
model = loopback.createModel(
modelName,
{},
{ base: this.modelName });
}
return model;
};
};
最后一点是删除Car并在模型配置中使用CarDispatcher:
// server/model-config.json
{
"CarDispatcher": {
dataSource: null,
public: true
}
}
【讨论】:
model-config.js 中删除 Car 模型会使我的服务器崩溃,并显示错误提示 Test is not defined
默认情况下,您只能基于每个模型附加数据源。这意味着您可以通过 datasources.json 将每个模型附加到不同的数据源。
对于您的用例,您将为多个数据源所需的每个端点添加一个远程挂钩。在您的远程挂钩中,您将执行以下操作:
...
var ds1 = Model.app.dataSources.ds1;
var ds2 = Model.app.dataSources.ds2;
//some logic to pick a data source
if (context.req.params...
...
请参阅http://docs.strongloop.com/display/LB/Remote+hooks 了解更多信息。
【讨论】:
对于仍在寻找工作答案的任何人,动态切换数据库的解决方案是编写一个检查请求路径的中间件脚本,然后创建一个新的 DataSource 连接器,传入基于 req.path 变量的变量。例如,如果请求路径是/orders,那么“orders”作为字符串将被保存在一个变量中,然后我们附加一个新的数据源,为“orders”传递该变量。这是完整的工作代码。
'use strict';
const DataSource = require('loopback-datasource-juggler').DataSource;
const app = require('../server.js');
module.exports = function() {
return function datasourceSelector(req, res, next) {
// Check if the API request path contains one of our models.
// We could use app.models() here, but that would also include
// models we don't want.
let $models = ['offers', 'orders', 'prducts'];
// $path expects to be 'offers', 'orders', 'prducts'.
let $path = req.path.toLowerCase().split("/")[1];
// Run our function if the request path is equal to one of
// our models, but not if it also includes 'count'. We don't
// want to run this twice unnecessarily.
if (($models.includes($path, 0)) && !(req.path.includes('count'))) {
// The angular customer-select form adds a true value
// to the selected property of only one customer model.
// So we search the customers for that 'selected' = true.
let customers = app.models.Customer;
// Customers.find() returns a Promise, so we need to get
// our selected customer from the results.
customers.find({"where": {"selected": true}}).then(function(result){
// Called if the operation succeeds.
let customerDb = result[0].name;
// Log the selected customer and the timestamp
// it was selected. Needed for debugging and optimization.
let date = new Date;
console.log(customerDb, $path+req.path, date);
// Use the existing veracore datasource config
// since we can use its environment variables.
let settings = app.dataSources.Veracore.settings;
// Clear out the veracore options array since that
// prevents us from changing databases.
settings.options = null;
// Add the selected customer to the new database value.
settings.database = customerDb;
try {
let dataSource = new DataSource(settings);
// Attach our models to the new database selection.
app.models.Offer.attachTo(dataSource);
app.models.Order.attachTo(dataSource);
app.models.Prduct.attachTo(dataSource);
} catch(err) {
console.error(err);
}
})
// Called if the customers.find() promise fails.
.catch(function(err){
console.error(err);
});
}
else {
// We need a better solution for paths like '/orders/count'.
console.log(req.path + ' was passed to datasourceSelector().');
}
next();
};
};
【讨论】: