# server-rs DDD 一次性重构落地方案 日期:`2026-04-28` ## 1. 背景与当前结论 当前仓库已经进入 `server-rs + Axum + SpacetimeDB` 单一后端路线,旧 `server-node` 已不存在,后续不再围绕 Express / PostgreSQL 做兼容设计。本轮重构目标不是新增玩法,而是把已有 Rust 后端统一成可长期演进的 DDD 边界: 1. 领域规则沉到 `module-*`。 2. SpacetimeDB 事务、表、reducer、procedure 留在 `spacetime-module`。 3. HTTP / SSE / BFF 留在 `api-server`。 4. 外部副作用留在 `platform-*` 或 `api-server` 应用编排层。 5. 前后端 DTO 留在 `shared-contracts`,跨领域纯值处理留在 `shared-kernel`。 本文件是后续编码前的总纲。若局部历史文档与本文冲突,以本文和对应 SpacetimeDB skill 为准;若本文仍无法精准指导某一处编码,必须先补本文或新增更细技术文档,再继续改工程。 ## 2. 依赖方向 允许方向: ```text api-server -> shared-contracts -> module-* -> spacetime-client -> platform-* spacetime-module -> module-* -> shared-kernel module-* -> shared-kernel -> 其他 module-* 的纯领域类型(仅在确有跨域规则复用时) platform-* -> shared-kernel shared-contracts -> shared-kernel ``` 禁止方向: 1. `module-*` 直接依赖 Axum、HTTP client、OSS、LLM、文件系统、SpacetimeDB table/reducer/procedure API。 2. `module-*` 新增 `mapper.rs`,映射只能落在 adapter crate。 3. `spacetime-module` 反向依赖 `api-server`、`spacetime-client` 或 `platform-*`。 4. `shared-kernel` / `shared-contracts` 依赖业务 crate。 5. 前端绕过 Axum 直接承接后端业务规则或数据真相。 阶段性例外必须满足三个条件: 1. 在本文“阶段性债务表”登记。 2. 有明确迁出目标 crate。 3. 不允许继续扩大例外范围。 ## 3. crate 职责矩阵 | crate | 职责 | 禁止内容 | | --- | --- | --- | | `module-ai` | AI 任务、阶段、流式片段、结果引用的纯领域模型和状态机 | 真实 LLM 调用、HTTP、SSE | | `module-assets` | 资产对象、资产绑定、历史查询输入输出和纯校验规则 | OSS head、reqwest、进程内 fallback store 扩散到领域核心 | | `module-auth` | 用户、会话、验证码、微信绑定、密码规则、领域错误 | 文件持久化、真实短信副作用、HTTP cookie 写入 | | `module-big-fish` | 大鱼创作会话、素材槽、运行态规则 | 图片生成、OSS 上传、HTTP handler | | `module-combat` | 战斗聚合、行动结算、奖励结果 | 直接写背包表、直接发放成长账本 | | `module-custom-world` | 世界 profile、Agent 会话、草稿卡、发布门禁规则 | LLM 推理、OSS、Axum response shape | | `module-inventory` | 背包、装备槽、堆叠与消耗规则 | SpacetimeDB table 操作 | | `module-npc` | NPC 关系、好感、互动、招募规则 | 战斗表初始化事务 | | `module-progression` | 玩家等级、章节预算、经验记账规则 | 查询前端视图拼装 | | `module-puzzle` | 拼图创作与运行态纯规则 | 图片生成、排行榜 HTTP shape | | `module-quest` | 任务领取、推进、完成、交付规则 | 奖励跨域副作用直接写表 | | `module-runtime` | 运行时设置、快照、个人页状态、存档领域模型 | 直接读写 SpacetimeDB | | `module-runtime-item` | 宝箱、奖励物品、运行时物品快照 | 背包持久化事务 | | `module-runtime-story` | RPG runtime story 新接口下的纯应用编排、剧情投影、场景旅行、战后收束、prompt context 投影 | Axum、LLM、SpacetimeDB | | `module-story` | story session、story event 与推进输入输出 | 直接调用 LLM 或 HTTP | | `spacetime-module` | 表、reducer、procedure、事务内查询写回、row/snapshot mapper、event table | 领域规则大段堆叠 | | `spacetime-client` | api-server 调用 SpacetimeDB 的客户端 facade 与绑定 mapper | 领域规则 | | `api-server` | 路由、鉴权上下文、请求响应映射、SSE、平台服务编排 | 表结构定义、领域规则主逻辑 | | `platform-*` | JWT/SMS/微信/OSS/LLM/HTTP client 等外部能力 | 玩法领域规则 | | `shared-kernel` | 字符串、时间、ID、纯值归一化 | 业务流程 | | `shared-contracts` | HTTP/前端 DTO、兼容 response shape | 领域状态机 | ## 4. 统一目录结构 所有 `module-*` 必须具备以下文件。第一阶段允许旧实现仍在 `lib.rs` 或历史子模块中,但新增和迁移代码必须进入对应落点。 ```text src/ ├─ lib.rs ├─ domain.rs 或 domain/ ├─ commands.rs ├─ application.rs ├─ events.rs └─ errors.rs ``` 落位规则: 1. `domain.rs` / `domain/*`:聚合、值对象、领域方法、纯校验、状态迁移。 2. `commands.rs`:写入用例输入,只表达意图和必要参数,不含 adapter 类型。 3. `application.rs`:纯应用编排函数,输出领域事件或应用结果,不执行外部副作用。 4. `events.rs`:领域事件和跨上下文事件,例如奖励待入账、画廊投影待刷新。 5. `errors.rs`:领域错误,优先可测试、可映射,不直接绑定 HTTP status。 6. `mapper.rs`:只允许在 `api-server`、`spacetime-module`、`spacetime-client` 等 adapter crate 中出现。 ## 5. 上下文设计清单 ### 5.1 认证 `module-auth` 聚合: 1. `AuthUser`:账号、公开百梦号、登录方式、绑定状态、token version。 2. `RefreshSession`:refresh token hash、客户端信息、过期、吊销、last seen。 3. `SmsVerification`:手机号、场景、验证码状态、冷却、失败次数。 4. `WechatBinding`:微信 provider 身份、union id、绑定状态。 命令: 1. `PasswordEntryInput` 2. `SendPhoneCodeInput` 3. `PhoneLoginInput` 4. `CreateRefreshSessionInput` 5. `RevokeRefreshSessionInput` 6. `ResolveWechatLoginInput` 事件: 1. `AuthUserCreated` 2. `RefreshSessionIssued` 3. `RefreshSessionRevoked` 4. `PhoneCodeAccepted` 5. `WechatIdentityLinked` 读模型: 1. `AuthMeResult` 2. `PublicUserSearchResult` 3. `AuthSessionListResult` 迁移要求:文件持久化和内存 store 从领域核心剥离到 adapter 或临时测试支撑;短信真实发送继续在 `platform-auth`。 ### 5.2 资产 `module-assets` 聚合: 1. `AssetObject`:bucket、object key、访问策略、hash、版本、业务归属。 2. `AssetEntityBinding`:实体类型、实体 id、slot、资产对象 id。 命令: 1. `ConfirmAssetObjectInput` 2. `AssetObjectUpsertInput` 3. `AssetEntityBindingInput` 4. `AssetHistoryListInput` 事件: 1. `AssetObjectConfirmed` 2. `AssetEntityBindingChanged` 读模型: 1. `AssetObjectUpsertSnapshot` 2. `AssetEntityBindingSnapshot` 3. `AssetHistoryEntrySnapshot` 迁移要求:OSS `head_object`、reqwest client 和 fallback store 不再放入领域核心;SpacetimeDB 持久化由 `spacetime-module` 完成。 ### 5.3 RPG 运行时 覆盖 `module-story`、`module-combat`、`module-inventory`、`module-npc`、`module-progression`、`module-quest`、`module-runtime-item`。 聚合: 1. `StorySession`、`StoryEvent` 2. `BattleState` 3. `InventorySlot` 4. `NpcState` 5. `PlayerProgression`、`ChapterProgression` 6. `QuestRecord`、`QuestLog` 7. `TreasureRecord` 跨上下文事件: 1. `CombatVictoryResolved` 2. `QuestTurnedIn` 3. `InventoryItemsGranted` 4. `ProgressionXpGranted` 5. `NpcRelationChanged` 6. `RuntimeStoryProjectionChanged` 事务边界: 1. 单聚合变更在对应 `module-*` 纯函数中完成。 2. 战斗奖励、任务奖励、背包写入、成长记账由 `spacetime-module` 或应用服务显式编排。 3. reducer/procedure 不允许复制领域规则,只负责取 row、调用领域函数、写回 row 和事件表。 ### 5.4 世界创作 `module-custom-world` 聚合: 1. `CustomWorldProfile` 2. `CustomWorldSession` 3. `CustomWorldAgentSession` 4. `CustomWorldAgentMessage` 5. `CustomWorldAgentOperation` 6. `CustomWorldDraftCard` 7. `CustomWorldGalleryEntry` 命令: 1. 创建/恢复 Agent 会话。 2. 写入用户消息。 3. 写入 LLM 最终回复。 4. 更新草稿卡。 5. 发布/下架 profile。 事件: 1. `CustomWorldDraftChanged` 2. `CustomWorldProfilePublished` 3. `CustomWorldGalleryProjectionChanged` 4. `CustomWorldAgentOperationProgressed` 迁移要求:LLM 提示词和推理在 `api-server + platform-llm`,SpacetimeDB 只落真相表和投影。 ### 5.5 拼图 `module-puzzle` 聚合: 1. `PuzzleAgentSession` 2. `PuzzleAgentMessage` 3. `PuzzleWorkProfile` 4. `PuzzleRuntimeRun` 事件: 1. `PuzzleDraftChanged` 2. `PuzzleWorkPublished` 3. `PuzzleRunAdvanced` 读模型: 1. 作品卡片。 2. 运行态快照。 3. 排行榜结果。 ### 5.6 大鱼吃小鱼 `module-big-fish` 聚合: 1. `BigFishCreationSession` 2. `BigFishAgentMessage` 3. `BigFishAssetSlot` 4. `BigFishRuntimeRun` 事件: 1. `BigFishDraftChanged` 2. `BigFishAssetSlotChanged` 3. `BigFishRunTicked` ### 5.7 AI `module-ai` 聚合: 1. `AiTask` 2. `AiTaskStage` 3. `AiTextChunk` 4. `AiResultReference` 事件: 1. `AiTaskStarted` 2. `AiTaskStageCompleted` 3. `AiTaskFailed` 4. `AiResultAttached` 边界:真实模型调用只在 `platform-llm`,`module-ai` 只表达状态机。 ## 6. SpacetimeDB adapter 映射 `spacetime-module` 中每个上下文遵循: ```text src// ├─ mod.rs ├─ tables.rs # table 和 public/event 标记 ├─ reducers.rs # 客户端可调用写入口 ├─ procedures.rs # 需要同步返回或外部 procedure 语义的入口 ├─ mapper.rs # row <-> module-* snapshot/input └─ queries.rs # 事务内查询辅助,只返回 adapter DTO ``` 当前已有历史拆分与本文不同名时,先按现有文档继续维护;新增或迁移时逐步对齐上面结构。 Reducer / procedure 规则: 1. reducer 使用 `&ReducerContext`,返回 `Result<(), String>` 处理预期错误。 2. reducer 内授权必须基于 `ctx.sender()`,禁止信任参数中的身份。 3. reducer 禁止网络、文件系统、外部随机数和全局状态。 4. procedure 使用 SpacetimeDB 2.0 正确 API;涉及事务必须显式 `with_tx` / `try_with_tx`。 5. 表访问必须使用 `ctx.db.table()`,更新只通过主键。 6. 复杂查询不复用写模型;需要前端订阅时新增明确读模型或 public 投影表。 ## 7. 表结构约束 默认不改现有 SpacetimeDB 主表。确需改表时按以下优先级: 1. 新增 optional 字段。 2. 新增投影表或 event table。 3. 新增索引。 4. 最后才考虑 rename/delete/type change,且必须单独写迁移方案。 任何 table 变更必须同步: 1. `server-rs/crates/spacetime-module/src/migration.rs` 2. `docs/technical/SPACETIMEDB_TABLE_CATALOG.md` 3. 对应 reducer/procedure 测试或最小 smoke 4. 绑定生成和前端/`spacetime-client` 映射 本阶段只做目录、文档和边界检查,不变更表结构,因此不需要改 `migration.rs`。 ## 8. 查询策略 1. 写模型不直接服务复杂前端页面。 2. 每个前端场景必须有独立 query/result DTO。 3. private table 默认不暴露给前端。 4. public table 只服务明确订阅场景。 5. event table 用于 reducer 后广播一次性事件,客户端必须显式订阅。 ## 9. 第一阶段落地范围 第一阶段只做低风险基础设施: 1. 新增本文作为 DDD 总纲。 2. 所有 `module-*` 补齐 `domain / commands / application / events / errors` 过渡落位文件。 3. 新增 `scripts/check-server-rs-ddd-boundaries.mjs`,检查 DDD 骨架、禁用 mapper 落位和 SpacetimeDB/Axum 绝对边界。 4. 更新文档索引和后端 README。 5. 不改表、不改 reducer/procedure 名、不改 HTTP contract。 ### 9.1 首个样板切片 `module-assets` 先作为 DDD 分层导出样板: 1. `domain.rs` 对外导出资产对象、实体绑定、访问策略、快照和纯校验函数。 2. `commands.rs` 对外导出确认资产、绑定实体、资产历史查询等输入。 3. `application.rs` 对外导出应用结果和纯构建函数。 4. `errors.rs` 对外导出资产领域错误。 5. `asset_object_core.rs` 暂作为内部历史实现文件保留,不再由 `lib.rs` 直接对外导出;后续触碰资产规则时继续把实现逐段迁到 DDD 文件。 ## 10. 阶段性债务表 | 债务 | 当前位置 | 迁出目标 | 约束 | | --- | --- | --- | --- | | 资产 OSS head 与 reqwest 服务仍在 `module-assets` server-service feature | `module-assets/src/asset_object_service.rs` | `api-server` 应用服务或独立 adapter crate | 不得被 `domain.rs` 引用,不得新增更多 OSS 领域规则 | | 认证内存 store / 文件持久化仍在 `module-auth` | `module-auth/src/lib.rs` | adapter / repository 目录或 `api-server` 启动恢复层 | 新业务规则不得继续依赖文件路径 | | `spacetime-module/src/lib.rs` 仍有大量 gameplay 入口 | `spacetime-module/src/lib.rs` | `src/gameplay/*`、`src/custom_world/*`、`src/puzzle/*` | 新增实现禁止继续堆回根入口 | | 部分 `module-*` 仍是单文件领域实现 | 多个 `module-*` 的 `src/lib.rs` | 对应 DDD 文件 | 每次触碰模块时顺手迁移相关片段 | | runtime story 兼容层仍存在 | `module-runtime-story-compat`、`api-server/src/runtime_story/compat*`、前端旧 runtime story client | `module-runtime-story`、session scoped 新接口、前端新 client | 本轮允许 breaking change,不再为旧 HTTP shape 保留双接口 | ## 11. 全局并行执行清单 `2026-04-29` 起,`server-rs` DDD 全局重构按 `SERVER_RS_DDD_PARALLEL_TASKLIST_2026-04-29.md` 执行。该清单覆盖: 1. 文档、契约、DDD 骨架和边界检查。 2. `module-auth`、`module-assets`、`module-ai`、`module-custom-world`、`module-big-fish`、`module-puzzle`、`module-runtime`、RPG gameplay 域和 runtime story 域。 3. `spacetime-module`、`spacetime-client`、`api-server`、`platform-*` 和前端接入。 4. 旧层删除、命名收口、全链验证和 Maincloud smoke。 ## 12. 去兼容层任务 `2026-04-29` 起,runtime story / chat 改造按 `SERVER_RS_DDD_PARALLEL_TASKLIST_2026-04-29.md` 中的 `WP-RS Runtime Story 去兼容层` 工作包执行。该工作包明确: 1. 当前 `compat` 层可以物理删除。 2. 新接口采用 `POST /api/runtime/story/sessions/:sessionId/...` 的 session scoped 口径。 3. 前端允许同步修改以匹配新 contract。 4. `api-server` 不再为旧 `worldType / character / monsters / history / context` 请求体保留正式主链分支。 ## 13. 验收命令 阶段验收至少执行: ```powershell node scripts/check-server-rs-ddd-boundaries.mjs cargo fmt --all --check --manifest-path server-rs/Cargo.toml cargo test --workspace --manifest-path server-rs/Cargo.toml cargo check -p spacetime-module --manifest-path server-rs/Cargo.toml npm run api-server npm run check:encoding ``` 若 `npm run api-server` 因本机未配置 Maincloud 数据库或令牌失败,必须记录具体错误;不能改用旧后端重启命令。