loopbackjs: прикрепите модель к разным источникам данных

Я определил несколько моделей, которые используют источник данных "db" (mysql) для моей среды.

Есть ли способ подключить к этим моделям несколько источников данных, чтобы я мог выполнять операции REST с разными базами данных?

то есть: ПОЛУЧИТЬ /api/Things?ds="db"

ПОЛУЧИТЬ /api/Things?ds="anotherdb"

GET /api/Things (будет использоваться ds по умолчанию)


person Juanra    schedule 25.11.2014    source источник


Ответы (3)


Как указывалось выше @superkhaus, каждая модель LoopBack может быть привязана только к одному источнику данных.

Вы можете создать (подкласс) новую модель для каждого источника данных, который вы хотите использовать. Затем вы можете либо предоставить эти модели для каждого источника данных через уникальные URL-адреса REST, либо реализовать модель-оболочку, которая будет отправлять методы в правильную модель, специфичную для источника данных.

В моем примере я покажу, как предоставлять модели для каждого источника данных для модели Car, которая прикреплена к db и anotherdb. Модель 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
  }
}
person Miroslav Bajtoš    schedule 04.02.2015
comment
Я следовал вашим шагам, но кажется, что отсутствует часть, удаление модели Car из model-config.js приводит к сбою моего сервера с ошибкой, говорящей Test is not defined - person AIMEUR Amin; 26.01.2021

По умолчанию вы можете присоединять источники данных только для каждой модели. Это означает, что вы можете прикрепить каждую модель к другому источнику данных через 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 для получения дополнительной информации.

person superkhau    schedule 25.11.2014

Для тех, кто все еще ищет рабочий ответ на этот вопрос, решение для переключения баз данных на лету состояло в том, чтобы написать скрипт промежуточного программного обеспечения, который проверял путь запроса, а затем создавал новый коннектор 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();
  };
};
person Jeff Frazier    schedule 04.02.2019