VonaJS多租戶同時支持共享模式和獨立模式
多實例/多租戶
VonaJS 通過多實例的概念來支持多租戶 SAAS 系統的開發。只需啟動一個后端服務,即可支持多個實例同時運行
VonaJS 支持以下幾種多實例/多租戶模式:
共享模式:多個實例共享同一個數據庫,通過實例Id字段隔離多實例之間的數據獨立模式:每個實例都使用獨立的數據庫,從而滿足大數據量的業務需求混合模式:在一個系統中同時支持共享模式和獨立模式,從而可以精確指定某個實例使用共享數據庫還是獨立數據庫
實例配置
1. 測試環境、開發環境
在測試環境和開發環境中,系統默認提供了一個缺省實例。同時提供了兩個測試實例,用于演示如何使用共享模式和獨立模式:
src/backend/config/config/config.test.ts
src/backend/config/config/config.dev.ts
// instances
config.instances = [
{ name: '', password: '', title: '', config: {} },
{ name: 'shareTest', password: '', title: '' },
{ name: 'isolateTest', password: '', title: '', id: 1000, isolate: true, isolateClient: 'isolateTest' },
];
- 實例清單
| 名稱 | 說明 |
|---|---|
| empty | 缺省實例 |
| shareTest | 用于演示共享模式,具體而言,shareTest與empty共享同一個數據庫 |
| isolateTest | 用于演示獨立模式,具體而言,isolateTest使用獨立的數據庫 |
- 實例屬性
| 名稱 | 說明 |
|---|---|
| name | 實例名 |
| password | 實例中用戶admin的初始密碼,默認是123456 |
| title | 網站標題 |
| config | 實例的配置信息 |
| id | 當使用獨立模式時,必須明確指定唯一的實例Id |
| isolate | 是否使用獨立模式,默認為共享模式 |
| isolateClient | 當使用獨立模式時,必須明確指定數據源 |
2. 生產環境
在生產環境,需要自行配置實例信息
src/backend/config/config/config.prod.ts
config.instances = [
{ name: '', password: '', title: '', config: {} },
{ name: 'vona', password: '', title: '', config: {} },
];
如何添加新實例
下面以實例shareTest為例,演示如何添加新實例:
1. 添加類型定義
src/backend/config/config/config.ts
declare module 'vona' {
export interface IInstanceRecord {
shareTest: never;
}
}
- 采用接口合并機制添加新實例的類型定義
2. 增加實例配置
在需要的 config 文件中添加實例配置,比如在測試環境配置新實例:
src/backend/config/config/config.test.ts
// instances
config.instances = [
{ name: 'shareTest', password: '', title: '' },
];
- 對于
獨立模式,還需要配置數據源,此處從略
獲取當前實例名的規則
當用戶訪問后端 Api 時,后端會自動根據規則獲取當前實例名,然后根據實例名獲取實例信息
1. 模塊配置
多實例是由模塊 a-instance 提供的核心能力,可以在 App config 中修改模塊的配置:
src/backend/config/config/config.prod.ts
// modules
config.modules = {
'a-instance': {
getInstanceName: undefined,
headerField: 'x-vona-instance-name',
queryField: 'x-vona-instance-name',
},
};
| 名稱 | 說明 |
|---|---|
| getInstanceName | 提供自定義函數,用于獲取當前實例名 |
| headerField | 從request header中獲取當前實例名,header key默認為x-vona-instance-name |
| queryField | 從request query中獲取當前實例名,query key默認為x-vona-instance-name |
2. 規則次序
系統按以下次序,依次判斷當前實例名,當獲取到實例名時則停止判斷流程
- 如果提供了
getInstanceName,則調用此函數 - 如果
queryField不為空,則從 request query 中獲取 - 如果
headerField不為空,則從 request header 中獲取 - 從域名中解析實例名
3. 如何從域名中解析實例名
比如,域名為https://cabloy.com,那么對應的實例名是cabloy。可以通過配置SERVER_SUBDOMAINOFFSET來修改計算規則
env/.env
# server
SERVER_SUBDOMAINOFFSET = 1
- 當
SERVER_SUBDOMAINOFFSET = 1時,域名與實例名對應關系如下:
| 域名 | 實例名 |
|---|---|
| cabloy.com | cabloy |
| store.cabloy.com | cabloy.store |
- 當
SERVER_SUBDOMAINOFFSET = 2時,域名與實例名對應關系如下:
| 域名 | 實例名 |
|---|---|
| cabloy.com | 空字符串 |
| store.cabloy.com | store |
使用多實例
1. 訪問當前實例信息
// 當前實例名
const name = this.ctx.instanceName;
// 當前實例對象
const instance = this.ctx.instance;
// 當前實例Id
const iid = this.ctx.instance.id;
2. 使用Model操作數據庫
由于多實例的數據是相互隔離的,因此在操作數據庫時,需要指定實例Id。VonaJS 提供了非常強大的Model對象,從而可以透明的處理多實例
// create
await this.scope.model.student.insert({ name: 'Tom' });
// select
await this.scope.model.student.select();
// get
await this.scope.model.student.get({ id: 1 });
// update
await this.scope.model.student.update({ id: 1, name: 'Jimmy' });
// delete
await this.scope.model.student.delete({ id: 1 });
當我們使用 Model student操作數據時,系統會自動設置實例Id
3. 使用Query Builder操作數據庫
如果使用builder()方法操作數據庫,就需要自行添加實例Id
await this.scope.model.student.builder().where({
iid: this.ctx.instance.id,
name: 'Tom',
});
如果使用builderSelect()方法操作數據庫,系統會自動添加實例Id
await this.scope.model.student.builderSelect().where({
name: 'Tom',
});
4. 使用原生Sql操作數據庫
如果使用原生Sql操作數據庫,就需要自行添加實例Id
await this.scope.model.student.query(
'select * from demoStudent where iid=?',
[this.ctx.instance.id],
);
await this.scope.model.student.queryOne(
'select * from demoStudent where iid=? and id=?',
[this.ctx.instance.id, 1],
);
Vona ORM已開源:https://github.com/vonajs/vona

VonaJS 通過多實例的概念來支持多租戶 SAAS 系統的開發。只需啟動一個后端服務,即可支持多個實例同時運行。同時支持共享模式和獨立模式。
浙公網安備 33010602011771號