数据变更的场景
模块一般都有与业务相关的数据架构,比如模块test-party
有一个业务数据表testParty
,其中包含若干字段。当模块编译并部署发布后,如果模块数据架构需要调整(比如给某个数据表添加一个字段),应该怎么设计这种数据变更的机制呢?
数据变更的策略
策略1: 使用SQL文件进行数据迁移
有的框架根据数据的变更生成一系列SQL文件,通过导入SQL文件实现数据的迁移。这种策略主要存在两个问题:
-
面对复杂变更力不从心
:由于在实际的业务场景中,数据的变更逻辑非常复杂,比如删除一个字段
,可能还需要联动对其他的业务数据做一些调整。如果仅仅使用SQL语句来表达这些业务数据变更,往往显得力不从心 -
无法便利的支持多实例多租户场景
:比如有1000个租户实例,每个实例都需要初始化自己的数据。基于性能考虑,不可能在系统启动时同时执行
这1000实例的初始化逻辑,而是要按需执行
。具体而言,就是系统启动时不执行实例的初始化逻辑,什么时候有前端用户访问某个实例的接口服务时,才会执行该实例的初始化逻辑
策略2: 使用JS代码进行数据变更
CabloyJS采用JS代码来管理数据变更的逻辑,具体而言就是通过一个Bean组件
集中管理模块数据变更的逻辑。当模块编译并部署发布后,模块当前的数据版本
处于封闭状态
。如果有新的数据架构
变更,只需要递增
模块的数据版本,然后在Bean组件
中实现变更逻辑
这样,当系统启动时,就会自动检测模块数据版本
是否有变化;如果有变化,就会执行Bean组件
的升级逻辑,从而完成数据架构的无缝升级
定义数据版本
在模块的package.json
文件中配置fileVersion
为当前数据版本
- 1{
- 2 "name": "egg-born-module-test-party",
- 3 "version": "4.0.8",
- 4 "eggBornModule": {
- 5 "fileVersion": 1
- 6 }
- 7}
当模块已经发布后,下次再发生数据架构变更时,
fileVersion
需要递增+1
Bean组件:version.manager
与模块相关的数据架构变更管理,都在Bean组件version.manager
中
- Bean组件定义
src/suite-vendor/test-party/modules/test-party/backend/src/bean/version.manager.js
- 1const VersionTestFn = require('./version/test.js');
- 2
- 3module.exports = app => {
- 4
- 5 class Version extends app.meta.BeanBase {
- 6
- 7 async update(options) {
- 8 // update
- 9 if (options.version === 1) {
- 10 let sql = `
- 11 CREATE TABLE testParty (
- 12 id int(11) NOT NULL AUTO_INCREMENT,
- 13 createdAt timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
- 14 updatedAt timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
- 15 deleted int(11) DEFAULT '0',
- 16 iid int(11) DEFAULT '0',
- 17 atomId int(11) DEFAULT '0',
- 18 personCount int(11) DEFAULT '0',
- 19 partyTypeId int(11) DEFAULT '0',
- 20 PRIMARY KEY (id)
- 21 )
- 22 `;
- 23 await this.ctx.model.query(sql);
- 24
- 25 sql = `
- 26 CREATE TABLE testPartyType (
- 27 id int(11) NOT NULL AUTO_INCREMENT,
- 28 createdAt timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
- 29 updatedAt timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
- 30 deleted int(11) DEFAULT '0',
- 31 iid int(11) DEFAULT '0',
- 32 name varchar(255) DEFAULT NULL,
- 33 PRIMARY KEY (id)
- 34 )
- 35 `;
- 36 await this.ctx.model.query(sql);
- 37
- 38 sql = `
- 39 CREATE VIEW testPartyView as
- 40 select a.*,b.name as partyTypeName from testParty a
- 41 left join testPartyType b on a.partyTypeId=b.id
- 42 `;
- 43 await this.ctx.model.query(sql);
- 44
- 45 }
- 46 }
- 47
- 48 async init(options) {
- 49 // init
- 50 if (options.version === 1) {
- 51 // types
- 52 for (const name of [ 'Birthday', 'Dance', 'Garden' ]) {
- 53 await this.ctx.model.partyType.insert({ name });
- 54 }
- 55 // add role rights
- 56 const roleRights = [
- 57 { roleName: 'system', action: 'create' },
- 58 { roleName: 'system', action: 'read', scopeNames: 'authenticated' },
- 59 { roleName: 'system', action: 'write', scopeNames: 0 },
- 60 { roleName: 'system', action: 'delete', scopeNames: 0 },
- 61 { roleName: 'system', action: 'clone', scopeNames: 0 },
- 62 { roleName: 'system', action: 'deleteBulk' },
- 63 { roleName: 'system', action: 'exportBulk' },
- 64 ];
- 65 await this.ctx.bean.role.addRoleRightBatch({ atomClassName: 'party', roleRights });
- 66 }
- 67
- 68 }
- 69
- 70 async test() {
- 71 const versionTest = new (VersionTestFn(this.ctx))();
- 72 await versionTest.run();
- 73 }
- 74
- 75 }
- 76
- 77 return Version;
- 78};
名称 | 说明 |
---|---|
options.version | 只需针对模块的不同数据版本 编写相应的变更逻辑,系统会根据当前数据版本 自动调用需要升级变更的部分 |
名称 | 说明 |
---|---|
update | 与实例无关 的数据架构变更 |
init | 与实例相关 的数据变更,比如初始化一些内置角色的授权 |
test | 仅在测试环境 执行,向数据库灌入测试用的种子数据,比如为后续的单元测试 提供初始测试数据 或初始角色授权 等 |
update
与init
的区别
- CabloyJS启动一个服务,可以支持多个实例运行。实例共享数据表结构,但运行中产生的数据是相互隔离的
update
处理与实例无关
的数据架构变更,如创建业务数据表testParty
,以及视图、存储过程、函数、索引等一系列数据架构init
处理与实例相关
的数据变更,如添加角色的原子授权
等
- Bean组件注册
src/suite-vendor/test-party/modules/test-party/backend/src/beans.js
- 1const versionManager = require('./bean/version.manager.js');
- 2
- 3module.exports = app => {
- 4 const beans = {
- 5 // version
- 6 'version.manager': {
- 7 mode: 'app',
- 8 bean: versionManager,
- 9 },
- 10 };
- 11 return beans;
- 12};
最佳实践
当已经发布的模块需要再次变更数据架构时,我们需要将模块package.json
中的eggBornModule.fileVersion
递增+1
由于是在开发过程当中,免不了需要不断的修改bean组件version.manager
中的升级逻辑。那么,如何让这些不断修改的数据变更在数据库中生效呢?
有人说打开数据库管理工具进行手工修改。而CabloyJS提供了一种更加便利的方法,只需执行一遍单元测试,就会自动化重建数据库,方法如下:
- 1# 重建数据库 + 单元测试
- 2$ npm run test:backend
- 3# 或者
- 4# 仅重建数据库
- 5$ npm run db:reset
这也是bean组件
version.manager
提供test
方法的意义所在:当执行单元测试的时候,会自动执行test
方法初始化一些测试数据,方便我们测试和开发比如,模块
test-party
就提供了一些测试角色、测试用户、测试权限。当单元测试完成后,数据库里就有了这些基础数据,我们就可以直接进入业务的测试环节,而不是
通过手工来重新输入这些基础数据
延伸阅读
运行单元测试就会自动重建测试数据库,这涉及到CabloyJS一个核心概念:数据库规划
,请与本文参照着阅读:
评论: