第3章:Room实例设计 — 单实例 vs 多实例方案权衡与决策拓扑

3.1 本章核心矛盾

跨模块数据同步的设计起点,是“数据库实例的共享范围”。选择错误将直接导致数据不一致、编译依赖膨胀或运行时崩溃。核心矛盾在于:
跨模块数据一致性 要求共享数据库实例,以利用 SQLite 的事务隔离与外键约束。
模块编译/运行隔离性 要求各自持有独立实例,避免模块 A 的 schema 变更破坏模块 B。

3.2 方案一:单实例(SharedDatabase)

边界定义:整个 App 进程内只存在一个 RoomDatabase 对象,通过依赖注入(如 Dagger/Hilt)统一提供。
  • 模块职责:所有模块的 DAO 操作同一个物理 SQLite 文件,事务对外可见。
  • 数据流/控制流:
    • 数据流:ModuleA → DAO_A → SharedDatabase → 磁盘 → SharedDatabase → DAO_B → ModuleB
    • 控制流:依赖注入框架实例化 SharedDatabase,各模块通过构造函数注入获取 DAO 实例。
  • 信任链路:编译时信任 —— 各模块必须在同一 Gradle 模块层级,且共同持有 `@Database` 注解定义;运行时信任 —— 所有 DAO 由同一实例创建,不存在状态分裂。
  • 适用场景:模块间需要强一致性(如用户画像+行为上报)、共享外键约束、跨模块事务。

3.3 方案二:多实例(SeparateDatabases)

推荐场景:各模块业务边界清晰,数据无强关联,且需要独立发布、独立演进。
  • 边界定义:每个功能模块持有自己的 RoomDatabase 实例,存放在模块私有路径或专有名称下。
  • 模块职责:各自管理 schema 迁移、备份、清理,完全自治。
  • 数据流/控制流:
    • 数据流:ModuleA → DAO_A → DB_A;ModuleB → DAO_B → DB_B。
    • 跨模块数据同步依赖上层 Coordinator 或 ContentProvider 桥接(见第5章)。
  • 信任链路:运行时协商 —— 每次同步需校验数据格式、版本号,无法利用数据库层的事务隔离。
  • 风险点:数据冗余、同步延迟、无外键保护。

3.4 跨模块共享数据库的边界原则

维度单实例(SharedDatabase)多实例(SeparateDatabases)
隔离性与一致性强一致,事务可见最终一致,需额外同步机制
编译依赖高耦合,所有模块需知完整 schema低耦合,模块独立编译
运行时性能单点写入串行化,大模块可能阻塞并发写入无锁冲突
数据迁移统一 migration,一人决策各自管理,易出现版本不兼容
信任边界编译时契约运行时协同(版本、格式校验)

3.5 实例拓扑决策树

以下决策树概括了从业务需求到实例拓扑的完整分支路径。

Room实例设计决策树 跨模块需要数据一致性? 单实例 (SharedDB) 模块A/B/C 共享 Room.Builder 统一 Migration 与 MPP 跨进程?→ ContentProvider 同进程直接注入 DAO 多实例 (SeparateDB) 模块各自持有独立 .db 数据同步依赖 BridgeDB 信任边界:版本+格式校验 需额外 Coordinator 层 决策出口 • 强一致/外键/事务→单实例 • 模块自治/低耦合/独立发布→多实例

3.6 风险点与落地步骤

风险点说明缓解措施
数据污染冲突单实例下模块A误写脏数据影响模块BDAO 层封装校验逻辑、schema reviewer 机制
耦合度过高单实例导致所有模块必须同步编译引入 API 模块暴露 DAO 接口,隐藏实现
版本分裂多实例下各模块 schema 版本不一致建立版本清单表、编译时检查
事务死锁单实例多模块竞争同一写事务使用 @Transaction 隔离、WAL 模式
架构决策警示:不要为了“未来模块化”盲目选择多实例;也不要为了“简单”强行共享实例。决策树的根节点必须是业务对“数据一致性”的实时性要求,而非开发者的惯性偏好。

3.7 验收标准

  1. 编译隔离性:模块 B 在未依赖模块 A 的情况下,可通过自己定义的 DAO 接口完成数据查询。
  2. 边界清晰:每个模块的 @Database 注解文件只包含本模块的 entity 和 DAO,不引入无关类。
  3. 同步可测:对于多实例场景,可独立编写单测验证跨模块数据同步逻辑(mock 对端 DB 层)。
  4. 版本独占:任何模块升级 schema 不影响全局构建,除非该模块处于单实例共享路径下。