Sequelize建⽴model及数据库配置
直接使⽤Sequelize虽然可以,但是存在⼀些问题。团队开发时,有⼈喜欢⾃⼰加timestamp,有⼈⼜喜欢⾃增主键,并且⾃定义表名。⼀个⼤型Web App通常都有⼏⼗个映射表,⼀个映射表就是⼀个Model。如果按照各⾃喜好,那业务代码就不好写。Model不统⼀,很多代码也⽆法复⽤。所以我们需要⼀个统⼀的模型,强迫所有Model都遵守同⼀个规范,这样不但实现简单,⽽且容易统⼀风格。
⼀、Model
我们⾸先要定义的就是Model存放的⽂件夹必须在models内,并且以Model名字命名,例如:Pet.js,User.js等等。
其次,每个Model必须遵守⼀套规范:
1. 统⼀主键,名称必须是id,类型必须是STRING(50);
2. 主键可以⾃⼰指定,也可以由框架⾃动⽣成(如果为null或undefined);
3. 所有字段默认为NOT NULL,除⾮显式指定;
4. 统⼀timestamp机制,每个Model必须有createdAt、updatedAt和version,分别记录创建时间、修改时间和版本号。其
中,createdAt和updatedAt以BIGINT存储时间戳,最⼤的好处是⽆需处理时区,排序⽅便。version每次修改时⾃增。
所以,我们不要直接使⽤Sequelize的API,⽽是通过db.js间接地定义Model。例如,User.js应该定义如下:
const db = require('../db');
email: {
type: db.STRING(100),
unique: true
},
passwd: db.STRING(100),
name: db.STRING(100),
gender: db.BOOLEAN
});
这样,User就具有email、passwd、name和gender这4个业务字段。id、createdAt、updatedAt和version应该⾃动加上,⽽不是每个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
};
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 = w();
if (obj.isNewRecord) {
if (!obj.id) {
obj.id = generateId();
}
obj.updatedAt = now;
obj.version = 0;
} else {
obj.updatedAt = w();
obj.version++;
}
}
}
});
}
我们定义的defineModel就是为了强制实现上述规则。
Sequelize在创建、修改Entity时会调⽤我们指定的函数,这些函数通过hooks在定义Model时设定。我们在beforeValidate这个事件中根据是否是isNewRecord设置主键(如果主键为null或undefined)、设置时间戳和版本号。
这么⼀来,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
};
⽽config-override.js可应⽤实际配置:
var config = {
database: 'production',
username: 'www',
password: 'secret-password',
js assignhost: '192.168.1.199'
};
config-test.js可应⽤测试环境的配置:
var config = {
database: 'test'
};
读取配置的时候,我们⽤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 (v.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}.`);
}
}
具体的规则是:
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)=>{
dsWith('.js');
}, files);
for (let f of js_files) {
console.log(`import model from file ${f}...`);
let name = f.substring(0, f.length - 3);
}
db.sync();
};
这样,需要⽤的时候,写起来就像这样:
const model = require('./model');
let
Pet = model.Pet,
User = model.User;
var pet = ate({ ... });
最终,我们创建的⼯程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.');
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论