分支管理
RxDB 提供类似 Git 的分支管理能力,让你可以在本地创建多个数据分支,安全地进行实验性修改,然后选择性地合并或切换分支。
核心概念
- 主分支(main):默认分支,记录稳定的生产数据
- 开发分支:用于测试和实验的临时分支
- 变更记录(RxDBChange):通过数据库触发器自动记录所有数据变更
- 分支切换:在不同数据状态之间切换,基于变更历史实现状态迁移
快速开始
访问版本管理器
import { RxDB, SyncType } from '@aiao/rxdb';
// 创建 RxDB 实例后,可以通过 versionManager 访问分支管理功能
const rxdb = new RxDB({
dbName: 'myapp',
entities: [Todo],
sync: {
local: { adapter: 'sqlite' },
type: SyncType.None
}
});
// 版本管理器提供了分支操作的所有方法
const { versionManager } = rxdb;
分支管理 API
创建分支
创建一个新的数据分支,可以选择从指定的变更点(changeId)创建:
// 从当前状态创建新分支
await rxdb.versionManager.createBranch('feature-1');
// 从指定变更点创建分支(用于从历史状态创建分支)
await rxdb.versionManager.createBranch('feature-2', 12345);
切换分支
切换到不同的分支,系统会自动应用所需的数据变更:
// 切换到指定分支
await rxdb.versionManager.switchBranch('feature-1');
// 切换回主分支
await rxdb.versionManager.switchBranch('main');
切换分支时发生的操作:
- 关闭数据库触发器
- 执行当前分支的逆向变更(
inversePatch)回退到共同祖先 - 应用目标分支的正向变更(
patch)到最新状态 - 更新分支激活状态
- 重新生成触发器(使用新的分支ID)
- 启用触发器
删除分支
删除不再需要的分支:
// 删除分支(会清理相关的变更记录)
await rxdb.versionManager.removeBranch('feature-1');
注意
删除分支会同时删除该分支的所有变更历史,操作不可恢复。
获取当前分支
查询当前激活的分支:
const currentBranch = await rxdb.versionManager.getCurrentBranch();
console.log('当前分支:', currentBranch.id);
console.log('是否激活:', currentBranch.activated);
实际应用场景
场景 1:测试新功能
// 1. 创建测试分支
await rxdb.versionManager.createBranch('test-feature');
await rxdb.versionManager.switchBranch('test-feature');
// 2. 在测试分支上修改数据
const todo = new Todo();
todo.title = '测试任务';
await todo.save();
// 3. 测试通过,切换回主分支
await rxdb.versionManager.switchBranch('main');
// 4. 不需要测试数据,删除测试分支
await rxdb.versionManager.removeBranch('test-feature');
场景 2:离线编辑与同步
// 1. 创建离线工作分支
await rxdb.versionManager.createBranch('offline-work');
await rxdb.versionManager.switchBranch('offline-work');
// 2. 进行离线修改...
// (用户在没有网络的情况下工作)
// 3. 恢复网络后,切换回主分支
await rxdb.versionManager.switchBranch('main');
// 4. 可选:将离线分支的修改合并到主分支(未来功能)
// await rxdb.versionManager.merge('offline-work', 'main');
场景 3:数据快照与回滚
// 1. 在重要操作前创建快照分支
const changeId = await rxdb.versionManager.getCurrentChangeId();
await rxdb.versionManager.createBranch('snapshot-before-import', changeId);
// 2. 执行批量导入操作
await importLargeDataset();
// 3. 如果出现问题,切换回快照分支
if (hasErrors) {
await rxdb.versionManager.switchBranch('snapshot-before-import');
}
系统表结构
系统表 RxDBChange 会通过数据库触发器(Trigger) 记录所有数据库的数据变化(插入,修改,删除),RxDBBranch 表会记录当前分支,这样我们就可以通过历史记录来计算出任何时刻的数据,再去修改其他表的数据达到切换版本的需求。
字段说明
RxDBBranch 表
id: 分支唯一标识符activated: 分支是否激活(同时只有一个分支激活)local: 是否为本地分支remote: 是否已同步到远程
RxDBChange 表
id: 变更记录唯一标识符(自增)branchId: 所属分支IDnamespace: 命名空间entity: 实体名称entityId: 实体IDtype: 变更类型(insert、update、delete)patch: 正向变更数据(JSON Patch 格式)inversePatch: 逆向变更数据(用于回滚)revertChangedAt: 撤销时间(用于 undo/redo)revertChangeId: 撤销时的变更IDtransactionId: 事务ID(同一事务的多个变更共享)createdAt: 创建时间updatedAt: 更新时间
分支合并(规划中)
你可以随时/实时 pull、push,如果遇到数据冲突需要像 Git 一样合并冲突。
直接合并
将源分支的所有变更直接应用到目标分支:
// 将 feature-1 分支合并到 main 分支
// await rxdb.versionManager.merge('feature-1', 'main');
压缩合并
将多个提交压缩成一个提交再合并:
// 压缩合并,保持历史简洁
// await rxdb.versionManager.merge('feature-1', 'main', { squash: true });
分支删除
合并后可以删除源分支:
// 合并并删除源分支
// await rxdb.versionManager.merge('feature-1', 'main', { deleteBranch: true });
最佳实践
1. 合理命名分支
使用描述性的分支名称:
// ✅ 好的命名
await rxdb.versionManager.createBranch('feature-user-profile');
await rxdb.versionManager.createBranch('bugfix-todo-delete');
await rxdb.versionManager.createBranch('experiment-new-ui');
// ❌ 不好的命名
await rxdb.versionManager.createBranch('test');
await rxdb.versionManager.createBranch('branch1');
2. 及时清理分支
定期清理不再需要的分支:
// 列出所有分支并清理
const branches = await rxdb.versionManager.listBranches();
for (const branch of branches) {
if (branch.id !== 'main' && !branch.activated) {
// 检查是否需要保留
const shouldKeep = await checkBranchStatus(branch);
if (!shouldKeep) {
await rxdb.versionManager.removeBranch(branch.id);
}
}
}
3. 使用快照保护重要操作
在执行危险操作前创建快照:
// 重要操作前的保护机制
async function safeOperation(operation: () => Promise<void>) {
const timestamp = Date.now();
const snapshotBranch = `snapshot-${timestamp}`;
try {
// 创建快照
await rxdb.versionManager.createBranch(snapshotBranch);
// 执行操作
await operation();
// 操作成功,删除快照
await rxdb.versionManager.removeBranch(snapshotBranch);
} catch (error) {
// 操作失败,回滚到快照
await rxdb.versionManager.switchBranch(snapshotBranch);
throw error;
}
}
4. 分支隔离实验
使用分支进行 A/B 测试或实验性功能:
// 创建实验分支
await rxdb.versionManager.createBranch('experiment-a');
await rxdb.versionManager.createBranch('experiment-b');
// 在不同分支测试不同方案
await rxdb.versionManager.switchBranch('experiment-a');
await testFeatureA();
await rxdb.versionManager.switchBranch('experiment-b');
await testFeatureB();
// 根据结果选择保留哪个分支
await rxdb.versionManager.switchBranch('main');
if (resultsA.isBetter()) {
// 未来支持:await rxdb.versionManager.merge('experiment-a', 'main');
}
注意事项
- 分支切换是数据库级别的操作:切换分支会影响所有实体的数据
- 触发器管理:分支切换时会自动管理触发器的启用/禁用
- 性能考虑:大量变更的分支切换可能需要较长时间
- 数据一致性:确保在分支切换前保存所有待处理的数据
- 并发控制:避免在分支切换过程中进行其他数据操作