Files
erqiwang_youle/docs/games/engineering/01-架构总则与分层.md
T
2026-07-06 17:16:05 +08:00

9.0 KiB
Raw Blame History

01 · 架构总则与分层

本篇给出七大架构总则,并落到一套前后端参考分层上。总则是"为什么这么设计",分层是"具体长什么样"。 所有分层里的模块名均为示例、可自定,约束的是层与依赖方向,不是名字。


1. 七大架构总则

1.1 单一权威数据源(Single Source of Truth)

同一业务数据只能有一个计算/写入的地方,其余模块只读取、不重算;多处并行计算必随规则演化而分叉、矛盾。

  • 上游算 + 写 + 校验,下游只读 + 消费。
  • 需要某数据时,读权威源,而不是"顺手再算一遍"。
  • 详见 03 篇 · 数据权威。

1.2 单向依赖,禁止环

分层之间自上而下单向依赖:入口依赖编排、编排依赖领域、领域依赖数据/共享;反向不依赖。

  • 稳定依赖原则:越被依赖的层越应稳定(领域/共享层最稳定,入口层最易变)。
  • 禁止环形依赖:A 依赖 B、B 又依赖 A,是"职责没分清"的信号,应抽出共同依赖或调整边界。
  • 双运行时下尤其致命:环依赖 + 中途 require 会在浏览器端崩溃(见 development-guide 的 require 守卫)。

1.3 职责单一、边界清晰

一个职能只在一个模块实现,别的模块需要它就调用,不"图方便"复制一份近似逻辑。

  • 判据:写代码前先问"这段逻辑属于谁的职责"。属于别人的,就调用它。
  • 权威能力不满足时,在权威模块内扩展,不要在调用方旁路重写。
  • 反例:在 A 模块内联 B 模块核心算法的"简化版";同一职能两处并行演化。

1.4 关注点分离(Separation of Concerns)

把"不同变化原因"的代码分开,让每块只因一个原因而改:

分离维度 一侧 另一侧
决策 vs 机制 "选哪个"(策略/决策) "怎么做"(算法/规则/执行)
数据 vs 表现 状态模型(真相) 渲染/UI(从数据重建)
编排 vs 算法 流程编排(controller) 纯计算(领域算法)
输入 vs 逻辑 收发包/参数校验 业务处理

例(决策 vs 机制):自动操作模块只决定"打哪张/是否碰杠胡过",不实现胡牌检测/听牌分析;后者是领域算法,决策层只读取其结果。既是关注点分离,也是职责边界。

1.5 对扩展开放、对修改封闭(OCP)

新增一种玩法/规则/策略/牌型,应当是新增一段代码并注册,而不是去改动已经稳定、已被测试覆盖的核心流程。

  • 手段:注册表 + 策略、管线/中间件、工厂(见 02 篇)。
  • 例:分级决策框架核心是"取上下文 → 跑管线 → 选结果",新增高级策略只是写策略对象 + 注册 + 单测三步,核心零改动。

1.6 配置优先于硬编码

会变的、复用的、无语义的值不写死在逻辑里,外提为常量/配置,用数据驱动行为。

  • 分数、阈值、类型字符串、跨模块 key → 常量;成套玩法开关 → 配置对象/规则编码。
  • 让"改规则"变成"改配置",而不是"改代码 + 重测逻辑"。
  • 详见 02 篇 · 配置化与去硬编码。

1.7 显式失败优于隐式兜底

关键路径上数据缺失,优先报错或返回 null,把问题暴露在离根因最近的地方;不要用 || 0、|| []、|| ''、双源回退把缺失悄悄填平。

  • 兜底会把 bug 藏进"看似正常"的流程里,到很远的下游才爆发,极难定位。
  • 只有展示层、纯 UI 兼容、非关键日志字段,才可在明确边界内用安全默认值。
  • 详见 03 篇。

2. 前后端参考分层

下面是一套可直接照搬思路的分层。层次是稳定的,层内文件如何组织由子游戏自定。

2.1 后端分层(自上而下依赖)

┌─────────────────────────────────────────────┐
│ 入口层  Entry      收包薄委托:一操作一入口,只接住并转交       │  易变
├─────────────────────────────────────────────┤
│ 收发层  IO         参数校验、响应构建、序列化、差异化广播        │
├─────────────────────────────────────────────┤
│ 编排层  Orchestrate 流程编排、状态机推进、跨模块协调(不含算法)   │
├─────────────────────────────────────────────┤
│ 领域层  Domain      规则/算法权威实现(胡牌/听牌/计分/牌型…)     │
├─────────────────────────────────────────────┤
│ 数据层  Data        对局状态、状态管理、统一访问层             │
├─────────────────────────────────────────────┤
│ 共享层  Shared      前后端逐字相同的纯逻辑与常量               │  最稳定
└─────────────────────────────────────────────┘
        依赖方向:上层 → 下层(下层绝不反向依赖上层)
  • 入口层薄:只做"接住请求 → 委托",不写业务;一操作一入口,不做二次路由。
  • 领域层是权威:所有"能不能胡/听什么/怎么算分"的算法只此一份,别层调用。
  • 数据层是唯一状态落点:对局状态集中管理,读写走统一访问层,避免散落。
  • 共享层最底、最稳定:见 [03 篇 · 数据权威] 与各端 development-guide 的 shared 说明。

2.2 前端分层(自上而下依赖)

┌─────────────────────────────────────────────┐
│ 入口层  Entry       平台受限入口:只解包转交,不写业务          │  易变
├─────────────────────────────────────────────┤
│ 分发层  Dispatch    唯一收包口,按 rpc 路由到处理器(纯路由表)   │
├─────────────────────────────────────────────┤
│ 处理层  Controllers 一类消息一处理器:改数据模型 / 触发表现       │
├─────────────────────────────────────────────┤
│ 状态层  Model       前端状态真相(界面从它无歧义重建)          │
├─────────────────────────────────────────────┤
│ 表现层  View/Managers 渲染、动画、音频、资源(从状态派生)        │
├─────────────────────────────────────────────┤
│ 共享层  Shared      与服务端同源的纯逻辑(只读同步副本)         │  最稳定
└─────────────────────────────────────────────┘
        表现从状态派生:任何时刻重画都能还原正确界面
  • 入口只转交:平台受限入口不堆业务,逻辑全在处理层。
  • 收包分发只分发:一张 rpc → 处理器 路由表,不在分发口写业务。
  • 数据优先、表现延后:先落状态模型,再由表现层派生;重连=重画,与正常对局复用同一重画路径。

2.3 依赖方向自检

  • 有没有"下层反过来 import/引用上层"?有 → 边界错位,重划。
  • 有没有"两个模块互相依赖"?有 → 抽公共依赖或合并职责。
  • 有没有"入口层里写了算法/规则"?有 → 下沉到领域层。
  • 有没有"表现层里存了业务真相"?有 → 上移到状态层。

3. 本篇小结

  • 七大总则:SSOT、单向依赖、职责单一、关注点分离、OCP、配置优先、显式失败。
  • 后端六层(入口/收发/编排/领域/数据/共享)、前端六层(入口/分发/处理/状态/表现/共享),依赖单向向下。
  • 层是稳定的,层内组织自定;判断对错的尺子始终是依赖方向与职责归属。

下一篇 02-可扩展性与配置化:把 OCP 与"配置优先"落成具体模式与判据。