Files
brainwave/docs/architecture.md
T

10 KiB
Raw Blame History

Android 系统架构

状态:已接受的 MVP 架构 技术方向:Kotlin + Jetpack Compose,单模块起步 骨架来源:Android Architecture Starter Template — base

1. 架构目标

  • 本地起卦是确定、可测试、无 Android/网络依赖的纯领域逻辑。
  • UI、内容、存储和 AI 可以独立替换,不改变已生成的 CastResult。
  • 核心流程离线可用,AI 故障只降级解释能力。
  • 包边界对人和编码代理都清楚,并能通过测试或静态规则检查。
  • MVP 保持单 Gradle 模块,避免过早多模块化;代码仍按领域和层分包。

Android 官方建议新应用采用清晰的 UI/数据分层、单向数据流、ViewModel、协程与 Flow;大型复杂业务才按需要增加 domain 层。本项目因为起卦不变量重要,保留轻量 domain 层。Android 架构指南

2. 系统上下文

┌──────────────── Android App ────────────────┐
│ Compose UI                                  │
│     ↓ intent              ↑ immutable state │
│ ViewModel / state holder                    │
│     ↓                                       │
│ Domain: CastEngine / explanation use cases  │
│     ↓ interfaces                            │
│ Repositories                                │
│   ├─ Assets / prepackaged content           │
│   ├─ Room history                           │
│   ├─ DataStore settings                     │
│   └─ AI gateway ───────────────→ App backend│
└─────────────────────────────────────────────┘

只有 AI 解释需要网络。起念、投币、起卦、卦库读取和本地解释都位于 Android 应用内部。

3. 建议目录

工程初始化后建议采用:

app/src/main/java/<package>/
├── app/
│   ├── BrainwaveApplication.kt
│   ├── MainActivity.kt
│   └── BrainwaveNavHost.kt
├── core/
│   ├── model/                 # 跨层不可变领域模型
│   ├── designsystem/          # 主题、字体、间距、卦象 Canvas
│   └── common/                # 极少量通用结果类型/调度器
├── domain/
│   ├── casting/               # CastEngine、映射与校验
│   └── explanation/           # 解释用例与端口
├── data/
│   ├── content/               # assets 卦库解析与版本校验
│   ├── history/               # Room entity、DAO、repository
│   ├── settings/              # DataStore
│   └── ai/                    # 网络 DTO、gateway、响应校验
└── feature/
    ├── onboarding/
    ├── home/                  # 回访首页、最近一次、已上线次级入口
    ├── question/
    ├── casting/
    ├── result/
    ├── explanation/
    ├── history/
    └── settings/

测试按相同包结构镜像放入 src/test 和 src/androidTest。

4. 依赖方向

允许:

feature UI → feature ViewModel → domain use case → repository interface
data implementation → repository interface + core model
app/navigation → feature public route
core/designsystem → Compose/Material + core model(仅绘制需要)

禁止:

  • domain 依赖 Compose、Activity、ViewModel、Room、Retrofit/Ktor 或具体 AI SDK。
  • CastEngine 读取系统时间、随机数、网络、数据库或问题文本。
  • Composable 直接访问 DAO、assets、网络客户端或 Hilt entry point。
  • data/ai 引用或调用 CastEngine。
  • AI DTO 直接成为 UI 状态;必须先校验并映射为领域结果。
  • feature 之间直接引用对方的内部 ViewModel 或 screen 实现。
  • 一个“Utils”包承载无边界的杂项业务逻辑。

工程具备代码后,应通过架构测试或静态检查机械执行这些禁止项,不能只依赖评审记忆。

5. 核心组件职责

CastEngine

纯 Kotlin、无副作用。输入六轮铜币和方法版本,输出不可变 CastResult。所有规则来自领域规则。

HexagramContentRepository

按卦号和 contentVersion 返回已校验的本地内容。缺失、重复或版本不兼容属于数据完整性错误,不使用 AI 猜补。

CastingSessionViewModel

维护草稿问题、六轮录入和状态机。通过 SavedStateHandle 保存可恢复的进行中状态;只在第六轮确认时调用 CastEngine。

HistoryRepository

接收第六爻锁定事件与当次 HistorySavePolicy 快照,在 autoSaveHistory=true 时持久化会话;总开关关闭时仍支持结果页明确发出的“保存本次”命令。策略包含 saveQuestion、saveExplanation 和 saveActionNote,生产默认值均为 true,由 DataStore 提供但不得泄露到领域起卦逻辑。

Repository 提供倒序历史与最新一条的 Flow 查询,并把随后产生的本地或 AI 解读事务性关联到同一 session。重新解读新增版本或执行经过明确确认的替换命令,禁止普通 upsert 静默覆盖。DAO 事务负责 session、explanation 和 action 的一致写入与级联删除;首页直接订阅最新记录的投影,不维护第二份敏感正文缓存。Room 模型不得泄露到 UI;数据库迁移必须有测试。

HomeViewModel / HistoryViewModel

HomeViewModel 组合 onboarding 状态、最新历史投影、保存策略摘要和已启用功能入口,只输出真实可进入的入口;不为未来功能生成禁用占位。首页固定输出本机保存/AI 发送边界文案和设置入口。HistoryViewModel 维护列表、空态、详情、单条删除和清空全部确认状态,通过 HistoryRepository 完成操作。导航使用一次性 UI 事件或 NavHost 回调,返回后恢复列表位置。

ExplanationRepository

提供统一接口:本地解释和 AI 解释返回相同的可展示领域结构,并带明确 source。AI 实现遵循AI 解释与安全。

6. 单向数据流

每个 feature 使用不可变 UiState 和显式 UiAction:

user action → ViewModel → use case/repository → state update → Compose render

一次性结果也应建模为状态及消费动作,避免裸 Channel/事件在配置变化时丢失。导航由 UI 根据已处理状态触发,并保证重复收集不会重复提交 AI 请求。

领域计算成功后 CastResult 为只读值。解释状态与起卦状态分开存储:

ResultUiState(
  castResult = immutable,
  content = immutable,
  explanationState = Idle | Loading | Local | Ai | Failed
)

7. 数据和存储选择

  • assets 或预置 Room:版本化的 64 卦及爻辞。首版如果仅按编号读取,JSON assets 更简单;需要全文检索或内容迁移时再采用预置 Room。
  • Room:按保存策略自动写入或由用户当次明确保存的会话、解释和行动记录。Android 官方推荐 Room 管理非平凡结构化数据。Room 文档
  • DataStore:方法说明是否已读、主题、AI 同意版本和历史保存策略等少量设置;不保存大型历史或完整卦库。DataStore 文档
  • SavedStateHandle:恢复当前未完成流程;不是长期历史数据库。

历史 Room 数据库及其辅助文件必须通过 Android backup/data-extraction 配置排除系统自动备份和设备迁移。此边界由构建配置测试机械验证,不能只依赖界面文案;引入导出、账号或同步前必须新增 ADR。

详细契约见数据与内容。

8. 网络与 AI 边界

正式发行版本通过自有后端代理模型服务:

App → HTTPS backend → model provider

开发者密钥不能放入 APK。网络层只在用户主动选择 AI 解读后工作,设置合理连接/读取超时、取消传播和有限重试。不得后台预取解释,不得在用户继续编辑问题时偷偷重发。

如果未来支持用户自带密钥,它是单独的产品模式,需要 Android Keystore、清晰风险说明和独立决策记录,不能与默认发布路径混合。

9. 安全与隐私

  • 仅请求联网所需权限;起卦不需要相机、定位、联系人、传感器或存储权限。
  • 所有请求使用 TLS,服务端执行认证、限流和滥用保护。
  • 日志不得包含问题原文、完整提示词、模型回复或 API 密钥。
  • 崩溃与分析事件只记录枚举状态、耗时桶和匿名错误码。
  • 用户删除本地记录后,不保留隐藏副本;服务端数据保留策略需在 AI 上线前单独确认。
  • 任何来自 assets、数据库或网络的数据在边界处解析和校验,失败后进入有恢复路径的错误状态。

10. 依赖和构建政策

  • 版本集中在 Gradle Version Catalog。
  • 优先使用 AndroidX、Kotlin 官方组件和维护活跃的小型依赖。
  • 新依赖必须说明用途、维护状态、许可证、体积和是否可由现有能力替代。
  • 禁止直接引入完整“算命 SDK”或无法审计的卦象计算库。
  • Compose 依赖使用 BOM 对齐版本。
  • compileSdk/targetSdk 使用实现时最新稳定且满足商店要求的版本,不在方案文档硬编码会迅速过期的数值。
  • minSdk 默认提案为 26,最终值见决策记录。

11. 多模块化触发条件

满足以下至少两个条件后再评估从 base 迁移为多模块:

  • 两名以上开发者长期并行修改独立 feature;
  • 构建时间已经影响反馈循环;
  • 有可独立发布/复用的设计系统或领域库;
  • 历史、内容浏览、账户等功能使单模块边界难以机械约束;
  • 需要独立基准测试或测试应用。

迁移前必须新增 ADR;“项目看起来更专业”不是拆模块理由。