模块职责与架构分层
约 692 字大约 2 分钟
2026-03-20
1. 背景与目标
本页专门解决一个实操问题:你写一段新代码时,应该放在哪个目录。
- 如果边界清晰,代码可维护性会明显提升。
- 如果边界混乱,项目会很快出现“哪里都能改、哪里都怕改”的状态。
2. 总览:模块树与职责速查
app/src/main/java/com/kurosu/sleepin/
├─ data/ # 数据实现:Room/DataStore/CSV/RepositoryImpl
├─ domain/ # 业务抽象:Model/Repository 接口/UseCase
├─ ui/ # 交互与渲染:ViewModel/Screen/Nav/Theme
├─ di/ # 手动依赖提供:DatabaseModule/RepositoryModule
└─ widget/ # 小组件展示与刷新(Glance + WorkManager)3. 分模块讲解(先子树,再规则)
3.1 domain:定义业务语言,不接触 Android 细节
domain/
├─ model/
├─ repository/
└─ usecase/- 你在这里定义“业务概念”和“业务动作”。
- 禁止出现
Context、Room、Compose等框架细节。 - 示例文件:
app/src/main/java/com/kurosu/sleepin/domain/model/Course.ktapp/src/main/java/com/kurosu/sleepin/domain/repository/CourseRepository.ktapp/src/main/java/com/kurosu/sleepin/domain/usecase/course/AddCourseUseCase.kt
3.2 data:实现数据来源与映射
data/
├─ local/
│ ├─ SleepInDatabase.kt
│ ├─ entity/
│ └─ dao/
├─ repository/
├─ preferences/
└─ csv/- 你在这里实现 Repository 接口,做 Entity <-> Domain Model 转换。
- 这层允许接触 Room、DataStore、文件 IO。
- 示例文件:
app/src/main/java/com/kurosu/sleepin/data/local/SleepInDatabase.ktapp/src/main/java/com/kurosu/sleepin/data/repository/CourseRepositoryImpl.ktapp/src/main/java/com/kurosu/sleepin/data/preferences/SettingsDataStore.kt
3.3 ui:状态驱动渲染,不直接操作存储
ui/
├─ navigation/SleepInNavHost.kt
├─ screen/
├─ component/
└─ theme/- ViewModel 调 UseCase,Screen 订阅状态并渲染。
- Screen 不应直接依赖 DAO 或 Entity。
- 示例文件:
app/src/main/java/com/kurosu/sleepin/ui/screen/home/HomeViewModel.ktapp/src/main/java/com/kurosu/sleepin/ui/screen/home/HomeScreen.ktapp/src/main/java/com/kurosu/sleepin/ui/navigation/SleepInNavHost.kt
3.4 di:手动组装依赖图(当前不启用 Hilt)
di/
├─ DatabaseModule.kt
└─ RepositoryModule.ktDatabaseModule提供SleepInDatabase和 DAO。RepositoryModule提供 Repository、CSV 组件。- 真实注入入口在
app/src/main/java/com/kurosu/sleepin/SleepInApplication.kt。
3.5 widget:进程外展示层
widget/
├─ TodayWidgetProvider.kt
├─ WeekWidgetProvider.kt
├─ WidgetCourseSnapshotProvider.kt
├─ WidgetRefreshScheduler.kt
└─ WidgetRefreshWorker.kt- 负责桌面小组件 UI 和刷新机制。
- 通过 UseCase 复用业务规则,避免和主界面逻辑分叉。
4. 调用边界(允许与禁止)
| 调用方向 | 是否允许 | 说明 |
|---|---|---|
ui -> domain | 允许 | ViewModel 调 UseCase |
domain -> data | 禁止 | Domain 只能依赖接口,不依赖实现 |
data -> domain | 允许 | RepositoryImpl 实现 Domain 接口 |
ui -> data | 禁止 | UI 不能直连 DAO/Entity |
widget -> domain | 允许 | 复用 UseCase,保持业务一致 |
5. 常见反模式与修复
5.1 反模式:ViewModel 直接注入 DAO
- 问题:UI 与数据库耦合,测试困难。
- 修复:改为
ViewModel -> UseCase -> Repository。
5.2 反模式:UseCase 接受 Context
- 问题:Domain 被 Android 框架污染。
- 修复:把系统 API 放到
data实现,再通过接口暴露给 Domain。
5.3 反模式:Screen 内写复杂业务判断
- 问题:重组时机和业务逻辑耦合,行为不可控。
- 修复:复杂计算上移到 UseCase / ViewModel,Screen 只消费结果。
6. 快速自检清单
提交前建议自检:
- 新增业务规则是否写在 UseCase,而不是 Composable。
- UI 输出是否只依赖
StateFlow,没有直接依赖 DAO。 - 新增数据结构是否在 Data 层完成了映射和实现。
- 依赖注入是否在
SleepInApplication.kt正确接线。
下一篇建议阅读:docs/SleepIn-Docs/docs/dev/data-model-room.md
