Application——面向 View 的 Domain API
+State 文章 已经为业务逻辑提供了明确归属:业务相关 State、Business Rule、派生、Selection、State Transition 和业务 Intent 都在那里。
但一个具体 View 通常只需要其中很小的一部分:
customersloadingselectCustomer()createCustomer()Application 从这里开始。
如果这一层变得复杂,通常意味着某些原本属于它下面或上面的职责滑了进来。
有意识地偏离经典 DDD
Section titled “有意识地偏离经典 DDD”在经典 DDD Layering 中,Application 层常常与 Application Service、Use Case 以及它们的协调联系在一起。它会控制一个 Use Case 的执行,但不拥有真正的 Business Rule。
这里的模型把边界切得更窄。业务决策、State Transition 和 Projection 已经存在于 +State,因此在它上面不会再建立第二层 Use-Case Orchestration。
所以 Application 刻意比很多经典 DDD 架构更薄。理想情况下,这一层几乎只由 Facade 组成。
这不是偷懒,而是有意识的前端解释:View 需要一个稳定入口访问 Domain,但不需要再多一个地方重新产生业务逻辑。
Facade 是进入 Domain 的入口
Section titled “Facade 是进入 Domain 的入口”Presentation 不应该需要知道 Domain 内部也许由 CustomerState、OrderState、PermissionState 和 SelectionState 组成。
没有 Facade 时,一个 View 可能直接访问多个内部区域:
customerState.customers;customerState.selectedCustomer;orderState.canOrder;permissionState.canEdit;这样一来,Domain 的内部切分就变成了 Presentation 必须知道的知识。
有了 Facade,View 只需要看到:
facade.customers;facade.selectedCustomer;facade.canOrder;facade.canEdit;Facade 了解下面的结构。View 只了解自己的 Contract。

这个基本思想来自经典 Facade Pattern:Facade 为一个更复杂的 Subsystem 提供更简化的入口(Design Patterns: Elements of Reusable Object-Oriented Software,Gamma/Helm/Johnson/Vlissides,1994)。这里把同一原则应用到 Presentation 与 Domain 之间的边界。
如果熟悉经典 MVC,可以大致把这个位置理解为靠近 View 的 Controller:UI 与 Model 之间有一个明确入口。但这里 Facade 的职责更窄——它不会发展出自己的业务逻辑或 Application Logic。
Facade 负责暴露和委托
Section titled “Facade 负责暴露和委托”Facade 可以收集 +State 中已有的值和数据流,并通过合适的 Contract 暴露出去:
export class CustomerDetailFacade { readonly customer = this.customerState.selectedCustomer; readonly canDelete = this.customerState.canDelete; readonly loading = this.customerState.loading;
deleteCustomer(): void { this.customerState.deleteSelected(); }}这是一个好的 Facade。它负责暴露、委托,并隐藏内部结构。它没有产生新的业务含义。
同一个 Contract 中可以并列暴露多个已经存在的 Stream 或 Reactive Value。Facade 不应该做的是:在这里才第一次计算出新的业务结论。
readonly canOrder = combineLatest([ this.customerState.customer$, this.orderState.orders$, this.permissionState.permissions$,]).pipe( map(([customer, orders, permissions]) => { // 业务决策在这里才产生 }),);如果多个数据源共同形成一条业务结论,这条结论就应该已经存在于 +State。
Application 只暴露结果:
readonly canOrder = this.orderState.canOrder;Facade 可以收集多个数据流。
它不负责在业务上把这些流解释成新的含义。
不要逻辑。就是这样。
Section titled “不要逻辑。就是这样。”Facade 不做决策。
Business Rule 不属于这里:
save(): void { if (this.customerState.customer().status === CustomerStatus.Active) { // ... }}业务派生也不属于这里:
readonly visibleCustomers = computed(() => this.customerState.customers() .filter(...) .sort(...),);前提是 Filter 与 Sort 属于 State 或具有业务意义的 Projection。
UI 职责同样不属于这里:
delete(): void { if (confirm('Really delete?')) { // ... }}Facade 可以做的是简单委托:
deleteCustomer(): void { this.customerState.deleteSelected();}或者暴露已有 State:
readonly canDelete = this.customerState.canDelete;这些方法看起来“太简单”是刻意的。如果 Facade 开始变得有趣,就值得检查:是不是有别的 Layer 的职责正在流进来。
那为什么还需要 Facade?
Section titled “那为什么还需要 Facade?”如果 Facade 只是暴露和委托,Presentation 理论上也可以直接访问 +State。
代价是:它开始了解 Domain 的内部结构。
今天可能只是:
CustomerListComponent ↓CustomerState新的需求一来,依赖就开始增加:
CustomerListComponent ├── CustomerState ├── PermissionState ├── SelectionState └── OrderState这样,Domain 内部如何拆分就变成 View Contract 的一部分。
有 Facade 时,View 始终只有一个入口:
CustomerListView ↓CustomerListFacade ↓Domain 内部结构Domain 下面怎么重新切分,Presentation 不需要跟着理解。
这就是这层看似“没什么代码”的结构真正价值:降低结构性耦合。
一个 View,一个 Facade
Section titled “一个 View,一个 Facade”这个模型里的规则非常简单:
每个 View 都有且只有一个对应 Facade。
CustomerListView ↔ CustomerListFacadeCustomerDetailView ↔ CustomerDetailFacadeCustomerEditView ↔ CustomerEditFacade一开始,一个通用 CustomerFacade 服务所有 Customer View,看起来很诱人:
CustomerListCustomerDetailCustomerEditor ↓CustomerFacade但随着 View 增加,它的 Contract 也会一起膨胀:
CustomerListCustomerDetailCustomerEditorCustomerHistoryCustomerSearchCustomerAdminCustomerExport ↓CustomerFacade最初只有 customers 和 selectedCustomer,后来会不断加入 history、orders、permissions、search、filter、sort、create、update、delete、archive、export 等能力,而每个 Consumer 往往只需要其中一小部分。
Facade 最终变成 God Service。
真正的问题是缺少自然边界。三个、五个还是十个 View,都不是有意义的架构阈值。某个“共享 Property 百分比”也不可能提供稳定规则。
1:1 关系刻意把这件事变得非常简单:
Facade 的职责边界,就是它所服务 View 的职责边界。
不需要 Size Heuristic,也不需要争论 Property 最多允许多少个。View 本身就是边界。

有意识地允许重复
Section titled “有意识地允许重复”1:1 规则必然会产生一些相似、甚至完全一样的 Facade:
export class CustomerListFacade { readonly customers = this.customerState.customers;}export class CustomerSearchFacade { readonly customers = this.customerState.customers;}这是故意的。
两个 Contract 可以独立演进。今天它们恰好暴露同一个 Projection,不意味着应该把两项职责绑定到一起。
重复委托很便宜。耦合职责很昂贵。
这不是反 DRY。真正要看的仍然是 Change Reason。CustomerListFacade 在 Customer List Contract 变化时修改,CustomerSearchFacade 在 Search View Contract 变化时修改。
因此 Facade 回答的不是:
我们的应用对 Customer 一共能做什么?
而是:
这个具体 View 需要从 Domain 得到什么?
CustomerListFacade、CustomerDetailFacade、CustomerEditFacade 这样的命名会把这项职责直接显示出来。Facade 是一份面向 View 的 Domain API,只暴露这个 View 被允许消费的内容。
Application 可以做什么,不该做什么
Section titled “Application 可以做什么,不该做什么”Application / Facade 可以:
- 消费
+State - 暴露已有 State 和 Projection
- 收集已有 Stream
- 把 Intent 和 Command 委托给
+State - 提供面向具体 View 的 Contract
- 向 Presentation 隐藏 Domain 内部结构
- 把多个内部 Domain Source 封装在一个 View Contract 后面
- 暴露已经生成的 ViewModel 或 Projection
Application / Facade 不应该:
- 实现 Business Rule
- 做业务决策
- 生成新的业务 Derived State
- 在这里才从业务上组合 Stream
- 建模自己的 State Transition
- Parse DTO
- 调用 HTTP 或外部 SDK
- 解释技术 Contract
- 控制 Router
- 打开 Dialog、Toast 或 Snackbar
- 了解 DOM 或 UI Framework 细节
- 做 Material、Ionic 或 Bootstrap 专属决策
- 为了复用,把多个 View 塞进一个共同的 God Facade
价值在边界,而不在代码量
Section titled “价值在边界,而不在代码量”一个 View 只拥有一个进入 Domain 的入口。它不需要知道单个值来自哪里,也不需要知道 Contract 后面有几个 Store 或 Projection。Domain 可以内部重构,而不迫使每个表现层一起理解这次变化。
Change Reason 也因此保持局部。Customer Editor 新增能力,不会自动扩大 Customer List 的 Contract。每个 Facade 只需要和它对应的 View 一样大。
这会在一些地方造成 Delegation 重复。但那是独立 Contract 的成本,而且成本很低。
所以 Application 层真正的收益,不来自里面有多少“聪明代码”。
而来自这些代码让边界变得可见。
不制造人工复杂度
Section titled “不制造人工复杂度”一个现实中的 Application Layer 可能几乎只包含这样的类:
export class CustomerListFacade { readonly customers = this.customerState.customers; readonly loading = this.customerState.loading;
selectCustomer(id: CustomerId): void { this.customerState.select(id); }
createCustomer(): void { this.customerState.create(); }}这并不说明这一层“没有必要”。价值在 Contract 和解耦结构,不在代码数量。
好的 Facade 很无聊。它不会重新发明业务逻辑,只会让已有业务能力以适合一个具体 View 的方式被访问。
+State 拥有业务逻辑。Application 为一个具体 View 提供合适 Contract。Domain 不知道 View。Facade 了解两边,但不会创造新的业务含义。
Contract 的另一侧,就是 Presentation。