--- title: 架构决策记录 updated: 2026-04-25 status: stable tags: [architecture, decisions, design-principles] --- # 架构决策记录 > 从 [[index]] 导航。关联: [[erp-core]] [[erp-server]] [[database]] [[wasm-plugin]] [[erp-health]] ## 1. 设计决策 ### 模块化单体 + 渐进式拆分 模块间零直接依赖,跨模块通信通过事件总线和 trait 接口。`ErpModule` trait 天然支持未来按模块拆分为微服务。 ### HMS 架构:原生模块 + 插件并存 HMS 继承 ERP 底座的所有基础模块,`erp-health` 作为原生 Rust 模块承载医疗业务。WASM 插件系统保留但非 HMS 主要扩展方式。 ``` HMS 平台 ├── 基础模块(继承 ERP): auth, config, workflow, message, plugin ├── 核心业务模块: erp-health(原生 Rust,18 实体/14 权限/13 页面)★ 已实现 └── 可选插件: crm, inventory, freelance, itops(WASM) ``` ### 为什么 erp-health 用原生模块? 医疗业务需要 18 强类型实体、自定义 API(趋势分析/统计报表)、PII 数据加密(AES-256-GCM)、文件上传、未来 AI 集成。WASM 插件的 JSONB 动态存储和 20 实体上限无法满足。详见 [[erp-health]]。 ### 为什么用 UUIDv7? 时间排序 + UUID 唯一性 + 接近自增 ID 的索引性能。多租户 SaaS 下不同租户数据不会因 ID 冲突互相影响。 ### 为什么 tenant_id 不在 API 路径中? 从 JWT 提取,中间件注入 `TenantContext`。防止:手动改 URL 越权 / API 暴露租户信息 / 忘记检查权限。管理员接口例外。 ### 为什么错误类型跨 crate 用 thiserror? `anyhow` 无类型信息,无法精确映射 HTTP 状态码。`thiserror` → `AppError` → 400/401/403/404/409/500。 ### 为什么预约用原子 CAS? 防止并发创建预约时超额。事务内 `UPDATE current_appointments + 1 WHERE current < max`,CAS 成功后才 INSERT 预约记录。 ## 2. 关键文件 + 数据流 ### 模块依赖图 ``` erp-core (L1) | +--------------+--------------+--------------+-----------+ | | | | | erp-auth erp-config erp-workflow erp-message erp-health (L2) | | | | | +--------------+--------------+--------------+-----------+ | erp-server (L3: 唯一组装点) | erp-plugin (WASM 插件运行时) ``` **禁止**: L2 间直接依赖 / L1 依赖业务模块 / 绕过事件总线 ### 模块实现状态 | 模块 | 状态 | 实体数 | 权限数 | 页面数 | |------|------|--------|--------|--------| | erp-auth | ✅ 完成 | 11 表 | - | 用户/角色/组织 | | erp-config | ✅ 完成 | 6 表 | - | 设置/字典/菜单 | | erp-workflow | ✅ 完成 | 5 表 | - | 工作流管理 | | erp-message | ✅ 完成 | 3 表 | - | 消息中心 | | erp-plugin | ✅ 完成 | 4 表 | - | 插件管理/市场 | | erp-health | ✅ 完成 | 18 表 | 14 | 13 页面 + 11 组件 | ### 技术选型 | 选择 | 理由 | |------|------| | Axum 0.8 | Tokio 团队维护,tower 生态,类型安全路由 | | SeaORM 1.1 | 异步、类型安全、迁移工具完善 | | PostgreSQL 18 | 企业级,JSON 支持,扩展丰富 | | Redis 7 | 缓存 + 限流 token bucket | | React 19 + Ant Design 6 | 企业后台 UI 标配 | | Zustand 5 | 极简状态管理 | | Wasmtime 43 | WASM 沙箱,Component Model,Fuel 限制 | ### 集成契约 | 方向 | 模块 | 触发时机 | |------|------|---------| | 定义 → | [[erp-core]] | 所有模块的 trait 和类型 | | 组装 ← | [[erp-server]] | 6 模块注册和启动 | | 扩展 ← | [[wasm-plugin]] | 插件通过 Host Bridge 桥接 | | 业务 ← | [[erp-health]] | 健康模块原生集成 | ## 3. 代码逻辑 ⚡ **不变量**: 模块间只通过 EventBus 和 trait 通信,无直接依赖 ⚡ **不变量**: 所有数据表必须含 `tenant_id`,查询自动过滤 ⚡ **不变量**: UUID v7 作为主键 ⚡ **不变量**: 软删除,不硬删除 ⚡ **不变量**: 所有 API 使用 `/api/v1/` 前缀 ⚡ **不变量**: 预约创建必须走原子 CAS,不能用 read-then-write ⚡ **不变量**: PII 数据(身份证、手机号)加密存储 + 脱敏展示 ## 4. 活跃问题 + 陷阱 ⚠️ 当前共享数据库 + tenant_id 过滤,未来可扩展为 Schema 隔离或数据库隔离 ⚠️ EventBus 内存 broadcast 需 outbox 持久化保障(已通过后台任务实现) ⚠️ 微信登录固定到 default_tenant_id — 多租户场景需设计解析策略 ## 5. 变更记录 | 日期 | 变更 | |------|------| | 2026-04-25 | 全面更新:6 模块已实现状态表、预约 CAS 决策、PII 加密不变量、健康模块集成 | | 2026-04-23 | 重构为 5 节结构,删除 erp-common 引用,精简技术选型表 |