Files
opsxshell/docs/architecture-refactor.md
T

77 lines
4.2 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.
# 架构重构(单独轨道,尚未开始)
**当前不要做。** 改动面覆盖几乎全部 `src-tauri` 服务、`src/desktop.d.ts`、`src/platform/*` 和测试替身。本文件只记录怎么拆,避免和日常功能 PR 缠在一起。
当前真实分层是 **commands → services → domain DTO**。目录里虽有 `application/`、`infrastructure/`,但:
- `infrastructure/` 只有 `file_log`,services 并不走它。
- `application/state.rs` 是服务集合,不是端口组合根。
- 稳定 IPC 错误码放在 `domain/error.rs` 的 `codes`(如 `native.taskJoin`),不是空壳 `DomainError` 类型。
- 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` 的 `codes` 常量,不要再加一层空壳 `DomainError`。
## 明确不做(本轨道也不做)
- 六边形/整仓重命名目录来「看起来像五层」。
- 对外 CLI、MCP、插件宿主。
- 把结账/签发搬回桌面进程。
- 和签名、公证、自动更新、功能修复混在同一个 PR。