Feature-Driven Architecture:设计可扩展的应用程序
Source: Dev.to
Introduction
在本系列的第一篇文章中,我们讨论了 Atomic Design 作为一种构建连贯、可复用且结构良好的界面的方法。
当主要挑战是 组织 UI 时,这是一种极其有效的做法。
但随着应用的增长,UI 很快不再是实际问题。
代码增多,功能倍增,需求变化比我们希望的更快。此时,会出现新的问题:
- 应用逻辑到底应该放在哪里?
- 如何在不破坏其他功能的情况下修改某个功能?
- 多个人如何在同一代码库上工作而不相互踩脚?
这就是 架构可扩展性 发挥作用的地方。
而这正是仅靠 Atomic Design 所不足的地方。
可伸缩性不仅仅是性能
当我们谈论可伸缩性时,我们常常立刻想到性能、负载或基础设施。
实际上,首个可伸缩性问题是代码本身。
当一个应用无法伸缩时:
- 每个新功能都需要在多个位置进行更改
- 逻辑分散且难以追踪
- 应用各部分之间的依赖是隐式且脆弱的
- 理解一次更改的上下文比更改本身更耗费成本
简而言之:当认知负担增长速度超过代码本身时,系统就无法伸缩。
面向特性的架构正是为了解决这个问题而存在的。
功能到底是什么
A 功能 并不是单个界面。
它甚至不是一个孤立的函数。
功能是 对用户有价值的单元。
从架构的角度来看,功能代表:
- 明确的目标
- 完整的行为
- 明确定义的上下文
当用户说 “我想做 X” 时,他们通常指的是一个功能。
以功能为中心的思考会转变关注点:
- 从技术转向 领域
- 从技术层面转向 功能价值
正是这种转变使得可扩展性成为可能。
面向特性的架构:关键原则
核心原则很简单:
围绕特性组织代码,而不是技术层次。
每个特性成为一个 内聚的容器,其中包含实现该行为所需的一切。
这并不意味着要消除技术概念,而是 在全局层面降低它们的可见性。细节保留在特性内部,符合其语境。
主要优势
- 特性可以独立演进
- 上下文清晰且受限
- 系统通过 添加 而非 纠缠 来增长
这就是架构可扩展的方式。
示例:cart 功能
为了让概念更具体,这里提供一个单一功能可能的组织方式示例:
features/
└─ cart/
├─ components/ # UI 元素,专属于购物车
├─ helpers/ # 可复用的函数或逻辑帮助器,专属于购物车
├─ state/ # 购物车的状态管理(本地或全局)
├─ views/ # 与购物车相关的页面或屏幕
├─ types/ # 类型定义或接口
├─ tests/ # 购物车功能的测试
├─ mocks/ # 用于测试/开发的模拟数据或服务
└─ index.ts # 对外暴露功能公共部分的入口文件
示例 index.ts
// 仅导出应在功能外部使用的内容
export * from './components';
export * from './helpers';
export * from './types';
// 不要导出 state、views、tests 或 mocks
// 这些保持为功能内部实现
⚠️ 暴露特性部分的重要规则
-
仅暴露应在特性外部使用的内容。
组件、辅助函数和类型通常可以安全导出。 -
其余全部保持私有。
state/→ 内部状态逻辑views/→ 屏幕或容器tests/→ 测试代码mocks/→ 虚假数据
-
始终使用
index.ts作为对外使用的唯一入口点。
这可以保持特性 封装,并使边界清晰。
将原子设计与特性驱动架构相结合
原子设计和特性驱动架构并不相互排斥——它们可以完美互补。
虽然特性包含业务逻辑、状态、视图和测试,原子设计提供了一套共享语言,用于构建可在多个特性之间复用的 UI 组件。
文件夹结构示例
features/
├─ cart/
│ ├─ components/
│ ├─ helpers/
│ ├─ state/
│ ├─ views/
│ ├─ types/
│ ├─ tests/
│ ├─ mocks/
│ └─ index.ts
├─ checkout/
└─ wishlist/
shared/
├─ components/
│ ├─ atoms/
│ ├─ molecules/
│ └─ organisms/
└─ utils/ # shared utilities, helpers, and services
关键点
shared/components/atoms、molecules、organisms→ 可复用的 UI 构建块features/→ 包含特性独立运行所需的全部内容
特性可以 使用共享的原子组件,同时在 components/ 中保留其特定的特性组件。
- 这种方式保持 可复用性清晰、模块化程度高、边界定义明确,使 UI 与业务逻辑能够独立扩展。
通过将原子设计与特性驱动架构相结合,我们可以兼得两者的优势:
- 一致且可复用的 UI
- 可扩展且自治的特性
功能与可重用性:思维方式的转变
这种方法最有趣的影响之一是它如何改变可重用性的概念。
在特性驱动架构中:
- 特性默认情况下不可重用
- 可重用性是一种有意识的选择,而非最初的需求
这与我们通常对架构的思考方式,尤其是 UI 方面,截然不同。
原子化设计仍然非常有用,但其角色发生了变化:它构建 …(继续你的内容)。
通用、稳定的元素
- 它不会在不需要的地方强迫可重用性。
- 特性成为接受并管理复杂性的场所。
- 共享元素变得少而坚固,真正通用。
- 减少过早抽象,提升清晰度。
扩展团队,而不仅是代码
可扩展性的另一个常被忽视的方面是 人为因素。
当代码围绕特性组织时:
- 责任分配更容易。
- 上下文更清晰。
- 冲突减少。
特性成为自然的单元,用于:
- 规划
- 并行开发
- 测试
- 维护
在实践中,架构开始反映人们的 思考和工作方式。
这正是系统真正可扩展的最有力迹象之一。
何时适合采用此方法
特性驱动架构并非灵丹妙药。它在以下情况下是有意义的:
- 预期应用会增长。
- 业务领域并非琐碎。
- 多人协同同一代码库。
- 变化是常态,而非例外。
在非常小的应用中,初始开销可能不值得。
但当复杂性是真实存在时,不围绕特性组织会在长期内更昂贵。
结论
特性驱动架构教会我们超越单一屏幕或组件的视角:围绕独立特性组织代码可以降低冲突风险,隔离上下文,使应用更清晰、更易维护。
从这个意义上说,可扩展性不仅是技术目标——它是 代码和团队在不混乱的情况下共同成长的能力。每个特性成为可管理的生态系统,能够演进而不影响应用的其他部分。
在下一篇文章中,我们将探讨如何组合多个特性、编排它们,并使其协同工作而不失凝聚力——将一组独立单元转化为真正可扩展的前端。