📐 整体架构
项目采用三层架构 ,通过 app、bridge、core 三个核心 crate 实现展示层、适配层和核心层的清晰分离。
架构分层图(简化版)
┌─────────────────┐
│ bridge │ ←── 顶层契约(无外部依赖)
│ 接口 + 实体 │
└────────┬────────┘
│
┌───────────┴───────────┐
│ │
▼ ▼
┌─────────────┐ ┌─────────────┐
│ app │ │ core │ ←── 两侧平级,互不依赖
│ 展示层 │ │ 核心层 │
│ 主程序入口 │ │ 业务+存储 │
└─────────────┘ └─────────────┘
三角形依赖关系 :bridge 为顶点,app 和 core 为底边两端,都依赖上层的 bridge。
依赖方向
app ──┬── bridge ──── core
│ ↑
└────────────────┘
bridge:定义接口契约和实体类型,零外部依赖app:展示层 + 主程序,依赖bridgecore:核心层 + 底层实现,依赖bridge(实现其接口)app和core互不依赖,通过bridge通信
🧠 核心设计思想
1. 前后端分离的桌面映射
本架构本质上是 "前后端分离"思想在桌面应用中的直接映射。
| 网页前后端 | 桌面三层 (A11) | 职责 |
|---|---|---|
| 前端 (React/Vue) | app | 人机交互、界面渲染、用户输入、程序入口 |
| API 契约 (OpenAPI/GraphQL) | bridge | 定义接口规范、实体类型、通信协议、状态缓存 |
| 后端 (Rust/Go/Java) | core | 业务逻辑、数据存储、核心计算 |
数据流对比:
- 网页前后端:浏览器 → HTTP/WebSocket → API 网关 → 后端 → 数据库
- 桌面三层:
app→bridge接口调用 →core→ SQLite
2. 适配层作为"缓存适配层"
bridge 不仅是接口契约,更是缓存适配层 ,核心作用是 弱化上下层独立演进带来的差异。
| 变化来源 | bridge 的缓存/适配作用 |
|---|---|
core 层重构(字段改名、表结构调整) |
bridge 做数据映射,UI 无需改动 |
app 层需求变化(展示字段增加、格式变化) |
bridge 提供转换,core 无需改动 |
| 存储引擎替换(SQLite → PostgreSQL) | bridge 接口不变,只需 core 实现新适配 |
| UI 框架切换(Dioxus → egui) | bridge 提供相同的 UiState,UI 只改渲染层 |
具体机制:
- 状态缓存 :
bridge持有UiState,UI 只读取缓存状态,不直接访问core - 数据转换 :
bridge负责将core的领域模型转换为 UI 所需的视图模型 - 接口隔离 :上下层只依赖
bridge定义的 Trait,不感知对方的具体实现
3. 设计价值
| 前后端分离的优势 | 桌面三层对应的能力 |
|---|---|
| 前端可独立换框架 | app 可独立换 GUI 框架 |
| 后端可独立换技术栈 | core 可独立换存储引擎 |
| API 契约保证协同 | bridge 保证接口契约稳定 |
| 前后端可并行开发 | app 和 core 可并行开发 |
| 前端可 Mock 后端 | app 可 Mock bridge 接口 |
| 后端可独立测试 | core 可独立单元测试 |
一句话总结 :桌面应用 = 前后端分离的 Web 应用,只是前端换成了原生 GUI,后端换成了本地 Rust 库,中间用 bridge 取代了 HTTP API,且 bridge 额外承担了缓存适配的职责。
📁 目录结构
A11/
├── Cargo.toml # 工作空间配置
│
├── app/ # 【展示层】
│ ├── Cargo.toml
│ └── src/
│ ├── main.rs # 入口: app::run()
│ ├── lib.rs
│ ├── launcher.rs # 组装 + 启动实现
│ ├── app.rs # Dioxus 根组件
│ ├── platform/ # 平台适配
│ │ ├── mod.rs
│ │ ├── desktop.rs # 桌面托盘
│ │ └── mobile.rs # 手机通知
│ ├── handler/ # 事件处理器
│ │ └── mod.rs # 实现 bridge::EventHandler
│ └── components/ # UI 组件
│ ├── mod.rs
│ ├── contact_tree.rs
│ ├── message_bubble.rs
│ └── workflow_card.rs
│
├── bridge/ # 【适配层】
│ ├── Cargo.toml
│ └── src/
│ ├── lib.rs
│ ├── entity/ # 实体模型(跨层共享)
│ │ ├── mod.rs
│ │ ├── id.rs # Id 类型
│ │ ├── time.rs # Time 类型
│ │ ├── error.rs # 通用错误
│ │ ├── contact.rs # 通讯录实体
│ │ ├── message.rs # 消息实体
│ │ └── workflow.rs # 流程实体
│ ├── traits/ # 接口契约
│ │ ├── mod.rs
│ │ ├── platform.rs # PlatformManager
│ │ ├── handler.rs # EventHandler
│ │ └── storage.rs # Storage
│ ├── types/ # 数据类型 + 状态缓存
│ │ ├── mod.rs
│ │ ├── ui_state.rs # UiState(缓存)
│ │ └── notification.rs
│ └── service.rs # Bridge 服务(组合 + 转换)
│
└── core/ # 【核心层】
├── Cargo.toml
└── src/
├── lib.rs
├── engine/ # 业务引擎
│ ├── mod.rs
│ ├── contact.rs # 通讯录引擎
│ ├── message.rs # 消息引擎
│ └── workflow.rs # 流程引擎
└── storage/ # 存储实现
├── mod.rs
└── sqlite.rs # 实现 bridge::Storage
📦 Crate 职责
| Crate | 定位 | 职责 | 对外暴露 |
|---|---|---|---|
| app | 展示层 | 界面渲染、用户交互、平台适配、程序入口 | app::run() |
| bridge | 适配层 | 实体模型、接口契约、数据转换、状态缓存 | bridge::entity, bridge::traits, bridge::types |
| core | 核心层 | 业务逻辑、数据存储、核心计算 | core::engine, core::storage |
🎯 设计原则
1. 依赖倒置
bridge定义Storage,core实现它bridge定义PlatformManager,app实现它- 高层(
app)不依赖低层(core),都依赖抽象(bridge)
2. 单一出口
- 整个应用只有一个入口:
app::run() - 所有初始化逻辑封装在
app内部 - 调用者无需关心
core/bridge的细节
3. 接口隔离
PlatformManager:仅系统能力(托盘/通知/角标)EventHandler:仅事件回调Storage:仅数据存取
4. 依赖方向
app → bridge ← core
bridge是核心枢纽,被core和app共同依赖core和app互不依赖,通过bridge通信
5. 缓存适配
bridge持有UiState,UI 只读缓存,不直接访问corecore变化通过事件触发bridge更新缓存,再推送 UI- 上下层各自重构时,只需调整
bridge的转换逻辑
📊 数据流
启动流
main() → app::run()
├── 创建 core::SqliteStorage
├── 创建 app::DesktopPlatform
├── 创建 app::GuiEventHandler
├── 组装 bridge::Bridge
└── 启动 Dioxus App
用户操作流
UI 组件 → bridge::Bridge::handle_command()
├── 调用 core::engine (业务逻辑)
├── 调用 core::storage (持久化)
└── 触发 bridge::EventHandler 回调
│
▼
更新 UiState 缓存
│
▼
UI 自动渲染新状态
🔧 技术栈概览
| 层级 | 技术选型 |
|---|---|
| 界面框架 | Dioxus 0.7 |
| 桌面窗口 | Dioxus Desktop + wry |
| 手机渲染 | 系统 WebView |
| 平台托盘 | tray-icon |
| 平台通知 | notify-rust |
| 数据存储 | SQLite + sqlite crate |
| ID 类型 | i64 包装为 Id |
| 序列化 | serde / serde_json |
| 异步运行时 | tokio |
📌 关键决策记录
| 决策 | 理由 |
|---|---|
三层架构:app / bridge / core |
a-b-c 简洁有序,语义精准,业界通用 |
bridge 作为顶层契约 |
依赖倒置,core 和 app 都依赖抽象 |
entity 目录存放跨层实体 |
所有在 app 和 core 间传递的数据结构统一放置 |
Id 底层用 i64 |
比 String 高效,比 usize 跨平台稳定 |
Id 用新类型包装 |
类型安全,防止与其他整数混淆 |
Time 放在 bridge/entity/ |
时间类型是跨层共享的公共类型 |
common 合并入 bridge/entity/ |
公共类型属于接口契约,由适配层统一提供 |
desktop 合并入 app |
启动器无独立逻辑,app::run() 直接启动 |
| 使用条件编译处理字节序 | 确保跨平台通信时小端存储 |
bridge 作为缓存适配层 |
弱化上下层独立演进带来的差异,降低变更成本 |
| 手机端输入限制通过条件编译 | 当前 GUI 不成熟,未来可一键解封 |
数据库接口在 bridge,实现在 core |
接口定义与具体实现分离,便于替换存储引擎 |
entity/ 保持平铺 |
文件数不超过 10-15 个时保持简洁,后续按需分流 |
✅ 设计价值总结
| 方面 | 优势 |
|---|---|
| 简洁 | 仅 3 个核心 crate,依赖清晰 |
| 解耦 | core ↔ app 完全隔离,通过 bridge 通信 |
| 入口统一 | app::run() 是唯一入口,调用简单 |
| 可测试 | 每层可独立 Mock 测试 |
| 可替换 | 替换 UI 或存储只需修改对应 crate |
| 跨平台 | 桌面/手机共享同一套 core + bridge |
| 前后端分离 | 桌面版的前后端分离,UI 和逻辑可独立演进 |
| 缓存适配 | bridge 弱化上下层差异,降低长期维护成本 |