需求为什么总在变
做需求最怕听到两句话:一句是「这个很简单,你先做出来看看」,另一句是「怎么又变了」。需求变更本身并不可怕,真正的问题是我们没有为变化留出结构——把每次改动都当成补丁打在原来的设计上,系统迟早会变成一团无法维护的泥球。
需求分析的目标不是把所有需求一次性问清楚(这基本不可能),而是建立一套能低成本吸收变化的结构。它的产出有两部分:一是明确、可验收的需求描述,二是支撑这些需求的架构骨架。
需求决定「做什么」,架构决定「怎么放」。前者描述问题空间,后者规划解决空间;两者之间需要一条清晰的映射链。
这条映射链一旦断裂,就会出现最典型的两类问题:需求做了,但结构放不下(改一处动全身);或者结构很漂亮,但没解决用户的真实问题(过度设计)。本文沿着「需求 → 模型 → 架构 → 决策 → 演进」的顺序,把这条链补全。
- 用户故事用「角色—功能—价值」承载需求,配合验收标准确定「做完」的含义。
- 领域建模用实体、值对象与聚合描述业务概念及其一致性边界。
- 分层架构通过依赖方向控制耦合,让业务逻辑不依赖具体框架。
- ADR用一页纸记录每次架构决策的背景、选项、结论与后果。
- 质量属性性能、可用性、可维护性等非功能需求,往往比功能更能决定架构。
从用户故事到验收标准
承接敏捷实践,需求通常以用户故事的形式进入待办列表。但用户故事只是「需求的一半」——它说清了价值,却没说清边界。补上边界靠验收标准:一组可执行的、非黑即白的判断条件。
一条好的验收标准要满足三点:
- 可验证:能写成一个自动化或手工的测试用例,而不是「体验流畅」这类主观描述。
- 覆盖边界:除了正常路径,必须包含异常路径与边界值。
- 无歧义:不同的人读完后,对「通过」的判定一致。
把验收标准写清楚,等于提前完成了一次需求评审。很多所谓的「需求变更」,其实是当初验收标准没写清、双方各自脑补了不同版本。等到验收时才发现分歧,返工成本已经翻了好几倍。
用例与领域建模
有了逐条需求,还要把它们拼成一张业务全景图。这一步通常分两层:用例视角描述「角色如何与系统交互」,领域视角描述「系统中存在哪些概念、它们如何关联」。
领域建模的关键动作是识别实体(有唯一标识、会随时间变化的对象,如订单、用户)、值对象(靠属性值区分、不可变的对象,如金额、地址)与聚合(一组必须保持一致性的对象边界)。聚合边界划得对不对,直接决定了后续事务与并发设计的复杂度。
# 领域模型示例:订单聚合
class Order: # 聚合根,对外唯一入口
id: OrderId # 实体标识
items: list[OrderItem] # 订单项,聚合内对象
status: OrderStatus # 值对象 / 枚举
def add_item(self, sku, qty, price): # 唯一入口:外部只能经由聚合根修改
self.items.append(OrderItem(sku=sku, qty=qty, price=price))
def total(self) -> Money: # 计算不变式在聚合内维护
return Money(sum(i.qty * i.price for i in self.items))
class OrderItem:
sku: str
qty: int
price: Money # 值对象,不可变
领域模型不必事无巨细。原则是只建模当下会用到的概念,随着业务演进再补充。过度建模和完全建模一样有害:前者会拖慢交付,后者会把设计绑死在还没验证的假设上。
分层架构与依赖方向
架构的核心问题可以浓缩成一句话:谁依赖谁。经典的分层架构把系统划分为表现层、应用层、领域层与基础设施层,并规定依赖只能由外向内——领域层不依赖框架、不依赖数据库,甚至可以完全不知道外面长什么样。
src/
├── presentation/ # 表现层:控制器、DTO、参数校验
│ └── order_controller.py
├── application/ # 应用层:用例编排、事务边界
│ └── place_order_service.py
├── domain/ # 领域层:实体、值对象、领域服务(不依赖框架)
│ ├── order.py
│ └── pricing.py
└── infrastructure/ # 基础设施层:数据库、消息、第三方网关
├── order_repository.py
└── payment_gateway.py
# 依赖方向:presentation -> application -> domain
# infrastructure -> domain(实现 domain 定义的接口)
# domain 不反向依赖任何一层
依赖倒置是这条规则的关键工具:领域层定义接口(如 OrderRepository),基础设施层提供实现。这样领域逻辑就能独立于具体存储技术被测试与复用。反过来说,一旦领域层 import 了某个 ORM 框架,你的业务逻辑就被牢牢绑死在了框架的版本上——升级框架时会付出成倍的代价。
分层不是目的,可控的改动范围才是目的。判断一个分层是否健康,可以问自己:改一个业务规则,需要动几层?如果答案是一层,说明边界清晰;如果需要横跨四层,那分层的意义就所剩无几了。
用 ADR 记录架构决策
架构方案一旦确定,往往会被后来的成员视为「本来就该这样」,其中的权衡与背景全部丢失。解决办法是架构决策记录(ADR):用一页纸记录一次决策的上下文、选项、结论与后果。
# ADR-007:订单查询采用 CQRS 读写分离
## 状态
已接受(2026-10-14)
## 背景
订单列表查询 QPS 高、维度多,与写模型的聚合边界冲突。
## 选项
1. 单一模型同时承担读写(简单,但读模型被迫迁就写模型)
2. 读写分离 CQRS(复杂度上升,但两侧可独立优化)
## 决策
采用方案 2,读模型以异构方式构建。
## 后果
- 正向:查询性能与扩展性提升,读侧可自由选型。
- 负向:需要处理最终一致性,团队需掌握事件驱动模式。
| 要素 | 说明 |
|---|---|
| 状态 | 提议 / 已接受 / 已废弃 / 被替代 |
| 背景 | 是什么问题促使我们做这个决定 |
| 决策 | 最终选择了什么,一句话讲清 |
| 后果 | 做这个决定之后,好处与代价分别是什么 |
ADR 的价值不在「决策」本身,而在「选项」与「后果」——它逼你把被否决的方案也写下来。当半年后有人问「当初为什么不用消息队列」,翻一翻 ADR 就有答案,而不是靠某个人模糊的记忆。
非功能需求与质量属性
功能需求决定系统能不能用,非功能需求决定系统能不能长期用下去。它们往往隐藏在一句轻描淡写的话里,却对架构影响深远。
| 质量属性 | 典型需求表述 | 对架构的影响 |
|---|---|---|
| 性能 | 首页 95 分位响应不超过 500ms | 缓存策略、读写分离、异步化 |
| 可用性 | 全年可用率不低于 99.9% | 多副本、故障转移、降级预案 |
| 可扩展性 | 业务量三年增长十倍 | 无状态服务、水平分片 |
| 可维护性 | 新增一类订单流程不超过两天 | 插件化、开闭原则、模块解耦 |
| 安全性 | 敏感数据全程加密 | 密钥管理、传输与存储加密 |
质量属性之间存在此消彼长的关系:追求极致性能可能牺牲可维护性,追求高可用会推高成本。架构师的工作不是让每个属性都拉满,而是在业务约束下找到一组平衡点,并向团队讲清取舍。
架构评审与演进
架构不是一次画完就冻结的图纸。它会随业务发展不断演进,因此需要建立定期体检的机制:
- 架构评审:重大变更前评审,重点看依赖方向是否被破坏、质量属性是否仍然满足。
- 依赖检查:在持续集成中加入依赖规则检查,禁止领域层被基础设施层反向依赖。
- 技术债登记:把临时妥协显式记录进待办列表,而不是口口相传。
- 演进式设计:优先做「可替换」的设计,把不确定的部分封装在边界之后。
好的架构有一个共同特征:它让正确的做法变成最省力的做法。当团队成员想「偷懒」时,如果顺手写出来的代码天然就是解耦的,那么架构才真正发挥了作用。
架构的终极目标不是完美,而是让系统在面对变化时,改动成本始终可控。