第4章:数据仓库分层架构——Repository层、DataSource层、同步层职责
4.1 概述:三层职责边界
跨模块数据同步的核心挑战在于如何解耦业务模块与数据持久化细节,同时保证数据在多个模块间的一致性和可追踪性。本章提出的分层方案将数据仓库划分为三个职责层:Repository层(数据访问入口)、DataSource层(数据源适配与格式转换)、同步层(冲突裁定与合并策略)。每一层都有严格的边界和信任链路,避免跨层调用和职责扩散。
| 维度 | DataSource层 | Repository层 | 同步层 |
|---|---|---|---|
| 角色 | 数据源的适配器 | 业务模块的数据门面 | 数据一致性的仲裁者 |
| 主入口 | Room DAO、Retrofit Service、File I/O | Kotlin 接口(如 UserRepository) | SyncManager |
| 核心职责 | 将原始数据转换为统一数据模型(DTO) | 聚合多个DataSource,提供业务需要的领域模型 | 检测数据版本冲突,执行合并策略 |
| 典型操作 | insert/query/delete + 网络请求 | getUser(), saveOrder() | resolveConflict(), merge() |
| 模块边界 | 不包含业务逻辑,纯数据管道 | 不直接操作数据库或网络 | 只处理冲突域内的数据差异 |
| 信任关系 | 提供幂等且类型安全的接口 | 缓存策略与数据失效由Repository管理 | 信任链路通过版本号与时间戳验证 |
4.2 边界划分与模块职责
4.2.1 DataSource层:数据源的统一适配
DataSource层的职责是将不同来源(本地Room、远程API、文件缓存)的数据封装成统一的数据对象,对外暴露无业务语义的CRUD操作。每一类数据源都对应一个独立的DataSource接口,使Repository层可以基于接口编程而非具体实现。
- 本地DataSource:封装Room DAO,负责将Entity与DTO互相转换,确保数据完整性约束。
- 远程DataSource:封装Retrofit或Ktor,负责网络请求与响应解析,处理HTTP错误码和重试。
- 缓存DataSource:管理内存缓存或磁盘缓存,提供预加载能力,降低对远程源的依赖。
DataSourcce层的核心原则:接口返回值必须是明确的数据类型,不允许在DataSource层抛出业务异常或执行合并逻辑。所有错误应转换为数据流中的空值或特殊状态。
4.2.2 Repository层:业务数据的门面与聚合器
Repository层是业务模块唯一可见的数据入口。它负责从多个DataSource中获取数据,执行格式转换(DTO → Domain Model),并决定缓存策略和刷新时机。Repository不应直接访问数据库或网络,所有操作都通过DataSource接口完成。
- 数据聚合:当业务需要组合本地未同步数据与远程最新数据时,Repository负责编排请求顺序。
- 缓存失效:定义数据时效性规则,例如本地数据超过5分钟则触发同步层刷新。
- 单元测试友好:Repository仅依赖DataSource接口,可轻易替换为FakeDataSource进行测试。
Repository层容易出现的反模式:将同步逻辑直接写在Repository中,导致数据获取与冲突解决耦合。同步逻辑必须下沉至同步层。
4.2.3 同步层:冲突检测与合并仲裁
同步层是跨模块数据一致性的核心。它既不负责数据存储,也不负责业务逻辑,只关注数据版本差异的检测和裁定。同步层通过监听Repository层提交的数据变更事件,捕获冲突域内的数据对(本地版本vs远程版本),执行预设的合并策略。
- 冲突检测:基于版本号(version)或时间戳(lastModifiedAt)对比数据是否被其他模块修改。
- 合并策略:常用的有“本地优先”、“远程覆盖”、“最后写入胜利(LWW)”、“自定义合并(如字段级别合并)”。
- 状态通知:合并结果通过LiveData/Flow通知Repository层刷新数据。
4.3 数据流/控制流/信任链路
数据流从业务模块发起,经Repository层调用DataSource层获取原始数据,再通过同步层检测数据一致性后返回给业务层。控制流遵循单向依赖:业务层 → Repository层 → DataSource层 / 同步层。信任链路建立在层间的接口契约之上,每一层只信任下一层的接口返回值,不信任实现细节。
// 信任链路示例:Repository获取用户信息
fun getUser(userId: String): Flow {
return localDataSource.getUser(userId)
.map { dto -> dto.toDomain() }
.combine(remoteDataSource.getUser(userId)) { localDto, remoteDto ->
// 同步层介入比对版本
syncManager.resolve(localDto, remoteDto)
}
.map { resolved -> resolved.toDomain() }
}
信任链路强调数据类型的可溯源:从DataSource返回的DTO,到Repository转换的Domain,再到同步层处理的SyncModel,每一层都根据上一层的格式进行转换,不跳级操作。
4.4 选型权衡:三层分离的代价与收益
| 选型点 | 收益 | 代价 |
|---|---|---|
| Repository层与DataSource层严格分离 | 数据源可替换、业务代码不依赖具体DB实现 | 增加接口数量和类的层级 |
| 同步层独立为出组件 | 冲突策略可单独测试和升级 | 引入状态管理复杂度(内存开销、事件驱动) |
| 同步层直接操作DataSource | 减少Repository的代理调用,提高性能 | 破坏单向依赖,同步层可能绕过Repository的缓存策略 |
| 使用Flow实现数据流 | 自然支持异步和协程,便于组合 | 学习曲线较RxJava略高 |
4.5 风险点与应对策略
- 风险1:Repository层过胖 —— 如果Repository同时负责多数据源聚合和缓存失效,容易变成“上帝类”。
对策:单一职责原则,一个Repository只管理一种聚合根数据。 - 风险2:DataSource层穿透 —— 开发者跨过Repository直接调用DataSource,导致同步逻辑失效。
对策:将DataSource接口设为internal或module-private,只通过Repository暴露给外部。 - 风险3:同步层死锁 —— 多个模块同时提交数据变更时,同步层可能会陷入循环合并。
对策:设置最大重试次数和版本号阈值,超出后放弃合并并通知业务层。
4.6 落地步骤:从接口定义到分层治理
- 定义DataSourcce接口:为本地和远程数据源分别编写接口,DTO为不可变数据类。
- 实现数据源适配器:在对应的模块内实现Room DAO和Retrofit Service,保证接口幂等。
- 构建同步引擎:实现SyncManager,包含冲突检测和合并策略的注册机制。
- 封装Repository:编写业务模块的Repository实现,注入多个DataSource和SyncManager。
- 测试信任链路:验证Repository→DataSource→同步层的数据流是否正确,类型转换是否安全。
4.7 验收标准:分层是否达标
- 单元测试可隔离:单独测试Repository时,不需要启动Room或网络服务。
- 数据源切换模拟:将本地DataSource切换为内存FakeDataSource,业务模块功能不受影响。
- 冲突模拟注入:在同步层单元测试中,手动制造版本冲突,合并策略可正确触发。
- 无跨层调用:代码审查时未发现Repository、DataSource、同步层之间的直接交叉引用。