数据层实现:本地数据库与网络数据源
数据层是 Repository 模式离线分层架构中直接与数据持久化和远程通信交互的边界。它屏蔽数据来源的物理差异,向上层 Repository 提供统一的数据访问接口。本章聚焦本地数据库(SQLite 与 Room)、网络 API 适配器以及数据源切换机制,阐述组件职责、选型权衡、落地方案与验收标准。
组件边界与职责
| 组件 | 职责 | 所属位置 |
|---|---|---|
| LocalDataSource | 封装本地数据库操作,提供离线缓存、持久化存储 | 数据层内(本地侧) |
| RemoteDataSource | 封装网络 API 调用,负责远程数据获取与提交 | 数据层内(网络侧) |
| DataSourceSwitcher | 根据离线策略(如网络可用性、缓存时效)选择数据源 | 数据层内(调度中心) |
| Repository | 组合本地与远程数据源,对外暴露纯净数据接口 | 数据层之上(领域层) |
本地数据库选型:SQLite vs. Room
| 维度 | 原生 SQLite | Room(Android官方ORM) |
|---|---|---|
| 抽象层级 | 直接操作 SQL,低层 API | 注解驱动,自动生成 DAO 与编译时验证 |
| 开发效率 | 低,需手写 SQL 与 cursor 解析 | 高,通过 Entity/DAO 快速映射 |
| 数据库迁移 | 手动版本管理,易出错 | 内置 migration 机制,自动升级 |
| 测试友好度 | 需 mock SQLiteDatabase | 支持 standalone 测试,MemoryDatabase |
| 推荐场景 | 极简嵌入式、已有 C 库复用 | Android 平台标准离线分层 |
架构建议:在离线分层架构中优先采用 Room。它天然适配 Repository 模式,编译时校验 SQL 语句,并提供 Flow/LiveData 响应式查询,减少数据同步样板代码。
网络 API 适配器设计
网络 API 适配器(RemoteDataSource)是数据层与外部服务之间的适配边界。常见实现使用 Retrofit + OkHttp,职责包括:
- 封装 RESTful 接口定义,返回 DataResult<T> 统一类型
- 处理连接超时、HTTP 错误码、序列化异常
- 提供限流、重试(指数退避)、Cookie 同步等可插拔组件
- 对接上游 DataSourceSwitcher,反馈网络状态(如 ConnectivityManager 监听)
数据源切换机制
数据源切换是离线分层架构的核心控制流。典型策略包含:
- 先本地后远程(Cache-First):每次请求优先查询本地数据库,若数据不满足时效或缺失,则调用网络同步后更新缓存。
- 仅远程(Network-Only):强制走网络,本地仅作失败降级(Offline-Fallback)。
- 仅本地(Offline-Mode):完全依赖本地数据库,适用于飞行模式或用户设置离线状态。
风险点:切换过程中需要处理并发竞争(网络回调与本地写入同时发生)和数据一致性(过期数据覆盖新数据)。建议在 DataSourceSwitcher 内使用乐观锁或版本号字段,避免脏写。
数据流与控制流:信任链路
下图展示数据层内部组件间的调用关系与数据流向(图中省略外部网络细节)。
落地步骤
- 定义数据源接口:
interface IDataSource<T> { suspend fun get(key: String): Result<T> suspend fun put(key: String, value: T): Result<Unit> suspend fun remove(key: String): Result<Unit> } - 实现 LocalDataSource:基于 Room 的 DAO,在 Entity 中添加 version 字段用于乐观锁。
- 实现 RemoteDataSource:使用 Retrofit 定义 ApiService,返回 Result 类型并映射异常。
- 构建 DataSourceSwitcher:持有 LocalDataSource 与 RemoteDataSource 引用,依据网络状态和缓存策略选择数据源。策略可配置为策略模式枚举。
- 注入 Repository:Repository 只依赖 DataSourceSwitcher,由依赖注入框架(如 Hilt)提供。
选型权衡
| 决策点 | 选项 | 权衡 |
|---|---|---|
| 网络库 | Retrofit vs. Ktor Client | Retrofit 成熟生态好;Ktor 协程原生更轻量,但适配 Android 时需注意线程切换 |
| 序列化 | Moshi vs. Gson vs. Kotlin Serialization | Moshi 性能好且 Kotlin 空安全;Kotlin Serialization 编译时生成,无反射开销 |
| 离线缓存策略 | Write-Through / Write-Back / Cache-Aside | Write-Through 强一致性但写入慢;Cache-Aside 常见于读过期场景;Write-Back 风险大 |
验收标准
- 本地数据库操作(增删改查)在无网络环境下正常返回数据,且符合缓存时效逻辑。
- 网络 API 适配器能正确处理超时(10s)、400/500 状态码,并统一转换为 DataResult.Error。
- 数据源切换器在模拟飞行模式后自动降级至本地,恢复网络后能触发增量同步。
- 并发写入测试(多次直接修改同一记录)最终一致且不丢失数据(基于 version 冲突检测)。
风险点小结:数据库迁移失败导致数据丢失;网络切换期间缓存与远程数据不一致;Room 的 Flow 与远程流合并时背压处理不当导致 OOM。建议在架构早期加入 version 版号控制和数据一致性单元测试。