Loopbackjs: attach a model to various data sources

I have identified several models that use the Datasource "db" (mysql) for my environment.

Is there a way to have multiple data sources attached to these models, so I could perform REST operations on different databases?

i.e.: GET / api / Things? Ds = "db"

GET / api / Things? ds = "anotherdb"

GET / api / Things (will use default ds)

+8
source share
3 answers

As @ superhaw noted, each LoopBack model can only be connected to one data source.

You can create (subclass) a new model for each data source that you want to use. You can then either expose these models using data using unique REST URLs, or you can implement a wrapper model that will send methods to the correct model, depending on the data source.

In my example, I will show how to expose models with one data source for the Car model, which is bound to db and anotherdb . The Car model is defined in the usual way through common/models/car.json and common/models/car.js

Now you need to define models for each data source:

 // 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" } } 

You now have the following URLs:

 GET /api/Cars:db GET /api/Cars:anotherdb GET /api/Cars 

The solution described above has two limitations: you must define a new model for each data source, and the data source cannot be selected using the query parameter.

To fix this, you need a different approach. Once again, I assume that the Car model is already defined.

Now you need to create a "dispatcher".

 // 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; }; }; 

The final bit is removing Car and using CarDispatcher in the model configuration:

 // server/model-config.json { "CarDispatcher": { dataSource: null, public: true } } 
+6
source

By default, you can only connect data sources for each model only. This means that you can attach each model to a different data source through datasources.json.

In your use case, you add a remote hook to each endpoint that you want to use for multiple data sources. In your remote hook you will do something like:

 ... var ds1 = Model.app.dataSources.ds1; var ds2 = Model.app.dataSources.ds2; //some logic to pick a data source if (context.req.params... ... 

See http://docs.strongloop.com/display/LB/Remote+hooks for more details.

+1
source

For those who are still looking for a working answer to this question, the solution for switching databases on the fly was to write a middleware script that checked the query path and then created a new DataSource connector, passing a variable based on the req.path variable. For example, if the request path is / orders, then "orders" will be stored as a string in a variable, then we added a new data source by passing this variable to "orders". Here is the full working code.

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

};

0
source

Source: https://habr.com/ru/post/978675/


All Articles