loopbackjs: Attach a model to different datasources

前端 未结 3 1560
予麋鹿
予麋鹿 2021-01-02 16:14

I have defined several models that use a Datasource \"db\" (mysql) for my environment.

Is there any way to have several datasources attached to those models, so I wo

相关标签:
3条回答
  • 2021-01-02 16:57

    For anyone still looking for a working answer to this, the solution for switching databases on the fly was to write a middleware script that examined the request path and then created a new DataSource connector, passing in a variable based on the req.path variable. For example, if the request path is /orders, then "orders" as a string would be saved in a variable, then we attached a new Datasource, passing in that variable for "orders". Here's the complete 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 讨论(0)
  • 2021-01-02 17:00

    As @superkhau pointed above, each LoopBack Model can be attached to a single data-source only.

    You can create (subclass) a new model for each datasource you want to use. Then you can either expose these per-datasource models via unique REST URLs, or you can implement a wrapper model that will dispatch methods to the correct datasource-specific model.

    In my example, I'll show how to expose per-datasource models for a Car model that is attached to db and anotherdb. The Car model is defined in the usual way via common/models/car.json and common/models/car.js.

    Now you need to define per-datasource models:

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

    Now you have the following URLs available:

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

    The solution outlined above has two limitations: you have to define a new model for each datasource and the datasource cannot be selected using a query parameter.

    To fix that, you need a different approach. I'll again assume there is a Car model 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 to remove Car and use CarDispatcher in the model config:

    // server/model-config.json
    {
      "CarDispatcher": {
        dataSource: null,
        public: true
      }
    }
    
    0 讨论(0)
  • 2021-01-02 17:08

    By default, you can only attach data sources on a per-model basis. Meaning you can attach each model to a different data source via datasources.json.

    For your use case, you will to add a remote hook to each endpoint you want 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 info.

    0 讨论(0)
提交回复
热议问题