为什么需要 Layering?
即使没有有意识的 Layering,一个应用也能被开发出来。Component 可以加载数据,Service 可以保存 State,Store 可以直接发 HTTP,请求之外的某个地方再塞进 Business Logic。
它会工作。
至少会工作一段时间。
但随着应用增长,另一个问题会越来越重要:某一项职责到底应该放在哪里?
Layering 就从这里开始。
Layering 不是一个新概念
Section titled “Layering 不是一个新概念”把应用拆分成不同职责区域,是软件架构中很成熟的一类基本思想。
例如 Martin Fowler 在 Presentation Domain Data Layering 中讨论了表现、业务逻辑与数据访问之间的分离。其中一个重要价值是:开发者每次只需要关注系统的有限部分。处理业务逻辑时,不必同时理解 UI 细节和数据获取机制。
Domain-Driven Design 则会使用 User Interface、Application、Domain 和 Infrastructure 等概念——Eric Evans 在 DDD Reference 中描述了这些层次。其他架构方法会采用不同表达。Alistair Cockburn 的 Hexagonal Architecture 与其说强调上下层,不如说强调内部与外部:业务逻辑不应该依赖它是由 GUI、Test 还是其他技术 Adapter 调用,也不应该依赖某个具体 Database 或外部 Service。
名称、边界和图示可能不同,但核心思想相近:
不同职责不应该不受控制地混在一起。
灵感,而不是教条
Section titled “灵感,而不是教条”我理解前端 Layering 的一个重要灵感来源,是 Manfred Steyer 的相关工作,尤其是他对 DDD 启发下 Angular 与前端架构的讨论。
本系列并不会原样复制这些方法,而是把这些思想与真实项目、长期维护以及开发团队协作中的经验结合起来。
我的解释在一些地方有意偏离更经典的 DDD 模型。最明显的是 +state:它承担了很大一部分业务职责。Business Rule、业务派生和 State Transition 都放在这里。Application 层则刻意保持轻薄,并且更靠近具体 View。
因此,后面的文章不是为了定义唯一正确的前端 DDD Layering。它们描述的是一个受 DDD 启发、在实践中经过长期使用的模型,主要想回答一个每天都会遇到的问题:
哪项职责应该放在哪里?
真正的问题不是文件夹
Section titled “真正的问题不是文件夹”Layering 往往最先以目录结构的形式出现。很多人从经典 Backend 应用中熟悉类似结构:
src/├── controllers/├── services/├── repositories/└── entities/Controller 接收 Request,Service 协调 Application Logic,Repository 负责 Persistence。不同职责首先拥有了不同位置。
这种结构很有帮助,但它本身还不是架构。
目录只说明“计划有哪些职责区域”。最终能不能形成可持续的架构,要看代码是否真正遵守这些边界。
每个区域都必须清楚:它负责什么,以及职责在哪里结束。只有团队能够稳定理解并遵守这些边界,文件夹结构才会真正变成架构。
减少日常决策
Section titled “减少日常决策”好的架构不会消灭所有讨论。但它应该避免每做一个 Feature,都重新讨论同一批基础问题。
Business Rule 放哪里?
谁可以发 HTTP?
ViewModel 在哪里产生?
Component 可以直接访问 Store 吗?
谁负责聚合多个业务数据流?
已有 State 的派生逻辑应该放哪里?
如果团队没有共同规则,这些问题就会由不同开发者、不同 Feature 给出不同答案。架构最终不是被设计出来,而是由一连串局部决定慢慢堆出来。
清晰的 Layer Boundary 提供护栏。不是每个决定都需要重新做一次。
这样既能减少争论,也能让 Codebase 更可预测。打开一个陌生 Feature 之前,开发者就应该对某类逻辑大概会在哪里有基本判断。
Layering 缩小思考空间
Section titled “Layering 缩小思考空间”这个效果经常被低估。
如果我正在处理表现层,而且知道这里不允许实现 Business Rule,那么这一刻我就不需要理解应用的大量其他部分。
如果我要修改一条业务规则,我不希望同时思考当前到底用了哪个 UI Framework。
如果我要接入 Backend 数据,也不应该需要关心以后究竟由哪个 Button 改变这些数据。
Layering 会缩小一次具体修改所需同时放进脑中的上下文。这不仅帮助实现,也帮助 Debugging、Review、Onboarding 和 Refactoring。
只要职责与位置清晰,一个人就不必理解整个系统,才能安全地做局部变化。
这一点对人有效——而且越来越也对工具有效。
给 AI Agent 的护栏
Section titled “给 AI Agent 的护栏”在 AI 辅助开发中,这个特性又多了一层价值。
Agent 可以非常快地生成代码。但它并不会自动知道某个具体项目的架构决策。如果边界不清晰,一开始会有很多技术上都“说得通”的方案:
HTTP 放 Store?Business Rule 放 Component?Mapping 放 Facade?Navigation 从 State 发起?这些方案技术上都可以实现。真正重要的是:哪一种符合这个应用自己的架构。
清晰定义的架构会缩小可接受的解空间。
如果规则已经明确:Business Rule 放在 +state,基础设施层封装技术外部世界,表现层不包含业务逻辑,那么这些规则不仅可以教给人,也可以进入 Architecture Documentation、Agent Instruction、Generator、Dependency Constraint、Review 和 Static Check。
这样每次生成一段代码时,就不必重新讨论架构。
Agent 得到的不只是:
实现这个 Feature。
还应该得到:
在这套架构边界内实现这个 Feature。
代码生成得越快,限制其结构的规则就越重要。
分离创造选择空间
Section titled “分离创造选择空间”清晰边界不只是为了整齐。
它会创造选择。
如果 Business Logic 不在 Component 中,同一套业务能力就可以被不同 UI 消费。Angular Material 的表现层、Ionic 的表现层,或者未来其他 UI,都不需要重新实现第二份业务决策。
这不是纯理论。在我的一个长期项目中,Angular Material 和 Ionic 曾在相当长时间内共享同一套业务基础并行运行。
它之所以可行,是因为边界被持续遵守:表现层可以展示业务结果、接收用户交互,但不能发展出第二套 Business Logic。
不过,可替换性本身不必成为架构目标。没有人应该仅仅因为“未来理论上可能换 UI Framework”,就把今天的系统设计得更复杂。
可替换性更适合作为一种边界质量测试:
如果只更换表现形式,或者只更换某个技术接入,究竟需要改多少代码?
如果大量与这项变化无关的业务代码也受到影响,说明职责分离得并不干净。
技术通信同样如此。如果基础设施层封装得清楚,业务层不需要知道数据最终来自 HTTP、Local Storage 还是其他机制。
如果业务 State 有明确归属,派生和规则也不需要分散在 Component、Service 和 View 中。
本系列采用的前端 Layering
Section titled “本系列采用的前端 Layering”后面的文章使用一个有意面向前端、并且与经典 DDD 有所不同的模型。
一个 Feature 的组织结构例如:
my-feature/├── domain/│ ├── infrastructure/│ ├── +state/│ └── application/├── models/└── presentation/这里的 domain 是 Feature 非视觉核心的组织性总称,并不等同于经典 DDD 中严格意义上的 Domain Layer。
目录结构本身也不代表数据流。
接下来的文章会沿着数据从技术外部世界走到用户界面时经过的边界来观察:
技术外部世界 ↓Infrastructure ↓ +State ↓ Application ↓ Presentation ↓ 用户每个区域都有清楚限定的职责。
基础设施层封装技术外部世界,把技术通信转换成 Feature 可以使用的形式。
+State 表达业务相关的运行时 State。Business Rule、State Transition、业务派生和 Selection 都放在这里。
Application 刻意保持轻薄。每个 View 由自己独立的 Facade 收集已有数据流,并向表现层暴露一个稳定、面向该 View 的 Contract。
表现层负责展示和用户交互,但不会再次解释或重做业务规则。
models 有一个特殊位置。有些模型会有意跨越 Layer Boundary。例如 ViewModel 可以由 Application 产生、由表现层消费,而不必因此完全“属于”其中任何一层。
这个模型不是普遍真理,也不是试图把 Domain-Driven Design 原封不动移植到前端。
它是我对前端 Layering 的解释,受 Manfred Steyer 等工作的影响,也加入了真实项目和长期使用中的经验。
最重要的是,它提供了一组护栏:职责从哪里开始,又在哪里结束。