25 Layers And Boundaries

一个系统可以简单地被认为有三个部分构成:UI,业务逻辑和数据库。对于简单的系统,这就足够了,但是对于大部分的系统部件比这多的多。

Hunt the wumpus

让我们把这些抽象的概念具体化。假设这个游戏是1972年经典的《Hunt the Wumpus》(猎杀 Wumpus)冒险游戏。这是一款基于文本的游戏,使用非常简单的指令,比如“GO EAST(向东走)”和“SHOOT WEST(向西射击)”。玩家输入指令,计算机会反馈玩家所看到、闻到、听到和经历的一切。玩家在一个洞穴系统中猎杀 Wumpus,同时必须避开陷阱、深坑以及各种潜伏的危险。如果你感兴趣,这个游戏的规则在网上很容易找到。

现在假设我们仍然保留这种文本界面,但将其与游戏规则解耦,这样我们的版本就可以在不同市场使用不同语言。游戏规则将通过一种与语言无关的 API 与 UI 组件通信,而 UI 再将这些 API 转换为对应的人类语言。

如果源代码依赖关系管理得当(如图25.1所示),那么任意数量的 UI 组件都可以复用同一套游戏规则。游戏规则既不知道,也不关心使用的是哪种人类语言。
image

再假设游戏状态保存在某种持久化存储中——可能是闪存、云端,或者仅仅是内存。在这些情况下,我们都不希望游戏规则了解具体细节。因此,我们同样会创建一个 API,使游戏规则可以通过它与数据存储组件进行通信。
image

我们不希望游戏规则了解不同数据存储方式的任何细节,因此依赖关系必须按照依赖规则(Dependency Rule)正确指向,如图25.2所示。

Clean Architecture?

清洁架构:在这个场景中,我们当然可以很容易地应用清洁架构的方法,引入用例、边界、实体以及相应的数据结构,但问题是:我们真的找到了所有重要的架构边界吗?例如,UI 的变化维度不仅仅是语言,我们还可能改变文本的传递方式,比如使用命令行窗口、短信或聊天应用等多种形式,这就意味着这里还存在一个潜在的架构边界,也许我们应该构建一个 API,将“语言”和“通信机制”隔离开来;这样系统会变得更复杂,但本质上并不意外:抽象组件(虚线框)定义 API,由上下层组件实现,例如 Language API 由 English 和 Spanish 实现,GameRules 通过自己定义的 API 与 Language 通信,而 Language 再通过自己定义的 API 与 TextDelivery 通信,这些 API 的所有权属于调用方(上游组件)而非实现方;在 GameRules 内部,会有由自身使用、由 Language 实现的多态边界接口,同时也有由 Language 使用、由 GameRules 实现的接口;Language 内部同样如此,它定义的接口由 TextDelivery 实现,而 TextDelivery 使用的接口由 Language 实现;这些 API 都由上游组件定义,而具体变化(如 English、SMS、CloudData)则通过多态接口在抽象 API 中定义,并由具体组件实现。进一步简化后可以只保留 API 组件,并将结构按“依赖向上”排列,使 GameRules 位于顶部,因为它包含最高层策略。从数据流看,用户输入从左下角的 TextDelivery 进入,经 Language 转换为命令传递给 GameRules 处理,再将结果发送到右下角的数据存储,同时输出也从 GameRules 返回到 Language,再经 TextDelivery 展示给用户,这样系统被划分为两条数据流:左侧负责用户交互,右侧负责数据持久化,两者在顶部的 GameRules 汇合。但系统不一定只有两条流,例如如果加入网络实现多人游戏,就会增加第三条数据流,由 GameRules 统一控制;随着系统复杂度提升,这种“流”会不断增加,而且这些流并不一定最终只汇聚到一个组件,例如在 Wumpus 游戏中,地图与移动属于较低层策略,而玩家状态(生命值、奖励惩罚等)属于更高层策略,前者产生事件(如找到食物或掉入陷阱),后者根据这些事件更新玩家状态并决定胜负,这本身也可能形成架构边界;如果进一步扩展为多人游戏,并将玩家状态管理放到服务器上,通过微服务 API 提供给本地的移动管理组件,那么两者之间就形成了一个完整的架构边界。这个例子的意义在于说明:架构边界无处不在,架构师需要识别哪些边界是必要的,但也必须意识到完整实现这些边界成本很高;同时,如果一开始忽略这些边界,后期再补的成本和风险也同样巨大。因此架构设计需要在 YAGNI(不要过度设计)与前瞻性之间权衡:过度设计可能更糟,但缺失必要边界也代价高昂。架构师的职责是“理性预测未来”,判断哪些边界需要完全实现、部分实现或暂时忽略,而且这不是一次性决策,而是随着系统演进不断观察,当出现摩擦(缺少边界带来的问题)时,比较实现边界的成本与忽略它的成本,在两者交叉点做出决策,及时引入边界,这需要持续的关注与判断力。

posted @ 2026-03-21 14:54  cyusouyiku  阅读(3)  评论(0)    收藏  举报