עבודה עם מספר בסיסי נתונים ב Sequelize

08/08/2019

בפרויקט ברירת מחדל חדש ש Sequelize יוצר אנחנו יכולים בקלות להגדיר מספר בסיסי נתונים עבור הסביבות השונות לדוגמא בסיס נתונים אחד עבור Production, שני עבור test ושלישי לפיתוח. הבעיה שמאותו שלד פרויקט לא כל כך ברור איך להתחבר למספר בסיסי נתונים במקביל כדי למשל להעביר מידע ביניהם, תוך שימוש במודלים ובקוד שכבר יש לנו מ Sequelize. אני רוצה להציג כאן תבנית פרויקט פשוטה שכן מאפשרת עבודה עם מספר בסיסי נתונים במקביל בפרויקט Sequelize.

1. הקובץ config.js

השינוי הראשון בהשוואה לפרויקט שמג'ונרט אוטומטית הוא הקובץ config.js שעכשיו יכיל את פרטי בסיסי הנתונים השונים:

const path = require('path');

module.exports = {
  "db1": {
    dialect: 'sqlite',
    storage: path.resolve(__dirname, '..', 'data', 'db1.db'),
  },
  "db2": {
    dialect: 'sqlite',
    storage: path.resolve(__dirname, '..', 'data', 'db2.db'),
  },
};

אני קראתי להם בשמות הקצת טפשיים db1 ו db2. בעולם האמיתי ייתכן ותרצו שכל אחד מהם יהיה אוביקט מורכב ובתוכו הסביבות production, test ו development. בכל מקרה אם כן זה ידרוש מכם קצת עבודת התאמה בקובץ index.js.

2. קובץ הגדרות המודלים models/index.js

קובץ הגדרות המודלים הדיפולטי בוחר את הסביבה המתאימה וטוען את כל המודלים עבורה בלבד. עם שינוי קטן מאוד אפשר לטעון את כל המודלים בכל הסביבות, או לפחות בסביבות איתן אנחנו רוצים לעבוד. האוביקט אותו אני מייצא יראה כך:

const dbs = {
  db1: {},
  db2: {},
};

ובעצם כל אחד מהאוביקטים הפנימיים בו יכיל את מנגנון החיבור לבסיס הנתונים של הסביבה המתאימה. החלפתי את קוד הטעינה המקורי בקוד הבא שטוען את כל המודלים לכל אחת מהסביבות:

'use strict';

const fs = require('fs');
const path = require('path');
const Sequelize = require('sequelize');
const basename = path.basename(__filename);
const env = process.env.NODE_ENV || 'development';
const config = require(__dirname + '/../config/config.js')[env];

const dbs = {
  db1: {},
  db2: {},
};

for (let [name, db] of Object.entries(dbs)) {  
  let config = require(__dirname + '/../config/config.js')[name];
  db.sequelize = new Sequelize(config.database, config.username, config.password, config);
}

fs
  .readdirSync(__dirname)
  .filter(file => {
    return (file.indexOf('.') !== 0) && (file !== basename) && (file.slice(-3) === '.js');
  })
  .forEach(file => {
    for (let [name, db] of Object.entries(dbs)) {  
      const model = db.sequelize['import'](path.join(__dirname, file));
      db[model.name] = model;
    }
  });

for (let [name, db] of Object.entries(dbs)) {  
  Object.keys(db).forEach(modelName => {
    if (db[modelName].associate) {
      db[modelName].associate(db);
    }
  });
}

dbs.Sequelize = Sequelize;

module.exports = dbs;

3. הקוד בפעולה - הקובץ app.js

עכשיו אנחנו מוכנים לצאת לדרך עם הקובץ app.js שיראה איך להשתמש בשני בסיסי הנתונים במקביל. הפונקציה הבאה מתחברת לשני בסיסי הנתונים ומוצאת את כל אוביקטי הספרים שמוגדרים באחד אך לא בשני:

async function main() {
  const books1 = await dbs.db2.Book.findAll();
  const ids = books1.map(b => b.id);

  const books2 = await dbs.db1.Book.findAll({
    where: {
      id: { [Op.notIn]: ids }
    }
  });

  console.log(books2.map(b => b.title));
}

בצורה כזאת אנחנו יכולים לעבוד עם שני בסיסי הנתונים באותו הזמן והכל תוך שימוש במחלקות של Sequelize.

4. נ.ב. הפעלת מיגרציות

הקוד של Sequelize כבר יודע להפעיל מיגרציות שיתחברו לבסיסי נתונים שונים, כאשר כל מה שצריך זה לקבוע את הסביבה באמצעות משתנה סביבה בשם המתאים - לדוגמא כדי להפעיל db:migrate על שני בסיסי הנתונים נכתוב את שתי הפקודות:

$ NODE_ENV=db1 sequelize db:migrate
$ NODE_ENV=db2 sequelize db:migrate