Sequelize建立model及數據庫配置


  直接使用Sequelize雖然可以,但是存在一些問題。團隊開發時,有人喜歡自己加timestamp,有人又喜歡自增主鍵,並且自定義表名。一個大型Web App通常都有幾十個映射表,一個映射表就是一個Model。如果按照各自喜好,那業務代碼就不好寫。Model不統一,很多代碼也無法復用。所以我們需要一個統一的模型,強迫所有Model都遵守同一個規范,這樣不但實現簡單,而且容易統一風格。

一、Model

  我們首先要定義的就是Model存放的文件夾必須在models內,並且以Model名字命名,例如:Pet.jsUser.js等等。

  其次,每個Model必須遵守一套規范:

  1. 統一主鍵,名稱必須是id,類型必須是STRING(50)
  2. 主鍵可以自己指定,也可以由框架自動生成(如果為null或undefined);
  3. 所有字段默認為NOT NULL,除非顯式指定;
  4. 統一timestamp機制,每個Model必須有createdAtupdatedAtversion,分別記錄創建時間、修改時間和版本號。其中,createdAtupdatedAtBIGINT存儲時間戳,最大的好處是無需處理時區,排序方便。version每次修改時自增。

  所以,我們不要直接使用Sequelize的API,而是通過db.js間接地定義Model。例如,User.js應該定義如下:

const db = require('../db'); module.exports = db.defineModel('users', { email: { type: db.STRING(100), unique: true }, passwd: db.STRING(100), name: db.STRING(100), gender: db.BOOLEAN });

  這樣,User就具有emailpasswdnamegender這4個業務字段。idcreatedAtupdatedAtversion應該自動加上,而不是每個Model都去重復定義。

  所以,db.js的作用就是統一Model的定義:

const Sequelize = require('sequelize'); var sequelize = new Sequelize('dbname', 'username', 'password', { host: 'localhost', dialect: 'mysql', pool: { max: 5, min: 0, idle: 10000 } }); const ID_TYPE = Sequelize.STRING(50); function defineModel(name, attributes) { var attrs = {}; for (let key in attributes) { let value = attributes[key]; if (typeof value === 'object' && value['type']) { value.allowNull = value.allowNull || false; attrs[key] = value; } else { attrs[key] = { type: value, allowNull: false }; } } attrs.id = { type: ID_TYPE, primaryKey: true }; attrs.createdAt = { type: Sequelize.BIGINT, allowNull: false }; attrs.updatedAt = { type: Sequelize.BIGINT, allowNull: false }; attrs.version = { type: Sequelize.BIGINT, allowNull: false }; return sequelize.define(name, attrs, { tableName: name, timestamps: false, hooks: { beforeValidate: function (obj) { let now = Date.now(); if (obj.isNewRecord) { if (!obj.id) { obj.id = generateId(); } obj.createdAt = now; obj.updatedAt = now; obj.version = 0; } else { obj.updatedAt = Date.now(); obj.version++; } } } }); }

  我們定義的defineModel就是為了強制實現上述規則。

  Sequelize在創建、修改Entity時會調用我們指定的函數,這些函數通過hooks在定義Model時設定。我們在beforeValidate這個事件中根據是否是isNewRecord設置主鍵(如果主鍵為nullundefined)、設置時間戳和版本號。

  這么一來,Model定義的時候就可以大大簡化。

二、數據庫配置

  接下來,我們把簡單的config.js拆成3個配置文件:

  • config-default.js:存儲默認的配置;
  • config-override.js:存儲特定的配置;
  • config-test.js:存儲用於測試的配置。

  例如,默認的config-default.js可以配置如下:

var config = { dialect: 'mysql', database: 'nodejs', username: 'www', password: 'www', host: 'localhost', port: 3306 }; module.exports = config;

  而config-override.js可應用實際配置:

var config = { database: 'production', username: 'www', password: 'secret-password', host: '192.168.1.199' }; module.exports = config;

  config-test.js可應用測試環境的配置:

var config = { database: 'test' }; module.exports = config;

  讀取配置的時候,我們用config.js實現不同環境讀取不同的配置文件:

const defaultConfig = './config-default.js'; // 可設定為絕對路徑,如 /opt/product/config-override.js
const overrideConfig = './config-override.js'; const testConfig = './config-test.js'; const fs = require('fs'); var config = null; if (process.env.NODE_ENV === 'test') { console.log(`Load ${testConfig}...`); config = require(testConfig); } else { console.log(`Load ${defaultConfig}...`); config = require(defaultConfig); try { if (fs.statSync(overrideConfig).isFile()) { console.log(`Load ${overrideConfig}...`); config = Object.assign(config, require(overrideConfig)); } } catch (err) { console.log(`Cannot load ${overrideConfig}.`); } } module.exports = config;

  具體的規則是:

  1. 先讀取config-default.js
  2. 如果不是測試環境,就讀取config-override.js,如果文件不存在,就忽略。
  3. 如果是測試環境,就讀取config-test.js

  這樣做的好處是,開發環境下,團隊統一使用默認的配置,並且無需config-override.js

  部署到服務器時,由運維團隊配置好config-override.js,以覆蓋config-override.js的默認設置。

  測試環境下,本地和CI服務器統一使用config-test.js,測試數據庫可以反復清空,不會影響開發。

  配置文件表面上寫起來很容易,但是,既要保證開發效率,又要避免服務器配置文件泄漏,還要能方便地執行測試,就需要一開始搭建出好的結構,才能提升工程能力。

三、使用Model

  要使用Model,就需要引入對應的Model文件,例如:User.js。一旦Model多了起來,如何引用也是一件麻煩事。自動化永遠比手工做效率高,而且更可靠。我們寫一個model.js,自動掃描並導入所有Model:

const fs = require('fs'); const db = require('./db'); let files = fs.readdirSync(__dirname + '/models'); let js_files = files.filter((f)=>{ return f.endsWith('.js'); }, files); module.exports = {}; for (let f of js_files) { console.log(`import model from file ${f}...`); let name = f.substring(0, f.length - 3); module.exports[name] = require(__dirname + '/models/' + f); } module.exports.sync = () => { db.sync(); };

  這樣,需要用的時候,寫起來就像這樣:

const model = require('./model'); let Pet = model.Pet, User = model.User; var pet = await Pet.create({ ... });

  最終,我們創建的工程model-sequelize結構如下:

model-sequelize/
|
+- .vscode/
|  |
|  +- launch.json <-- VSCode 配置文件 |
+- models/ <-- 存放所有Model |  |
|  +- Pet.js <-- Pet |  |
|  +- User.js <-- User |
+- config.js <-- 配置文件入口 |
+- config-default.js <-- 默認配置文件 |
+- config-test.js <-- 測試配置文件 |
+- db.js <-- 如何定義Model |
+- model.js <-- 如何導入Model |
+- init-db.js <-- 初始化數據庫 |
+- app.js <-- 業務代碼 |
+- package.json <-- 項目描述文件 |
+- node_modules/ <-- npm安裝的所有依賴包

  注意到我們其實不需要創建表的SQL,因為Sequelize提供了一個sync()方法,可以自動創建數據庫。這個功能在開發和生產環境中沒有什么用,但是在測試環境中非常有用。測試時,我們可以用sync()方法自動創建出表結構,而不是自己維護SQL腳本。這樣,可以隨時修改Model的定義,並立刻運行測試。開發環境下,首次使用sync()也可以自動創建出表結構,避免了手動運行SQL的問題。

  init-db.js的代碼非常簡單:它最大的好處是避免了手動維護一個SQL腳本。

const model = require('./model.js'); model.sync(); console.log('init db ok.'); process.exit(0);

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM