3.2 架构视图、质量权衡与决策记录
架构不是方框数量,也不是技术名词清单。它是一组对系统结构、关键质量属性和高成本选择有长期影响的决策。架构文档的任务,是让不同读者看到与自己关注点匹配的视图,并理解这些决定为何成立。
从架构重要需求出发
不是每条需求都影响架构。通常需要优先处理:
- 决定系统边界或主要职责分配的功能;
- 可用性、性能、安全、合规等关键质量属性场景;
- 无法轻易改变的技术与组织约束;
- 风险高、未知多或修改代价大的决定。
例如,“报名页面按钮颜色”通常不改变架构;“支付确认必须可审计且不能因消息重投重复入账”会影响数据模型、幂等策略和系统边界。
C4:用不同缩放层级解释静态结构
C4 的核心静态结构图有四层:System Context、Container、Component 和 Code。这里的 Container 不是专指 Docker 容器,而是可独立运行或部署的数据存储、应用、服务等单元。
| 层级 | 回答的问题 | 主要读者 |
|---|---|---|
| System Context | 系统服务谁,与哪些外部系统交互? | 所有利益相关者 |
| Container | 系统由哪些可运行/存储单元组成,怎样通信? | 技术人员与相关业务方 |
| Component | 某个容器内部有哪些主要职责? | 开发团队 |
| Code | 组件怎样落到类、接口或模块? | 具体实现者 |
不必为了“完整”画满四层。C4 官方说明中,Context 和 Container 对多数团队已经足够;Component 与 Code 只在能回答实际问题时添加。
一个擂台系统的 Context 视图可以写成:
[参赛者] ----报名/取消/查赛程----> [擂台系统]
[管理员] ----建赛事/裁决/审计----> [擂台系统]
[擂台系统] ----收退款请求--------> [支付平台]
[擂台系统] ----发送通知----------> [消息供应商]进入 Container 视图后才讨论内部单元:
[Web 应用]
-> HTTPS/JSON -> [业务应用]
-> SQL -> [关系数据库]
-> publish -> [消息代理]
[通知 Worker] <- consume --------+
-> HTTPS -> [消息供应商]每个框应标明名称、类型/技术和职责;每条关系标明方向、目的与协议。只有箭头而没有语义,读者无法判断它是同步调用、异步事件还是数据依赖。
UML:按问题选择图,不按清单交作业
UML 是一套标准化建模语言,不要求项目使用所有图。常见选择是:
- 类图:领域结构、关键关系和静态依赖;
- 时序图:一次场景中的调用顺序、边界与失败路径;
- 状态机图:实体生命周期和合法转换;
- 部署图:运行节点与制品的映射。
状态机尤其适合报名流程:
PENDING --支付成功--> CONFIRMED --用户取消--> CANCELLED
| |
+--超时-------------> EXPIRED
+--赛事结束--> COMPLETED这张图还不完整:团队必须明确重复事件、非法转换和并发转换的处理。图的价值在于逼出问题,不能替代文字契约。
时序图应包含重要失败路径,而不只画“阳光路径”。例如支付成功后写库失败,究竟由回调重试、对账任务还是人工流程恢复,会直接影响一致性设计。
4+1:按利益相关者关注点组织视图
Kruchten 的 4+1 模型用多个并行视图描述架构:
| 视图 | 重点 |
|---|---|
| Logical | 面向功能的主要设计元素与关系 |
| Process | 运行期进程、并发、通信与同步 |
| Development | 源码模块、子系统和开发组织 |
| Physical | 软件到硬件/运行节点的部署映射 |
| Scenarios(+1) | 用关键用例驱动并验证其他视图 |
C4 与 4+1 不是一一对应的替代品。C4 主要提供层级化结构表达,4+1 强调不同关注点;项目可以用 C4 Container 图解释静态单元,再用时序图解释进程交互、用部署图解释物理视图。
架构图必须绑定质量属性
图上增加缓存、消息代理或副本,并不会自动获得高性能与高可用。每个结构选择都应回到场景:
需求:决赛期间排名查询 p95 < 300 ms。
候选:每次实时聚合;维护预计算排名;短 TTL 缓存。
代价:新鲜度、写放大、失效复杂度、额外运维组件。
验证:在约定并发和数据规模下做负载测试,并观测尾延迟。架构设计是在约束下分配责任和风险,不是从模式目录中挑名字。
用 ADR 保存“为什么”
图通常展示当前结构,却很难解释为什么没有选另一个方案。Architecture Decision Record(ADR)适合记录单个重要决定:
# ADR-0003:首个版本采用模块化单体
状态:Accepted
## Context
团队规模、交付期限、关键质量属性、已知增长假设。
## Options
1. 模块化单体
2. 独立微服务
## Decision
选择模块化单体,并以明确模块 API 隔离报名、赛程和结算。
## Consequences
- 本地事务和部署流程更简单;
- 模块不能独立扩缩容;
- 若增长假设失效,按监控数据重新评估边界。ADR 应记录上下文、备选方案、决定、理由和后果。已接受的决定发生变化时,新增一条 Superseded 记录并互相链接,比悄悄改写历史更容易追溯。
只记录对结构、关键质量属性或可逆性有显著影响的决定。“使用四个空格缩进”更适合代码规范。
让文档保持可用
- 图上注明标题、范围、受众、更新时间和图例;
- 让名称与代码、部署和监控中的名称一致;
- 文档与代码放在同一评审流程,变更结构时同步更新;
- 删除过期图,或明确标记为历史快照;
- 用关键场景走查图:正常路径、超时、重试、部分故障和恢复;
- 对低置信度决策写出验证方法与复审触发条件。
本章交付练习
为一个自己熟悉的系统完成:
- 一张 System Context 图;
- 一张 Container 图;
- 一个包含失败路径的时序图或状态机图;
- 两条可度量的质量属性场景;
- 一份记录真实权衡的 ADR。
如果某个框、箭头或组件无法关联到需求、质量属性或约束,先问它是否真的需要出现在架构中。