0%

【译】什么是前端架构

英语原文在此:https://ducin.dev/what-is-frontend-architecture
一开始看到了其他人的翻译,比较认可这篇文章的不少内容,所以进行一个转载,但又不想纠结于一些版权方面的问题,所以干脆基于原文让最近大火的 DeepSeek R1 帮我翻译一遍。

当你思考系统设计时,不要纠结于技术选型,而应聚焦于你希望系统具备的核心特性。技术选型只是这些特性的载体。 —— Gregor Hohpe

免责声明:如果你自认为只是个”码农”,请立即关闭本页面😉

前端社区存在一个普遍问题😉:我们过度关注库、框架、打包工具、GitHub star 数等次要因素。我们常会狂热追捧某个工具(比如2015-2016年的Redux),然后滥用它。

新框架新工具对前端开发者的蜜汁吸引力

后来,我们又因同样的原因彻底厌恶这个工具(比如现在的Redux)。这种爱恨都没有道理…究竟发生了什么?🤨

“问题”根源在于:许多前端开发者缺乏软件架构的基本认知,因为我们总把注意力放在别处。而这些架构能力恰恰是项目长期成功的关键(虽非唯一因素)。因为架构是连接业务价值和技术实现的隐形桥梁。

在开展开发者培训、技术咨询或团队招聘时,我常会提问:如何理解软件架构?哪些是核心要素?如何设计稳健的系统架构?架构师的角色是什么?

在继续阅读前,建议你先尝试回答这些问题😉…

滴答⏰…

这个问题特意设计得非常开放,以便对话者能自由表达他们认为重要的观点。我不会给出任何暗示。但当回答开头是类似”(前端)架构就是如何组织目录和文件[…]”时,这对我来说立即成为危险信号🟥。没错,正是最近又有人这样回答,促使我写下本文。

亲爱的读者,本文旨在转变你的关注焦点:启发你从不同维度思考架构。跳脱代码仓库中的结构,摆脱具体实现方案的束缚。集中精力思考你希望系统具备哪些核心特性。从更宏观的视角,你需要哪些系统能力。摆脱工具本身的局限,转而关注它们带来的权衡取舍。最重要的是——你的业务需求如何决定必要的软件能力。


什么是(前端)架构?

某次推文讨论中我说😅

目录结构不是架构

在评论区被问到我对架构的定义。我的简洁回答是:

根据业务需求做出的、塑造当前系统且未来难以变更的决策。

事实上,软件架构并没有单一标准定义。我强烈推荐阅读Martin Fowler的软件架构指南。其他替代定义包括:

  • 你希望在项目早期就做对的那些决策
  • 架构就是关于重要的事——无论它是什么

注意两个关键词:重要决策

在进入具体案例之前(文章后续会涉及),让我们从基础要素开始。


决策

在项目的整个生命周期中,我们需要做出大量决策:语言平台选择、类库选型、编程范式、代码风格(Tabs还是空格🤔)…但更重要的是:

  • 如何确保业务优先级得到满足?
  • 如何让数十名开发者高效协作?
  • 如何实现高频部署(包含每日、每小时甚至周五的部署)?

如你所见,这些主题的重要性差异巨大。我们分析的角度和做出的决策具有不同的相关权重。经验越丰富的开发者,越懂得在无关紧要处节省精力——特别是当决策容易修改时。

那么,如何评估某个决策是否正确?


驱动因素

当面临困惑时,退一步审视全局总是明智的,这包括:

  1. 业务最高优先级是什么?
  2. 需要考虑哪些限制条件
  3. 哪些目标可以妥协(可选),哪些不可退让(必需)?

架构驱动因素是迫使我们在特定项目上下文中深度探索的关键要素。它相当于项目的语境过滤器,用于判断某个理论上的优势或劣势在具体情境中是否重要。

典型架构驱动因素包括:

  • 响应时间:系统必须极速响应
  • 流量承载:需要处理海量请求
  • SLA/高可用:需保持约99.99%的正常运行时间
  • 组织规模:需要支持数十甚至数百名开发者协作
  • 上手门槛:应便于技能较弱的开发者理解
  • 上市时间:因业务需求必须快速交付功能 (备注:商业成功的关键因素之一是 上市时间-TTM-Time To Marketing。TTM是指从产生想法到向客户推出最终产品或服务的时间长度。市场发展很快,延迟的TTM可能会毁掉整个商业理念。)

在商业环境中,这些驱动因素几乎总是存在的。你的业务代表很可能直接表达过这些需求(只是未使用”驱动因素”这个术语)。若不能识别这些,你将可能专注错误方向,从而大幅降低成功概率。

那么,我们该如何进行系统设计,以实现这些高层次目标呢?


权衡取舍

必须清醒认识到:所有特性都有代价。若想让系统具备某个优点,就必须接受对应的成本。让我们扩展之前的驱动因素列表,列出可能的负面影响:

驱动因素 所需代价
系统必须极速响应 复杂度↑,灵活性↓
通过水平扩展/自动扩缩容处理海量流量 需从单体架构转向分布式系统
保持99.99%正常运行时间 需维护金丝雀发布、蓝绿部署等高成本方案
支持大规模团队协作 代码重复↑,基础设施复杂度↑
便于初级开发者上手 无法使用团队最爱的技术栈
快速交付业务功能 技术债务累积↑

取舍的本质在于:我们可以有意识地决定哪些可以放弃。例如:

  • :99.99%可用性是否必要?
    :必要,因合同条款要求
  • :80%测试覆盖率是否必要?
    :锦上添花,非必需,可舍弃

制定架构决策时,必须聚焦驱动因素,同时牢记取舍代价。我们的目标是达成核心诉求,但也清楚可能需要牺牲什么。


限制

还有一个不言而喻的真理:并非所有事情都能实现😉。

有时即使所有分析都证明某个决策正确,我们仍无法实施。外部因素可能产生冲突:

理想决策 现实阻碍
系统必须极速响应,但需接受复杂度↑和灵活性↓ 因故无法重构数据库
通过水平扩展/自动扩缩容处理海量流量,需转向分布式系统 因故无法更换云服务商
业务需要快速交付功能,但会产生技术债务 因故无法扩招开发人员

这就是现实的残酷之处。为了让挑战更有趣些——我们必须接受能力受限的事实😉。但我们仍然要达成目标!


简要回顾

快速总结架构决策的三要素:

  1. 驱动因素:业务核心诉求
  2. 权衡取舍:每个决策的代价
  3. 现实限制:不可抗的外部约束

跳出代码层面思考


再次提问:什么是架构?

回顾我的简易定义:
根据业务需求做出的、塑造当前系统且未来难以变更的决策。

现在通过具体案例区分架构决策技术决策

架构决策 ✅ 技术决策 ❌
是否实施微前端(MFE)及实施方式 使用webpack模块联邦或其他工具
重用性与隔离性的优先级抉择 是否使用barrel文件(index.js/ts)
模型跨模块共享 vs ACL隔离 采用类/OOP还是函数式/FP
状态管理采用集中共享式 vs 分布式 是否使用Redux类状态库
数据获取模式:PULL vs PUSH 使用Promise/async await/rxjs
UI对实时数据的依赖程度 选择Firebase/Supabase等BaaS
客户端-服务端API契约变更权限 使用GraphQL/REST/SSE协议
确定核心架构驱动因素 是否宣称遵循”最佳实践”
LCP(最大内容渲染)优化是必备项还是加分项 UI组件代码行数(LoC)
系统单租户 vs 多租户架构 认证数据存于Redux/Context/useState
前端容错机制设计 CI流水线强制80%测试覆盖率

通过这些对比,可以清晰看到架构决策与技术决策的本质区别😅。注意上述对照展现了架构决策与技术决策的根本差异。


为什么目录结构不应该被视作架构?

目录结构是设计工作和引入规范的产物。它们旨在帮助我们:

  • 更快速地开发
  • 更安全地交付(减少破坏性变更)

但目录结构本身不是目标,而是实现更高层次目标的手段,例如:

  • 通过微前端/模块化架构划分限界上下文
  • 支持独立团队间的解耦部署
  • 通过ACL(访问控制列表)隔离本地模型

显然,目录结构可能更好地适配某个架构,也可能适配度较低。但究其本质,它只是某个概念的具象化实现——属于实现细节层面,是达成最终目标的路径。


目录结构无法告知我们什么

许多关键架构要素无法通过目录结构推断,包括:

  1. 是否存在‘上帝类’:无法判断模块是否真正隔离为限界上下文
  2. 模型复用情况:无法识别契约层与前端逻辑是否共享同一模型
  3. 状态管理模式:无法确认是集中式共享状态还是分布式本地状态
  4. 代码耦合度:无法定位问题耦合点,无法判断是否应用依赖倒置原则
  5. 代码内聚性:无法评估模块间的功能聚合程度

温和地说,目录结构的重要性不足以构成架构。它可能服务于某个架构(也可能不),但本身不是架构。


构建正确的前端架构认知

架构不是我们😘渴望、🥰向往或😤强制执行的东西。它不会突然浮现😶🌫️,更不该直接复制前公司的成功方案🥸(即便在之前公司运行良好)。

架构是沟通、分析和推理的产物。如同函数根据输入产生输出,架构师的职责就是持续收集知识经验,定期运行这个”输入→输出”函数。


输入源与获取方式

架构师的核心技能是与管理层、业务方和开发团队的全方位沟通。需要收集的关键信息包括:

业务维度

产品核心优势:竞争差异点所在领域

  • 核心领域保护:核心模块/功能/团队禁止外包
  • 质量红线:不可妥协的质量标准
  • 领域建模:采用事件风暴等DDD实践
  • 非核心领域:次要优先级

组织维度

康威定律影响:公司结构如何决定系统交付形态

  • 开发团队规模:部门人数与团队数量
  • 产品导向程度:各团队是否100%独立负责子产品(开发→测试→部署全链路)
  • 企业关联关系:关联公司、技术共享、并购等可能颠覆现有团队架构的因素

交付维度

  • 预期交付速度:方案交付时间窗与技术储备匹配度
  • 部署频率要求:持续交付准备度评估

进入开发维度

在技术层面,我们需要形成以下问题链:

代码复用策略

  • 应鼓励/抑制多少代码复用?
    • 复用越多代码量越少,但团队独立性越弱(特别是在共享模块频繁变更时,与无共享架构相比)
    • 修改共享模块的后果分析(文件/组件库/制品等):
      • 是否触发重建?若需要,需重建多少模块?
      • 需要多少自动化测试?
      • 需部署多少组件?同步还是异步?
      • 总体耗时多少?
      • 共享机制引入的效率损耗?
        • 对系统可用性/SLA的影响评估

系统灵活性度量

  • 系统灵活性与架构测量方式
    • TTM(上市时间):预期值与当前实际值对比
    • 部署频率(DF):生产环境变更次数/单位时间
    • 交付周期(CLT):从开发者开始编码到生产部署的时间跨度
    • 故障率(CFR):变更引发故障的频率
    • 恢复时间(MTTR/FDRT):故障修复耗时

系统稳定性保障

(超越测试覆盖率等基础指标)

  • 故障处理机制
    • 故障发生时的标准流程?
    • 该流程的实际调用频率?😄
  • 同步部署分析
    • 需同步部署的模块数量与体积?
    • 是否因CI/CD配置或仓库过度拆分导致依赖项冗余构建?
  • 团队信任机制
    • 是否信任其他团队的交付物?
    • 采用Git-flow还是主干开发?
    • CI/CD流程如何适配这些决策?
  • 容错能力验证
    • 前端是否针对后端各类故障场景进行自动化测试?
  • 可观测性效用评估
    • 定位前端问题的耗时?
    • 回滚操作耗时?
    • 修复问题耗时?
    • 如何/何时发现核心Web指标(LCP/FID/CLS)的回归?

跨平台策略

  • 用户设备与环境:Web/原生移动端/混合方案?
    • 复用与分叉策略
      • 哪些组件应复用?
      • 哪些应为减少跨团队依赖而分叉?
    • 团队组织模式
      • 按技术平台划分 vs 按限界上下文划分?😉

特殊需求案例

  • 预期/必需的系统特性

    • 实时协作模式

      • 现状:仅支持单用户操作
      • 需求:多用户实时编辑同一数据集
      • 方案对比:
        • 直接状态修改(set/update)→ 缺乏共享模型
        • 命令模式(如Redux Action)→ 天然支持协作迭代
        • 进阶方案:CRDT(无冲突复制数据类型)
    • 数据实时性要求

      • 社交帖子点赞数延迟 → 可容忍
      • 银行系统账户余额标签切换过期 → 不可接受
      • 解决方案:
        • 客户端缓存失效策略(SWR)
        • 服务端推送机制(SSE/WebSocket)
    • SDK兼容性管理

      • 客户基于平台SDK开发定制功能
      • 平衡法则:
        • 系统演进 vs 向后兼容
        • 案例:React组件props重构
          • 后果:可能引发客户重大变更
          • 困境:即使实现并测试,仍可能因”无破坏性变更”要求被回滚

架构管理的本质在于提出正确问题。引用经典格言:
“我宁愿拥有无法回答的问题,也不要接受不容置疑的答案”

通过深入思考这些问题,我们将确定适合的架构风格与关键技术选型。


工具决策 vs 架构决策

有人可能会说:

“嘿兄弟,但有些与工具、类库、规范或代码结构相关的决策,对项目有重大影响且后期难以更改。比如使用Redux就是架构决策!!! 😉”

嗯,是也不是。😉

人们很容易执着于特定术语,却丢失了关键语境(即从业务和整体视角看什么是重要的)。

确实,某些编码规范决策在多年后可能(非常)难以更改。编码规范、代码结构或目录结构(真的,任何东西)都可能加速或拖慢开发速度——当然!有些规范更合适,有些则不然。有些能让我们更快推进,有些则不能,等等。

但当你跳出做出该决策的团队范围,这些就完全无关紧要了🔥。它们只是实现细节,在团队之外毫无意义。

示例

  • 你决定每个文件只保留一个组件 → 在团队外完全无关
  • 你选择lodash/ramda工具库或不用任何库(因为”非我发明”) → 仍然与团队外无关
  • 你为每个模块设计特定文件结构 → 该规范影响测试、Storybook和重构 → 仍然与团队外无关
    (顺便说,如果Storybook被团队外频繁使用,它就变得相关了)

请注意:这些决策确实重要,对你的团队很关键。但仅对你的团队。它们不会带来/强制任何整体系统特性。如果决策不同,整体系统特性也不会改变。让我们进一步分析之前的说法:

“使用Redux就是架构决策”

(Redux对不住了😅)

现在请注意:架构决策不是选择Redux本身!而是选择集中式状态管理方案,因为这可能导致模块间交叉依赖(所有人都能访问全局store的一切,对吧?),或者在将单体拆分为微前端时——使用多个独立store(如MobX)会更简单。此外,架构决策还涉及选择客户端事件溯源方案,因为业务可能需要实现实时协作功能。

那么选择Redux会带来后果吗?当然。但再次强调,重点不在库本身,而在于Redux带来的高层次特性——既包括它提供的能力(前文提过),也包括引入的成本和限制。例如Redux是唯一数据源,这在考虑微前端时显然不利。Redux与其特性密不可分,但构建架构的是这些特性,而非工具本身。

让我们再看一个Angular生态的例子:

“不同意!如果是像NGRX这样的高层次库,选择库本身就是架构决策。需要回答多个问题:1.如何使用NGRX;2.是否总是使用Effects;3.是否通过Facade抽象;4.与哪些层级关联;5.如何跨域共享NGRX Store?”

让我们一个一个来讨论:

  • 我们如何使用NGRX?
    这是个狡猾的问题,因为”如何使用”可能涉及高层次和低层次两个维度。模棱两可的问题😉

  • 是否总是使用Effects?
    (上下文:NGRX Effects等同于redux-observable的epics——派发action后,通过rxjs响应式流处理,通常派生新action返回store)
    这属于实现细节。无论选择命令式还是响应式范式,都属于编程(实现)范式,无关架构。未来可以改变这个决策。

  • 是否通过Facade抽象?
    这属于封装和/或设计模式/编码模式…比架构模式低一个层级。在C4模型中属于代码层(Level 4)(实现细节)。重申——对团队重要吗?重要。对外部重要吗?不重要。

  • 与哪些层级关联?
    可能涉及架构——但这与NGRX无关。使用其他状态管理方案(如React自定义hooks)时也会提出同样的问题。假设的层级(或其缺失)当然构成架构,但即使换用其他库,这个问题依然存在,对吧?

  • 如何跨域共享NGRX Store?
    绝对属于架构决策。但同样与NGRX本身无关,因为使用任何其他集中式状态管理方案时都会遇到同样的问题。对吗?

补充说明
是否使用NGRX/redux-observables当然会影响:

  • 前端开发者的入门门槛
  • 他们的积极性(与工具的爱恨情仇🥹)
  • 测试编写方式等

但重申:当你走出团队/模块/仓库范围——这些真的有那么重要吗?

归根结底,决策的变更成本高低,并不决定其在大局和/或长期中的相关性。同样,在团队/仓库内部极其重要的东西,也不必然对外部具有相关性。可能有,但不必然。

依我拙见,是否将选择Redux称为架构决策并不重要,只要我们聚焦于该决策带来的后果。

特征 工具决策 架构决策
影响范围 团队内部 跨团队/系统
变更成本 可能高昂但局部 系统性影响
业务关联度 间接 直接驱动
示例 Redux/NGRX选型 集中式状态管理策略

总结

架构的核心在于做出重要决策。这些决策应:

  1. 由业务优先级驱动
  2. 考量权衡取舍
  3. 适应现实限制

面对这些挑战,架构师的职责是在业务优先级/需求技术实现/复杂度之间找到平衡点。

切勿混淆以下概念:

  • 架构:助你达成目标的高层次决策
  • 实现方式:工具、类库、规范、API 等底层细节

后者只是实现目标的可能路径,从业务优先级和现实限制的角度看,它们只是次要细节。

希望本文对你有所启发,感谢阅读🤓。
特别致谢 Damian、Mateusz 和 Manfred 提供的宝贵反馈。

特别特别致谢 DeepSeek R1 提供的翻译