英语原文在此:https://ducin.dev/what-is-frontend-architecture
一开始看到了其他人的翻译,比较认可这篇文章的不少内容,所以进行一个转载,但又不想纠结于一些版权方面的问题,所以干脆基于原文让最近大火的 DeepSeek R1 帮我翻译一遍。
当你思考系统设计时,不要纠结于技术选型,而应聚焦于你希望系统具备的核心特性。技术选型只是这些特性的载体。 —— Gregor Hohpe
免责声明:如果你自认为只是个”码农”,请立即关闭本页面😉
前端社区存在一个普遍问题😉:我们过度关注库、框架、打包工具、GitHub star 数等次要因素。我们常会狂热追捧某个工具(比如2015-2016年的Redux),然后滥用它。
后来,我们又因同样的原因彻底厌恶这个工具(比如现在的Redux)。这种爱恨都没有道理…究竟发生了什么?🤨
“问题”根源在于:许多前端开发者缺乏软件架构的基本认知,因为我们总把注意力放在别处。而这些架构能力恰恰是项目长期成功的关键(虽非唯一因素)。因为架构是连接业务价值和技术实现的隐形桥梁。
在开展开发者培训、技术咨询或团队招聘时,我常会提问:如何理解软件架构?哪些是核心要素?如何设计稳健的系统架构?架构师的角色是什么?
在继续阅读前,建议你先尝试回答这些问题😉…
滴答⏰…
这个问题特意设计得非常开放,以便对话者能自由表达他们认为重要的观点。我不会给出任何暗示。但当回答开头是类似”(前端)架构就是如何组织目录和文件[…]”时,这对我来说立即成为危险信号🟥。没错,正是最近又有人这样回答,促使我写下本文。
亲爱的读者,本文旨在转变你的关注焦点:启发你从不同维度思考架构。跳脱代码仓库中的结构,摆脱具体实现方案的束缚。集中精力思考你希望系统具备哪些核心特性。从更宏观的视角,你需要哪些系统能力。摆脱工具本身的局限,转而关注它们带来的权衡取舍。最重要的是——你的业务需求如何决定必要的软件能力。
什么是(前端)架构?
某次推文讨论中我说😅
在评论区被问到我对架构的定义。我的简洁回答是:
根据业务需求做出的、塑造当前系统且未来难以变更的决策。
事实上,软件架构并没有单一标准定义。我强烈推荐阅读Martin Fowler的软件架构指南。其他替代定义包括:
- 你希望在项目早期就做对的那些决策
- 架构就是关于重要的事——无论它是什么
注意两个关键词:重要 和 决策。
在进入具体案例之前(文章后续会涉及),让我们从基础要素开始。
决策
在项目的整个生命周期中,我们需要做出大量决策:语言平台选择、类库选型、编程范式、代码风格(Tabs还是空格🤔)…但更重要的是:
- 如何确保业务优先级得到满足?
- 如何让数十名开发者高效协作?
- 如何实现高频部署(包含每日、每小时甚至周五的部署)?
如你所见,这些主题的重要性差异巨大。我们分析的角度和做出的决策具有不同的相关权重。经验越丰富的开发者,越懂得在无关紧要处节省精力——特别是当决策容易修改时。
那么,如何评估某个决策是否正确?
驱动因素
当面临困惑时,退一步审视全局总是明智的,这包括:
- 业务最高优先级是什么?
- 需要考虑哪些限制条件?
- 哪些目标可以妥协(可选),哪些不可退让(必需)?
架构驱动因素是迫使我们在特定项目上下文中深度探索的关键要素。它相当于项目的语境过滤器,用于判断某个理论上的优势或劣势在具体情境中是否重要。
典型架构驱动因素包括:
- 响应时间:系统必须极速响应
- 流量承载:需要处理海量请求
- SLA/高可用:需保持约99.99%的正常运行时间
- 组织规模:需要支持数十甚至数百名开发者协作
- 上手门槛:应便于技能较弱的开发者理解
- 上市时间:因业务需求必须快速交付功能 (备注:商业成功的关键因素之一是 上市时间-TTM-Time To Marketing。TTM是指从产生想法到向客户推出最终产品或服务的时间长度。市场发展很快,延迟的TTM可能会毁掉整个商业理念。)
在商业环境中,这些驱动因素几乎总是存在的。你的业务代表很可能直接表达过这些需求(只是未使用”驱动因素”这个术语)。若不能识别这些,你将可能专注错误方向,从而大幅降低成功概率。
那么,我们该如何进行系统设计,以实现这些高层次目标呢?
权衡取舍
必须清醒认识到:所有特性都有代价。若想让系统具备某个优点,就必须接受对应的成本。让我们扩展之前的驱动因素列表,列出可能的负面影响:
驱动因素 | 所需代价 |
---|---|
系统必须极速响应 | 复杂度↑,灵活性↓ |
通过水平扩展/自动扩缩容处理海量流量 | 需从单体架构转向分布式系统 |
保持99.99%正常运行时间 | 需维护金丝雀发布、蓝绿部署等高成本方案 |
支持大规模团队协作 | 代码重复↑,基础设施复杂度↑ |
便于初级开发者上手 | 无法使用团队最爱的技术栈 |
快速交付业务功能 | 技术债务累积↑ |
取舍的本质在于:我们可以有意识地决定哪些可以放弃。例如:
- 问:99.99%可用性是否必要?
答:必要,因合同条款要求 - 问:80%测试覆盖率是否必要?
答:锦上添花,非必需,可舍弃
制定架构决策时,必须聚焦驱动因素,同时牢记取舍代价。我们的目标是达成核心诉求,但也清楚可能需要牺牲什么。
限制
还有一个不言而喻的真理:并非所有事情都能实现😉。
有时即使所有分析都证明某个决策正确,我们仍无法实施。外部因素可能产生冲突:
理想决策 | 现实阻碍 |
---|---|
系统必须极速响应,但需接受复杂度↑和灵活性↓ | 因故无法重构数据库 |
通过水平扩展/自动扩缩容处理海量流量,需转向分布式系统 | 因故无法更换云服务商 |
业务需要快速交付功能,但会产生技术债务 | 因故无法扩招开发人员 |
这就是现实的残酷之处。为了让挑战更有趣些——我们必须接受能力受限的事实😉。但我们仍然要达成目标!
简要回顾
快速总结架构决策的三要素:
- 驱动因素:业务核心诉求
- 权衡取舍:每个决策的代价
- 现实限制:不可抗的外部约束
跳出代码层面思考
再次提问:什么是架构?
回顾我的简易定义:
根据业务需求做出的、塑造当前系统且未来难以变更的决策。
现在通过具体案例区分架构决策与技术决策:
架构决策 ✅ | 技术决策 ❌ |
---|---|
是否实施微前端(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(访问控制列表)隔离本地模型
显然,目录结构可能更好地适配某个架构,也可能适配度较低。但究其本质,它只是某个概念的具象化实现——属于实现细节层面,是达成最终目标的路径。
目录结构无法告知我们什么
许多关键架构要素无法通过目录结构推断,包括:
- 是否存在‘上帝类’:无法判断模块是否真正隔离为限界上下文
- 模型复用情况:无法识别契约层与前端逻辑是否共享同一模型
- 状态管理模式:无法确认是集中式共享状态还是分布式本地状态
- 代码耦合度:无法定位问题耦合点,无法判断是否应用依赖倒置原则
- 代码内聚性:无法评估模块间的功能聚合程度
温和地说,目录结构的重要性不足以构成架构。它可能服务于某个架构(也可能不),但本身不是架构。
构建正确的前端架构认知
架构不是我们😘渴望、🥰向往或😤强制执行的东西。它不会突然浮现😶🌫️,更不该直接复制前公司的成功方案🥸(即便在之前公司运行良好)。
架构是沟通、分析和推理的产物。如同函数根据输入产生输出,架构师的职责就是持续收集知识经验,定期运行这个”输入→输出”函数。
输入源与获取方式
架构师的核心技能是与管理层、业务方和开发团队的全方位沟通。需要收集的关键信息包括:
业务维度
产品核心优势:竞争差异点所在领域
- 核心领域保护:核心模块/功能/团队禁止外包
- 质量红线:不可妥协的质量标准
- 领域建模:采用事件风暴等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选型 | 集中式状态管理策略 |
总结
架构的核心在于做出重要决策。这些决策应:
- 由业务优先级驱动
- 考量权衡取舍
- 适应现实限制
面对这些挑战,架构师的职责是在业务优先级/需求与技术实现/复杂度之间找到平衡点。
切勿混淆以下概念:
- 架构:助你达成目标的高层次决策
- 实现方式:工具、类库、规范、API 等底层细节
后者只是实现目标的可能路径,从业务优先级和现实限制的角度看,它们只是次要细节。
希望本文对你有所启发,感谢阅读🤓。
特别致谢 Damian、Mateusz 和 Manfred 提供的宝贵反馈。
特别特别致谢 DeepSeek R1 提供的翻译