第4章 分层数据流架构:UI层→ViewModel层→Domain层→Data层,反向数据流驱动
在 KMP + Compose 的状态共享实践中,反向数据流(Unidirectional Data Flow,UDF)是核心原则。本层架构将系统划分为四个职责清晰的层次,强制事件向上传递、状态向下流动,从而形成可预测、可测试、可追踪的状态变更链路。反向数据流并非技术选项,而是架构纪律。
核心认知:每一层只信任下层提供的稳定状态,下层不信任上层的业务判断。信任链路沿 Data → Domain → ViewModel → UI 单向建立,反向只能通过事件(Event)驱动。
4.1 边界划分与分层策略
| 层次 | 职责 | 依赖方向 | 共享状态范围 |
|---|---|---|---|
| UI | 纯 Compose 组件,渲染状态,收集用户操作并包装为事件 | 仅依赖 ViewModel | 单个 Composable 或 Screen |
| ViewModel | 管理界面状态(StateFlow),协调 Domain 层,格式化数据供 UI 消费 | 仅依赖 Domain | ViewModel 实例(与 Lifecycle 绑定) |
| Domain | 纯业务逻辑,包含 UseCase、数据校验、聚合计算 | 仅依赖 Data | UseCase 级别(无状态或缓存状态) |
| Data | 数据源操作:本地(Room/SQLDelight)、远程(API/GraphQL)、缓存策略 | 无上层依赖 | Repository / DataSource 级别 |
4.2 组件职责与数据流/控制流/信任链路
- 数据流:Data → Domain → ViewModel → UI,状态从最底层逐层转换、映射、裁剪为 UI 友好的形式。
- 控制流(事件流):UI 产生事件(Action/Intent),经 ViewModel 转为业务请求,Domain 编排执行,最终流向 Data。
- 信任链路:每一层只信任直接下层的公开接口;Domain 不信任 ViewModel,Data 不信任 Domain——信任由接口契约保证,不依赖运行时状态。
4.3 选型权衡
| 维度 | 分层 UDF 优势 | 潜在代价 |
|---|---|---|
| 可测试性 | 每一层可单独 Mock 下层接口,单元测试覆盖率高 | 需为每层编写接口和测试桩,初期成本略高 |
| 状态可预测 | 状态变更路径唯一,调试时仅需追踪 StateFlow 链路 | 跨层状态同步需额外映射,可能产生少量样板代码 |
| 平台差异隔离 | Domain/Data 层纯 Kotlin,完全复用;UI 层仅依赖 Compose Multiplatform | ViewModel 层需处理 Lifecycle 平台差异(如关闭协程) |
| 团队协作 | 边界清晰,前端、业务、数据团队可并行开发不同层 | 需要严格的代码 Review 保证分层洁癖 |
4.4 风险点
- 事件泛滥:UI 层频繁发送微小事件导致 ViewModel 重复计算 → 使用
debounce/distinctUntilChanged收敛。 - 状态穿透:ViewModel 直接暴露 Data 层内部状态(如 HTTP 错误码) → 强制在 Domain 层做领域转换。
- 协程泄露:ViewModel 层协程作用域未与 Lifecycle 正确绑定 → 使用
viewModelScope或lifecycleScope。 - 过度抽象:简单 CRUD 场景强行分层 → 适当采用扁平架构(仅 UI + Data),但须在文档中显式声明。
4.5 落地步骤
- 定义层间接口契约:创建
Repository接口(Data 层)、UseCase接口(Domain 层)、ViewModel接口(ViewModel 层)。 - 实现 Data 层:先写本地/SQLDelight 数据源,再提供 Fake/Mock 实现供上层测试。
- 实现 Domain 层:基于 Data 接口编写 UseCase,仅暴露领域状态(如
Sealed Class)。 - 实现 ViewModel 层:注入 Domain 层,用
MutableStateFlow管理 UI 状态,接收事件并映射为 Domain 调用。 - 实现 UI 层:收集 ViewModel 的
StateFlow,渲染 Compose 组件,将交互封装为 sealed 事件。 - 验证反向数据流合规:Code Review 时检查是否有下层直接修改上层状态,或 UI 直接调用 Data 层。
4.6 验收标准
| 验收项 | 判断标准 |
|---|---|
| 状态流单向性 | 编译期无上层直接引用下层可变状态(除 ViewModel 的 StateFlow) |
| 事件流单向性 | UI 至 Data 的事件传递全部经过 ViewModel → Domain → Repository 接口 |
| 信任链完整性 | 每层的公开接口仅包含 suspend 函数或 Flow,不暴露内部状态引用 |
| 可替换性 | 任意层的实现可通过 Koin/Kodein 替换为 Mock 或另一平台实现,不影响其他层编译 |
| 调试体验 | 启用 Compose 状态检查工具时,能清晰看到每个 StateFlow 的订阅者和发射源 |
常见反模式:在 UI 层直接创建 ViewModel 的
StateFlow 并手动 update。这破坏了反向数据流——状态必须由 ViewModel 统一管理,UI 只能通过事件请求变更。