Files
opsxshell/docs/architecture-refactor.md
T

79 lines
4.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 架构重构(渐进推进)
局部职责拆分已开始;下述 EventSink、契约生成和 infrastructure 端口仍是待实施方案。不要把已完成的局部改造当成全仓迁移,也不要顺带引入平行架构。
已落地:本地文件业务下沉至无 Tauri 依赖的 `services/files.rs`;`AppError` 部分接入;前端 IPC 拒绝错误统一映射;settings 功能模型、Hook 和控件提取;设置串行保存与失败回退;系统主题同步修复。实现位置与限制见 [架构说明](architecture.md)。
当前真实分层是 **commands → services → domain DTO**。目录里虽有 `application/`、`infrastructure/`,但:
- `infrastructure/` 只有 `file_log`,services 并不走它。
- `application/state.rs` 是服务集合,不是端口组合根。
- `domain/error.rs` 已有稳定 IPC 错误码 `codes` 和部分接入的 `AppError`;尚未覆盖所有业务错误。
- SSH / 存储 / 桌面服务直接 `use tauri::AppHandle`;known_hosts 还用 `bind_app_handle` 全局 static。
- 前后端契约靠手写 `src/desktop.d.ts` + `shared/types/`,没有 specta / ts-rs。
产品数据流保持不变:UI → Zustand → `src/platform/*` → Tauri command → Rust 服务。重构不得把 SSH、文件系统或加密拉回 React,也不得改成插件宿主。
## 为什么单独做
1. EventSink 要改所有 `app.emit` 调用点,和功能修复冲突时极难回滚。
2. specta 会动 command 签名和前端 `invoke` 形状,必须一次对齐 CI,不能「先生成一半」。
3. 把 storage / SSH / 凭据收进 infrastructure trait,等于重写生命周期,和转发 retain、桌面 worker 取消语义绑在一起。
一次只开一条轨道,合入后再开下一条。每条轨道自己的 PR,跑 `npm run test:patrol` 和 `npm run acceptance:full`。
## 轨道 A:EventSink(先做,最小)
目标:services 不再拿 `AppHandle`,也不再 `bind_app_handle`。
1. 在 `application/` 定义:
```rust
pub trait EventSink: Send + Sync {
fn emit_json(&self, channel: &str, payload: serde_json::Value);
fn emit_empty(&self, channel: &str);
}
```
2. `TauriEventSink(AppHandle)` 只放在 application / command 边界。
3. `SshService`、known_hosts、RDP/VNC engine、license 等改为持有 `Arc<dyn EventSink>`。
4. 删除 `known_hosts` 的 `APP_HANDLE` / `PENDING` / `ONCE_TRUSTED` 全局 static,收进 `AppState`。
5. 单测用内存 `EventSink` 断言事件,不启 Tauri。
完成标准:`rg "use tauri::AppHandle" src-tauri/src/services` 为空(command 层可以留)。行为与现网事件名、payload 完全一致。
## 轨道 B:契约生成(specta 或 ts-rs)
目标:消灭「Rust DTO / `desktop.d.ts` / `shared/types`」三处手工对齐。
1. 选一个:`tauri-specta`(和 command 绑定)或 `ts-rs`(只生成类型)。不要两个一起上。
2. Rust 侧先把 `serde_json::Value` 逃逸收成强类型(`ProxyConfig`、`JumpHostConfig`、会话字段)。
3. 生成文件放到例如 `shared/types/*.generated.ts`,CI 跑 `git diff --exit-code`。
4. 手写 `src/desktop.d.ts` 只留窗口/`opsxApi` 外壳,领域类型改 re-export 生成结果。
5. `test/setup.ts` 跟着生成类型改,不要继续复制一份松散 `unknown`。
完成标准:改一个 command 字段,忘跑生成则 CI 红。不要在这一轨改 EventSink 或换存储引擎。
## 轨道 C:infrastructure 端口(最后)
目标:I/O 可替换,services 只编排。
建议端口(按需,不要一次全铺):
| Trait | 先搬谁 | 不要顺便做 |
| -------------- | ---------------------------------- | ----------------------- |
| `SessionStore` | `services/storage.rs` 的 JSON 会话 | 不要同时改成 SQLite |
| `SecretStore` | `credential_store.rs` | 不要同时改系统 keychain |
| `SshTransport` | 仅当要换 russh 时再抽 | 默认保持 libssh2 |
`infrastructure/mod.rs` 里现在的「SQLite / keychain / PTY 适配器」是过期注释,做 C 轨时改成与真实 trait 一致,没做的删掉,避免再写成已存在。
IPC 错误继续扩展 `domain/error.rs` 的 `AppError` / `codes`,保留既有信封和运行态抛错契约,不另建重复错误类型。
## 明确不做(本轨道也不做)
- 六边形/整仓重命名目录来「看起来像五层」。
- 对外 CLI、MCP、插件宿主。
- 把结账/签发搬回桌面进程。
- 和签名、公证、自动更新、功能修复混在同一个 PR。