docs: harness 工程文档
This commit is contained in:
69
docs/architecture.md
Normal file
69
docs/architecture.md
Normal file
@@ -0,0 +1,69 @@
|
||||
# 架构
|
||||
|
||||
## 概述
|
||||
|
||||
本项目采用 Google Now in Android(NIA)风格的多模块架构,将应用按**职责边界**拆分为 `app`、`core/*`、`feature/*` 三类模块。核心目标是让依赖方向**严格单向**、让每一层**可独立编译与测试**、让领域逻辑**与 Android 框架解耦**。
|
||||
|
||||
## 模块结构
|
||||
|
||||
| 模块 | 类型 | 职责 |
|
||||
|------|------|------|
|
||||
| `app` | application + compose + hilt | 组合根:`@HiltAndroidApp` Application、`MainActivity`、单一 `AppNavHost` |
|
||||
| `core:common` | 纯 JVM | 无 Android 依赖的通用工具 |
|
||||
| `core:model` | 纯 JVM | 领域模型(跨层共享的不可变数据) |
|
||||
| `core:domain` | 纯 JVM | UseCase + Repository 接口(业务规则,框架无关) |
|
||||
| `core:data` | android library + hilt | Repository 实现,编排 network/database/datastore |
|
||||
| `core:network` | android library + serialization | Retrofit/OkHttp、DTO、序列化 |
|
||||
| `core:database` | android library + room | Room 实体 / DAO / 数据库 |
|
||||
| `core:datastore` | android library | DataStore 偏好存储 |
|
||||
| `core:designsystem` | android library + compose | 主题 / 颜色 / 字体 / 通用组件 |
|
||||
| `core:ui` | android library + compose + hilt | 共享 Compose UI 与导航辅助 |
|
||||
| `feature:*` | feature | 各业务功能(auth/checkin/plan/course/survey/feedback/profile) |
|
||||
|
||||
## 分层与依赖方向
|
||||
|
||||
依赖必须**单向向下**,杜绝环。完整 allow-list 编码在 `build-logic/convention/src/main/kotlin/RootPlugin.kt` 的 `verifyArchitecture` 任务中——任何越层依赖都会在 `./gradlew verifyArchitecture` 时失败。
|
||||
|
||||
```
|
||||
app ────────────► 所有模块(组合根)
|
||||
feature/* ──────► core:ui, core:designsystem, core:domain, core:model
|
||||
core:data ──────► core:domain, core:model, core:network, core:database, core:datastore
|
||||
core:network ───► core:common, core:model
|
||||
core:database ──► core:model
|
||||
core:datastore ─► core:model
|
||||
core:ui ────────► core:designsystem, core:model
|
||||
core:designsystem ► core:common
|
||||
core:model, core:common ► (叶子,无项目依赖)
|
||||
```
|
||||
|
||||
分层语义(自上而下):
|
||||
|
||||
- **UI 层**(`feature/*` + `core:ui` + `core:designsystem`):渲染界面、处理交互、持有 `ViewModel`。
|
||||
- **领域层**(`core:domain` + `core:model`):业务规则(UseCase)、领域模型、Repository 接口。**纯 Kotlin,不依赖 Android**,可快速跑在 JVM 上测试。
|
||||
- **数据层**(`core:data` + `core:network`/`database`/`datastore`):Repository 实现、网络与本地数据源,依赖领域层接口。
|
||||
|
||||
## 模块配置(约定插件)
|
||||
|
||||
每个模块的 `build.gradle.kts` 只声明 `mcl.*` 约定插件 + namespace,不手写模板。插件语义:
|
||||
|
||||
| 插件 | 适用 |
|
||||
|------|------|
|
||||
| `mcl.kotlin.library` | 纯 JVM 模块(common/model/domain) |
|
||||
| `mcl.android.library` | Android 库(network/database/datastore/data) |
|
||||
| `mcl.android.library.compose` | Android 库 + Compose(designsystem/ui) |
|
||||
| `mcl.android.feature` | feature 模块(= library.compose + hilt + 导航/lifecycle 依赖) |
|
||||
| `mcl.android.application.compose` | app(= application + compose) |
|
||||
| `mcl.android.hilt` | Hilt(KSP) |
|
||||
| `mcl.android.room` | Room(KSP) |
|
||||
| `mcl.android.lint` | Android Lint 配置 |
|
||||
|
||||
## 新增一个模块
|
||||
|
||||
1. 建目录 + `build.gradle.kts`(选对 `mcl.*` 插件 + 设 namespace)+ 空 `AndroidManifest.xml`(仅 Android 模块)。
|
||||
2. 在 `settings.gradle.kts` 里 `include(":...")`。
|
||||
3. 在 `RootPlugin.kt` 的 allow-list 里加一条(模块路径 → 允许依赖的模块)。
|
||||
4. 更新本文件的分层图。
|
||||
|
||||
## 组合根(app)
|
||||
|
||||
`app` 负责把各模块**装配**起来:`MingyuanApplication` 标注 `@HiltAndroidApp`;`AppNavHost` 用 Navigation Compose 把各 feature 的路由常量(`<NAME>_ROUTE`)与 Screen 组合成单一导航图。后续接入数据层时,`app` 会直接依赖 `core:data`(以及含 Hilt `@Module` 的 network/database/datastore)完成 DI 聚合。
|
||||
36
docs/conventions.md
Normal file
36
docs/conventions.md
Normal file
@@ -0,0 +1,36 @@
|
||||
# 编码规范
|
||||
|
||||
## Kotlin
|
||||
|
||||
- 遵循官方 Kotlin 风格:四空格缩进、类型 `UpperCamelCase`、成员 `lowerCamelCase`、常量 `SCREAMING_SNAKE_CASE`。
|
||||
- 优先不可变:用 `val`、`data class` 的 `copy()`,避免可变共享状态。
|
||||
- 严格空安全,不用 `!!` 除非有充分理由。
|
||||
- 用密封类(sealed class)表达状态 / 事件 / 结果。
|
||||
- 协程:数据操作在 `Dispatchers.IO`,UI 在 `Dispatchers.Main`;用 `StateFlow` 暴露 UI 状态(单向数据流)。
|
||||
|
||||
## Compose
|
||||
|
||||
- 屏幕组合函数命名 `<Feature>Screen`,接收 `modifier: Modifier = Modifier` 作为首个参数。
|
||||
- `@Composable` 函数名用 `PascalCase`;被 `@Preview` 或测试使用的纯函数可豁免下划线命名(见 `.editorconfig`)。
|
||||
- 复用组件放 `core:designsystem` 或 `core:ui`,不复制到 feature。
|
||||
|
||||
## 命名
|
||||
|
||||
- Activity/Application 以 `Activity`/`Application` 结尾;ViewModel 以 `ViewModel` 结尾;UseCase 以 `UseCase` 结尾;Repository 实现以 `RepositoryImpl` 结尾(接口在 `core:domain` 用 `Repository`)。
|
||||
- 资源用 `snake_case`;字符串、颜色、尺寸统一在 `core:designsystem` 管理。
|
||||
- 测试类以 `<Subject>Test` 结尾。
|
||||
|
||||
## 版本与依赖
|
||||
|
||||
- 依赖版本只写在 `gradle/libs.versions.toml`,模块里用 `libs.*` 访问器引用,禁止硬编码版本。
|
||||
- 新增依赖先确认是否已有等价项,避免重复引入。
|
||||
|
||||
## Git
|
||||
|
||||
- Commit 格式 `type(scope): description`,type 如 `feat`/`fix`/`refactor`/`docs`/`build`/`test`/`chore`;一次提交一个变更集。
|
||||
- 提交前跑 `./gradlew spotlessApply` 与 `bash scripts/verify.sh`。
|
||||
|
||||
## 文档
|
||||
|
||||
- 重要决策写入 `docs/decisions/`(ADR),行为与文档在同一次变更里一起更新。
|
||||
- 模块级"为什么这样设计"写在该目录的 `DESIGN_NOTES.md`(即时上下文)。
|
||||
22
docs/decisions/0001-multi-module-nia.md
Normal file
22
docs/decisions/0001-multi-module-nia.md
Normal file
@@ -0,0 +1,22 @@
|
||||
# ADR 0001:多模块 NIA 架构,feature 单模块(不做 api/impl 拆分)
|
||||
|
||||
- **状态**:已接受
|
||||
- **日期**:2026-09-03
|
||||
|
||||
## 背景
|
||||
|
||||
项目从单模块 Compose 模板起步,需要确立可扩展、可测试、可维护的架构基础。
|
||||
|
||||
## 决策
|
||||
|
||||
采用 Google Now in Android(NIA)风格多模块架构:`app`(组合根)+ `core/*`(共享能力)+ `feature/*`(业务功能)。每个 feature 用**单模块**,不做 nowinandroid 的 `feature:*/api` + `feature:*/impl` 拆分。
|
||||
|
||||
## 理由
|
||||
|
||||
- 依赖方向单向、职责清晰,符合参考项目 `nowinandroid` 与 `app_device` 的共识。
|
||||
- 团队规模小、feature 间共享少,api/impl 拆分会带来额外模块与样板,收益有限。
|
||||
|
||||
## 后果
|
||||
|
||||
- 约定插件 `mcl.android.feature` 预置 feature 通用依赖,新增 feature 只需一个插件声明。
|
||||
- 若未来 feature 间需要稳定 API 边界,再引入 api/impl 拆分(成本可控)。
|
||||
@@ -0,0 +1,23 @@
|
||||
# ADR 0002:约定插件 + 版本目录作为唯一配置事实源
|
||||
|
||||
- **状态**:已接受
|
||||
- **日期**:2026-09-03
|
||||
|
||||
## 背景
|
||||
|
||||
多模块项目若在每个模块手写 `android {}` 模板,会出现配置漂移、版本不一致、样板重复。
|
||||
|
||||
## 决策
|
||||
|
||||
- **约定插件**(`build-logic/`,included build):提供 `mcl.*` 插件,集中编码 compileSdk/minSdk、Java/Kotlin target、Compose、Hilt、Room、Lint、Spotless 等配置。模块只声明插件 + namespace。
|
||||
- **版本目录**(`gradle/libs.versions.toml`):所有依赖版本唯一事实源,主构建与 build-logic 共用。
|
||||
|
||||
## 理由
|
||||
|
||||
- 把"严谨性"编码进确定性规则(harness-engineering 的"严谨性转移"原则),而非依赖人工记忆。
|
||||
- 一个版本改一处,全项目生效。
|
||||
|
||||
## 后果
|
||||
|
||||
- 新增/调整模块配置需改 build-logic;新增依赖需改版本目录。
|
||||
- 好处:`verifyArchitecture` 等约束可挂在约定插件体系里统一执行。
|
||||
23
docs/decisions/0003-toolchain-and-versions.md
Normal file
23
docs/decisions/0003-toolchain-and-versions.md
Normal file
@@ -0,0 +1,23 @@
|
||||
# ADR 0003:工具链与版本选型
|
||||
|
||||
- **状态**:已接受
|
||||
- **日期**:2026-09-03
|
||||
|
||||
## 背景
|
||||
|
||||
需要为一套现代化 Android 技术栈锁定版本。项目原本是 AGP 9.4.0 + Kotlin 2.2.10 + Gradle 9.6.0。
|
||||
|
||||
## 决策
|
||||
|
||||
- **保留**:Gradle 9.6.0(wrapper)、AGP 9.4.0、compileSdk/targetSdk 37、minSdk 28、Compose BOM 2026.02.01。
|
||||
- **对齐 nowinandroid 已验证矩阵**:Kotlin 2.3.0、KSP 2.3.4、Hilt 2.59、Room 2.8.3、Retrofit 2.11.0、OkHttp 4.12.0、kotlinx-coroutines 1.10.1、kotlinx-serialization 1.8.0、Navigation Compose 2.8.5、DataStore 1.2.0、Lifecycle 2.10.0、truth 1.4.4、turbine 1.2.0、spotless 8.3.0、ktlint 1.4.0。
|
||||
|
||||
## 理由
|
||||
|
||||
- 采用 AGP 9 内置 Kotlin(不显式应用 `org.jetbrains.kotlin.android`),与项目原有模板一致,且经 nowinandroid 验证可与 KSP/Hilt/Room 共存。
|
||||
- Kotlin 升到 2.3.0 是为了直接复用 nowinandroid 已证明可用的 KSP 2.3.4 / Hilt 2.59 / Room 2.8.3 组合,避免为旧 Kotlin 猜测 KSP 补丁版本。
|
||||
|
||||
## 后果
|
||||
|
||||
- 全量构建已验证通过(`:app:assembleDebug`)。
|
||||
- 若未来 AGP 升级改变内置 Kotlin 行为,需回归验证 KSP/Hilt/Room 兼容性。
|
||||
22
docs/decisions/0004-package-name.md
Normal file
22
docs/decisions/0004-package-name.md
Normal file
@@ -0,0 +1,22 @@
|
||||
# ADR 0004:包名与显示名
|
||||
|
||||
- **状态**:已接受
|
||||
- **日期**:2026-09-03
|
||||
|
||||
## 背景
|
||||
|
||||
项目原有包名 `com.mingyuan.android_client`(applicationId 与 namespace),显示名 `android-client`。
|
||||
|
||||
## 决策
|
||||
|
||||
- **保留包名** `com.mingyuan.android_client`(避免牵连改动),仅把显示名 `app_name` 改为「明缘进阶场」。
|
||||
- 模块命名空间沿用 `com.mingyuan.android_client.<路径点分>`。
|
||||
|
||||
## 理由
|
||||
|
||||
- 重命名 applicationId/namespace 会牵连 manifest、包目录、DI 组件等;基础建设阶段优先把架构骨架跑通。
|
||||
- 下划线命名虽不符合惯例,但不影响构建与运行,作为已知小问题保留。
|
||||
|
||||
## 后果
|
||||
|
||||
- 显示名已是「明缘进阶场」;包名中的下划线如需规范化,可在后续单独决策并一次性迁移。
|
||||
27
docs/deployment.md
Normal file
27
docs/deployment.md
Normal file
@@ -0,0 +1,27 @@
|
||||
# 构建与部署
|
||||
|
||||
## 构建
|
||||
|
||||
```bash
|
||||
./gradlew :app:assembleDebug # 调试 APK(app/build/outputs/apk/debug/)
|
||||
./gradlew :app:assembleRelease # 发布 APK(未启用混淆,签名见下)
|
||||
./gradlew installDebug # 安装到已连接设备
|
||||
```
|
||||
|
||||
## 签名
|
||||
|
||||
当前发布构建**未配置签名**,也**未启用 R8 混淆**(`minify` 关闭)。接入真实发布前需:
|
||||
|
||||
1. 生成/配置 keystore(`keystore.properties` 或环境变量)。
|
||||
2. 在 `app/build.gradle.kts` 配置 `signingConfigs` 与 `release` buildType 的签名。
|
||||
3. 需要时启用 R8(`isMinifyEnabled = true` + proguard 规则)。
|
||||
|
||||
## 环境
|
||||
|
||||
- JDK 17+(`settings.gradle.kts` 有版本检查)。
|
||||
- Android SDK 路径见 `local.properties`(`sdk.dir`)。
|
||||
- `compileSdk` 37 / `minSdk` 28 / `targetSdk` 37。
|
||||
|
||||
## CI
|
||||
|
||||
`.github/workflows/ci.yml` 在推送 GitHub 后自动运行 `spotlessCheck → test/testDebugUnitTest → :app:assembleDebug → verifyArchitecture → lintDebug`。
|
||||
35
docs/progress.md
Normal file
35
docs/progress.md
Normal file
@@ -0,0 +1,35 @@
|
||||
# 工作状态
|
||||
|
||||
> 本文件是跨会话的工作状态快照。变更后请更新。
|
||||
|
||||
## 当前状态
|
||||
|
||||
- ✅ 基础建设:多模块骨架(9 core + 7 feature + app)、约定插件(`mcl.*`)、版本目录、`verifyArchitecture`、Spotless/Lint、验证脚本、CI、文档、DESIGN_NOTES。
|
||||
- ✅ 验证通过:`./gradlew :app:assembleDebug`、`test`/`testDebugUnitTest`、`verifyArchitecture` 全绿。
|
||||
- ⬜ 各 feature 为占位屏(`<Feature>Screen` + 空 `<Feature>ViewModel`),无业务逻辑。
|
||||
|
||||
## 已完成
|
||||
|
||||
- [x] build-logic 约定插件 + 版本目录 + 根配置
|
||||
- [x] 17 个模块骨架 + app 组合根(Hilt + Navigation)
|
||||
- [x] 主题迁入 `core:designsystem`(`MingyuanTheme`)
|
||||
- [x] `verifyArchitecture` 架构检查任务
|
||||
- [x] AGENTS.md / README / docs/* / DESIGN_NOTES
|
||||
- [x] scripts/verify.sh + CI workflow
|
||||
|
||||
## 已阻塞
|
||||
|
||||
- 无。
|
||||
|
||||
## 下一步计划
|
||||
|
||||
1. 接入真实后端:`core:network` 定义 Retrofit API + DTO(对应 wechat-backend 的 `/api/v1/*`),`core:data` 实现 Repository,`app` 补齐 DI 聚合。
|
||||
2. `core:database`/`core:datastore` 落地本地存储 schema。
|
||||
3. 逐个 feature 实现业务逻辑(checkin/plan/course/survey/feedback/profile/auth)。
|
||||
4. 发布前:签名 + R8 混淆 + 图标/品牌。
|
||||
|
||||
## 遗留项(暂缓)
|
||||
|
||||
- Roborazzi 截图测试、WorkManager、Gradle Managed Devices —— 有真实 UI/后台任务时再接入。
|
||||
- `docs/api.md`、`docs/data-model.md` —— 后端接线时再建。
|
||||
- app 尚未直接依赖 `core:network`/`core:database`/`core:datastore`;接入 DI 时补上(已在 `verifyArchitecture` allow-list 中放行)。
|
||||
34
docs/testing.md
Normal file
34
docs/testing.md
Normal file
@@ -0,0 +1,34 @@
|
||||
# 测试
|
||||
|
||||
## 运行命令
|
||||
|
||||
```bash
|
||||
./gradlew test # 纯 JVM 模块(core:model/domain/common)单元测试
|
||||
./gradlew testDebugUnitTest # Android 模块单元测试(本地 JVM)
|
||||
./gradlew connectedDebugAndroidTest # 仪器测试(需连接模拟器/真机)
|
||||
```
|
||||
|
||||
## 目录与命名
|
||||
|
||||
- JVM 单元测试:`src/test/kotlin/`;Android 单元测试:`src/test/kotlin/`。
|
||||
- 仪器测试:`src/androidTest/kotlin/`。
|
||||
- 测试类命名 `<Subject>Test`;测试方法名用反引号描述意图(`fun \`invoke delegates to implementation\`()`)。
|
||||
|
||||
## 测什么
|
||||
|
||||
- **领域层优先**(`core:domain`):UseCase、纯逻辑、状态转换——用 `kotlinx-coroutines-test` 的 `runTest` + `truth` 断言,快速且稳定。
|
||||
- **数据层**(`core:data`):Repository 用 fake 数据源(实现 `core:domain` 里的接口),不真打网络/数据库。
|
||||
- **UI**:`@HiltViewModel` 的 `ViewModel` 用 `StateFlow` 断言状态;Composable 用 Compose UI test。
|
||||
- 网络 / 数据库边界用接口隔离,用 fake/dummy 替换真实实现。
|
||||
|
||||
## 依赖
|
||||
|
||||
- `junit`:`@Test`
|
||||
- `truth`:断言(更可读的 `assertThat(...)`)
|
||||
- `turbine`:Flow 测试(需要时再加)
|
||||
- `kotlinx-coroutines-test`:`runTest`
|
||||
|
||||
## 约定
|
||||
|
||||
- 不写"假测试"(无断言的空壳);每条测试验证一个明确行为。
|
||||
- 提交 PR 时在描述里写清运行的命令与结果。
|
||||
Reference in New Issue
Block a user