1. KMP网络与数据层工程导论

在 Kotlin Multiplatform(KMP)项目中,网络与数据层并不是“把 Android 的 Retrofit 和 Room 搬到公共代码里”这么简单。它位于整个跨平台架构的中段:向上为 Compose Multiplatform、SwiftUI、ArkUI 等各平台 UI 提供统一的数据接口,向下屏蔽 OkHttp、NSURLSession、平台数据库、文件系统与安全存储的差异。理解这一层的定位,是后续所有工程决策的起点。

一、网络与数据层在 KMP 架构中的位置

一个典型的 KMP 工程通常划分为三层:表现层(各平台 UI 与 ViewModel)、领域层(纯 Kotlin 的 UseCase、实体与业务规则)、数据层(网络、缓存、持久化、同步)。数据层再细分为远程数据源(Remote DataSource)、本地数据源(Local DataSource)和仓库(Repository)。Repository 是唯一对领域层暴露的入口,它决定“先读缓存还是先请求网络”“失败时是否回退到本地”“写入后如何触发同步”。

网络与数据层的核心职责可以归纳为四点:统一数据契约(DTO、实体、错误模型在 commonMain 中定义)、平台能力抽象(HTTP 引擎、数据库驱动、键值存储通过 expect/actual 或依赖注入提供)、数据流编排(缓存策略、并发控制、重试与退避)、状态可观测(以 Flow 暴露加载、成功、失败、空数据等状态)。这四点决定了上层 UI 能否用同一套逻辑运行在 Android、iOS、Desktop 甚至 Web 上。

KMP 网络与数据层定位与数据流 表现层 Compose MP (Android) SwiftUI (iOS) Desktop / Web ViewModel / State 领域层 UseCase / 业务规则 Entity / 领域模型 Result / 错误抽象 数据层(commonMain 统一契约) Repository 缓存策略 / 并发控制 重试与退避 Flow 状态暴露 唯一对领域层入口 Remote DataSource Ktor Client / OkHttp DTO 序列化 (kotlinx) 拦截器 / 鉴权 / 日志 超时与错误映射 Local DataSource SQLDelight / Room DataStore / NSUserDefaults 文件与安全存储 事务与迁移 平台层 Android (OkHttp / SQLite) iOS (NSURLSession / CoreData) JVM / JS / Native 请求 缓存

二、核心职责:从“能跑”到“可维护”

很多团队在 KMP 初期会把网络请求直接写在 ViewModel 里,结果 iOS 端复用困难、错误处理各写一套。正确的做法是让数据层承担以下职责:

  • 统一数据契约:DTO 使用 @Serializable 标注,字段命名与后端 JSON 对齐;领域实体在领域层定义,Repository 负责 DTO 到 Entity 的映射。这样后端字段变更只影响映射层,不会穿透到 UI。
  • 平台能力抽象:HTTP 引擎通过 expect fun createHttpClient(): HttpClient 声明,Android 用 OkHttp,iOS 用 Darwin,Desktop 用 CIO。数据库驱动同理,SQLDelight 为每个平台生成不同的 Driver。
  • 数据流编排:Repository 返回 Flow<Resource<T>>,其中 Resource 封装 Loading、Success、Error 三种状态。UI 只订阅这个 Flow,不关心数据来自网络还是缓存。
  • 可观测与可测试:数据层不依赖 Android Context 或 iOS UIViewController,可以在 commonTest 中用 MockEngine 和内存数据库完成单元测试。

三、技术选型全景

KMP 网络与数据层的选型需要同时考虑“跨平台一致性”和“平台性能”。下表给出一组常见组合及其适用场景,参数为示例性建议,实际项目需根据团队规模与业务复杂度调整。

层次 推荐方案 备选方案 关键考量
HTTP 客户端 Ktor Client OkHttp + 平台封装 Ktor 在 commonMain 中统一 API,拦截器、序列化、超时配置一次编写多端复用
序列化 kotlinx.serialization Moshi / Gson(仅 Android) 编译期生成序列化器,无反射,适合 Native 与 JS
本地数据库 SQLDelight Room KMP / Realm SQLDelight 从 .sq 文件生成类型安全 Kotlin API,支持多平台 Driver
键值存储 Multiplatform Settings DataStore(Android)/ NSUserDefaults(iOS) 轻量配置、Token、开关等小数据优先用键值存储
依赖注入 Koin 手动构造 / kotlin-inject Koin 在 KMP 中配置简单,适合中小团队快速落地
异步与流 Coroutines + Flow RxJava(不推荐新项目) Flow 与 Compose、SwiftUI 的桥接方案成熟

四、学习路线图

本课程后续章节将沿着“契约 → 实现 → 优化 → 验证”的路径展开。建议按以下顺序推进:

  1. 基础契约:先定义 DTO、Entity、Resource 与错误模型,确保 commonMain 中不出现平台类型。
  2. 网络实现:用 Ktor Client 搭建请求管线,配置 ContentNegotiation、Logging、Auth 拦截器与超时。
  3. 本地持久化:引入 SQLDelight,设计表结构与查询,处理事务、迁移与并发读写。
  4. 仓库编排:实现“网络优先 + 缓存回退”“缓存优先 + 后台刷新”等策略,用 Flow 暴露状态。
  5. 同步与冲突:处理离线写入、增量同步、版本冲突与幂等重试。
  6. 测试与观测:用 MockEngine、内存数据库和 Turbine 验证数据流,接入日志与性能指标。
实践建议:在第一个 KMP 模块中,先只实现一个 Repository 和一个 RemoteDataSource,跑通 Android 与 iOS 两端。确认 DTO 序列化、错误映射、Flow 收集都正常后,再逐步加入本地缓存与同步逻辑。不要一开始就追求“全平台全功能”,否则调试成本会迅速超过收益。
常见误区:把平台相关代码(如 Android 的 Context、iOS 的 NSURLSession 回调)直接写进 commonMain。这会导致 iOS 编译失败或行为不一致。正确做法是通过 expect/actual 或依赖注入在平台侧提供实现,commonMain 只依赖接口。

数据层的质量直接决定 KMP 项目的可维护性。一个清晰的 Repository 边界、一套统一的错误模型、一条可观测的数据流,能让后续的 UI 开发、测试和排障都变得可控。接下来的章节将从最基础的 DTO 与 Entity 设计开始,逐步构建完整的网络与数据层。