DAO、Repository、DataSource 三层边界定义:职责分离与协作关系
在 Room 持久化架构中,将数据访问逻辑拆分为 DataSource(数据源层)、DAO(数据访问对象层)与 Repository(仓储层)三个明确的边界,是控制复杂性与应对需求变化的核心手段。本章定义每一层的职责、边界契约与协作方式,为后续的落地实践奠定理论基础。
核心原则:每一层只关注一个抽象级,下层对上层无感知,上层通过接口依赖下层。信任链路自顶向下传递,控制流自底向上收敛。
2.1 三层职责与边界矩阵
| 层次 | 英文名称 | 核心职责 | 依赖方向 | 典型实现载体 |
|---|---|---|---|---|
| DataSource | 数据源层 | 封装原始数据源的连接、事务、表结构映射;不包含业务逻辑。 | 无下层依赖(底层) | RoomDatabase、网络 API 客户端、本地文件 |
| DAO | 数据访问对象层 | 定义原子数据操作(CRUD),处理查询参数映射与结果集转换;不可感知外层业务。 | 依赖 DataSource(Room 数据库实例) | @Dao 注解接口、Ktor 请求封装 |
| Repository | 仓储层 | 聚合多个 DAO / 数据源,编排完整业务单元(如「先插入缓存再返回远程」),对外暴露领域级接口。 | 依赖 DAO 接口(或抽象数据源) | UserRepositoryImpl 类 |
关键认知:Repository 不直接操作数据库,DAO 不感知调用方身份,DataSource 不负责数据组合或缓存策略。每一层只拥有完成自身职责所需的最小知识集。
2.2 数据流与控制流的信任链路
数据流(实线)自 DataSource → DAO → Repository → ViewModel。控制流(虚线)反向,每次调用都携带清晰的边界约束:
- DAO 层保证原子性:一次查询要么完全成功,要么回滚;
- Repository 层保证业务完整性:跨 DAO 调用必须处理异常和一致性;
- DataSource 层保证物理连接生命周期:由 Room 或网络库管理,上层不关心具体连接池。
2.3 选型权衡:为什么必须是三层?
业界常见误区是将 DAO 与 Repository 合并为一个“大 DAO”层,或让 Repository 直接操纵 RoomDatabase。以下对比说明三层分离的必要性:
| 架构形态 | 优点 | 风险点 | 适用场景 |
|---|---|---|---|
| 双层(DAO + 业务层) | 代码量少,快速原型 | 业务逻辑混入 DAO,单元测试困难,更换数据源需重写业务 | 小型工具类 App |
| 三层(Datasource + DAO + Repository) | 职责清晰,测试隔离,替换数据源只需改 DAO 实现 | 初期搭建成本略高,需设计泛型或接口抽象 | 中大型产品级 App |
| 多层过度抽象 | 理论上模块化更强 | 显著增加认知负担,调试链路过长,开发效率下降 | 不推荐 |
风险提示:当业务方要求“一个查询增加排序字段”时,如果修改了 DAO 签名,将导致所有调用方(即使只是 Repository)重新编译。因此需提前约定 DAO 接口返回不可变集合或 Flow,并使用参数对象而非散列参数来隔离高频变动。
2.4 落地步骤
- 定义 DataSource 契约(如 RoomDatabase 抽象类)—— 只声明数据库表结构与版本迁移策略。
- 编写 DAO 接口 —— 每个实体对应一个 DAO,只包含基础的增删改查方法,尽量不使用 suspend 或 Flow 之外的协程上下文。
- 实现 Repository 类 —— 注入 DAO 接口,在方法内编排跨 DAO 逻辑或缓存策略。
- 测试验证 —— 使用 FakeDataSource 模拟 DAO 行为,验证 Repository 逻辑;使用 Room 的内存数据库验证 DAO 查询正确性。
2.5 验收标准
- 每个 DAO 接口的方法能够独立通过单元测试,不依赖任何 Repository。
- Repository 单元测试中,可以完全替换 DAO 为 Mock 或 Fake。
- DataSource 层不包含任何业务相关的判断、循环或字符串拼接。
- 修改数据库表结构(如新增字段)时,只需修改对应实体类 + DAO 方法,无需修改任何 Repository 或 ViewModel。