14.3 前端控制器

以一个入口处理站点全部请求,集中认证、路由、错误处理和公共策略,再分派到具体动作。

学习目标

  • 能解释前端控制器如何把共同请求工作集中到一个入口,并指出它与具体业务动作的边界
  • 能修改一个 TypeScript 路由入口,让认证、日志和分发顺序可测试且不会把业务规则塞进入口
  • 能回答:当一个请求绕过统一入口时,哪一条安全或审计证据会消失,应该拒绝还是继续扩张中央控制器

先从一条请求开始

想象订单后台只有一个服务台:每位访客先在这里登记身份、说明要办的事,服务台再把人带到查看、编辑或删除窗口。这样,所有窗口都遵守同一套进门规则;如果访客可以从后门直达某个窗口,登记和记录就会出现缺口。

本章解决的就是这个“先过同一扇门,再去办具体事情”的问题。没有它时,每个页面都要重复检查身份、记录日志和判断语言,规则会逐渐不一致;有了它,也必须警惕这扇门变成包办一切的巨大房间。

先建立可验证的边界

把目标写成一条可追踪的链:HTTP 请求 → 统一入口 → 共同策略 → 具体动作 → HTTP 响应。每一段都要有一个可回答的问题:谁解释输入?谁记录安全事件?谁读取订单?谁决定返回哪种页面?如果同一段业务规则在入口和动作对象各写一遍,边界已经开始泄漏。

什么是前端控制器

是站点请求处理的共同门槛。它不等于“所有代码都放在一个类里”,而是让每条请求先经过同一个 handler,再由该 handler 选择请求专属的命令对象。Martin Fowler 的公开模式摘要也把核心描述为:单一 handler 统一处理请求,再分派给命令对象完成特定行为。

它通常负责解析 method 与 path、建立请求上下文、执行共同策略、查找路由并转交;它不应该直接写“订单能否取消”这类领域判断。这样做的收益是横切规则只有一个入口,运行时可用装饰器追加行为;代价是入口会增加间接层,路由表和错误处理也需要测试。

三个职责层次

共同策略只做一次

包括认证、授权、日志、国际化和统一错误映射。它们放在入口附近,是为了让查看订单和编辑订单不会各自发明一套登录检查。共同并不意味着随便放:入口可以验证“谁在请求”,但不能替订单决定“这个状态是否允许转换”。后者应留给领域服务或命令对象。

变化留在路由表

GET /orders/42POST /orders/42/cancel 映射到不同处理者。把映射显式化有两个好处:新增页面时只改一处,测试可以直接验证“请求去了哪里”;也有一个边界:路由表只能做选择,不能在每个分支中偷偷执行完整业务流程。

具体动作交给命令

承接查看、编辑、删除等具体动作。它可以调用应用服务和领域模型,完成输入校验、状态变更与视图选择;前端控制器只关心把请求交给谁。若命令对象之间还重复认证和日志,说明共同策略没有真正集中,应该先修入口契约。

订单后台的请求流

先预测:如果把“记录审计事件”复制到三个页面处理者里,新增第四个页面时最可能漏掉什么?打开下方主图,按步骤看共同工作如何只出现一次;再点“注入绕过故障”,观察后门调用为何破坏证据链。

专属机制图 · 入口集中
订单后台:一个入口吸收共同请求工作统一边界负责解释与守门,具体命令负责业务动作浏览器请求GET /orders/42Front Controller解释请求 · 检查边界 · 选择命令认证 / 日志 / 国际化路由表:method + path → handler不直接编写订单业务规则共同工作在这里集中一次命令对象ViewOrderCommandEditOrderCommandDeleteOrderCommand单一入口共同策略只执行一次认证 · 日志 · 国际化选择 handler失败路径:旧页面入口绕过 Front Controller认证与审计没有共同证据;修法是收紧入口,而不是把更多业务塞进中央控制器。当前步骤:入口集中 · 所有订单请求先穿过同一个边界,后续观察才有共同起点。Front Controller → Command

第 1 / 4 步 · 请求先到同一个入口,而不是散落到每个页面

按步骤观察共同工作和具体动作的边界;再注入故障,检查是否有人绕过入口。

前端控制器把共同请求工作留在单一入口,再把具体动作交给命令对象;入口越过边界时,安全与审计就无法统一验证。

三个静态快照

主图适合连续观察;下面的三张快照把同一条链拆开,方便逐步核对每个边界。每一步都保留该阶段的图示,不用记忆纯文字描述。

分步1 / 3

1. 请求先到统一入口

浏览器只提交请求,入口先接住它;此时还没有选择具体订单动作。

专属机制图 · 入口集中
订单后台:一个入口吸收共同请求工作统一边界负责解释与守门,具体命令负责业务动作浏览器请求GET /orders/42Front Controller解释请求 · 检查边界 · 选择命令认证 / 日志 / 国际化路由表:method + path → handler不直接编写订单业务规则共同工作在这里集中一次命令对象ViewOrderCommandEditOrderCommandDeleteOrderCommand单一入口共同策略只执行一次认证 · 日志 · 国际化选择 handler失败路径:旧页面入口绕过 Front Controller认证与审计没有共同证据;修法是收紧入口,而不是把更多业务塞进中央控制器。当前步骤:入口集中 · 所有订单请求先穿过同一个边界,后续观察才有共同起点。Front Controller → Command
前端控制器把共同请求工作留在单一入口,再把具体动作交给命令对象;入口越过边界时,安全与审计就无法统一验证。

代码实践:让入口可测试

下面是一段最小 TypeScript 草图。它把共同策略、路由选择和具体动作分成可替换的函数,便于在测试中分别验证“未登录会被拒绝”和“已登录请求到达正确命令”。这不是某个 Web 框架的配置,而是模式边界的可执行表达。

type Request = { method: string; path: string; userId?: string };
type Response = { status: number; body: string };
type Context = { request: Request; audit: (event: string) => void };
type Handler = (context: Context) => Response;
 
const routes: Record<string, Handler> = {
  "GET /orders/42": ({ audit }) => {
    audit("order.view");
    return { status: 200, body: "order detail" };
  },
  "POST /orders/42/cancel": ({ audit }) => {
    audit("order.cancel");
    return { status: 202, body: "cancel requested" };
  },
};
 
export function frontController(request: Request): Response {
  if (!request.userId) return { status: 401, body: "sign in required" };
  const handler = routes[`${request.method} ${request.path}`];
  if (!handler) return { status: 404, body: "route not found" };
  return handler({ request, audit: (event) => console.info(event) });
}

这里的入口只完成三件事:先守住身份边界,再查表,再转交。真实项目还应把 audit 和命令注册表注入,而不是在入口里直接创建数据库连接或写复杂领域规则。一个有价值的单元测试可以替换 audit,断言一次合法请求只记录一次事件;另一个测试可以传入不存在的路径,确认不会调用任何命令。

什么时候应该停下来拒绝

单一入口不是越集中越好。可以采用它的信号是:多个请求确实共享认证、日志或语言策略,且这些规则需要一致变更。应重新评估的信号是:入口开始读取订单表、拼装每个页面的全部数据、处理每个状态转换,或者一个小页面改动会触及中央控制器的许多业务分支。

此时可把策略拆成中间件、把领域规则移入应用服务、把页面差异留给命令对象。若团队只是因为框架默认提供一个全局 controller 就把所有逻辑塞进去,那不是模式证据;评审应要求一条请求流和一组能证明入口边界的测试。

常见误区

本章小结

  • 前端控制器是所有请求共同经过的单一入口。
  • 横切关注点在入口集中一次,避免页面处理者各自复制。
  • 路由表负责选择,命令对象负责具体业务动作。
  • 入口应守住边界,不应演变成业务总管。
  • 绕过入口会让安全与审计失去共同证据。

练习

练习

问题 1:补全入口边界。 在代码草图中加入一个 audit 断言:合法的 GET /orders/42 只能通过 frontController 到达 ViewOrderCommand,不能由测试直接调用命令来代替入口测试。

问题 2:诊断故障。 删除页绕过统一入口,自己从 cookie 读取用户并写审计日志。指出两个风险,并说明应把代码移到哪里。

问题 3:改 Demo 代码。 给主图增加一个“导出报表”命令。你需要修改哪一层?如何证明增加它没有让入口重新承担报表业务?

名词解释

名词解释

本章出现的专业名词,用大白话再讲一遍。

前端控制器

所有请求先到达的统一入口;它负责共同的请求工作,然后把具体动作交给别的对象。

横切关注点

许多请求都会需要、但不属于某一个页面业务的规则,例如认证和日志。

路由表

把请求方法和路径对应到处理者的一张映射表。

命令对象

代表一次具体请求动作的对象,例如查看订单或取消订单。

前后导航

参考资料

资料与写作方式声明

本章以Patterns of Enterprise Application Architecture权威目录界定学习范围,并结合正文列出的技术资料独立重写;不宣称复现原书正文,也不沿用原作表述。

原作版权归作者与出版社所有;本站原创教学结构与表述仅供学习交流。

讨论

评论区加载中…