架构到底是什么:结构、约束与决策
架构不是文档里那张漂亮的框图,而是一组关于结构的重要决策,加上让这些决策长期不被破坏的约束。判断一句话描述算不算架构,只需问:把它改掉,代价会不会显著上升?会,它属于架构;不会,它只是实现细节。
架构的三要素
| 要素 | 回答的问题 | 典型产出物 |
|---|---|---|
| 结构(structure) | 系统由哪些部件组成、谁调用谁 | 模块划分、分层图、部署图 |
| 约束(constraint) | 哪些事以后不许做 | 依赖方向规则、接口契约、数据访问边界 |
| 决策(decision) | 为什么这样选、付出的代价是什么 | 架构决策记录(ADR)、选型对比表 |
只有结构没有约束,架构会在半年内退化成一张过期图;只有约束没有决策,团队会照着规则做却不知道为什么,一遇例外就崩。三者齐备,架构才具备可执行性。
哪些是架构,哪些不是
| 描述 | 是否架构 | 理由 |
|---|---|---|
| 订单服务通过 gRPC 暴露接口,不直连他人数据库 | 是 | 改动会影响多个团队 |
| 系统分为接入层、领域层、基础设施层 | 是 | 决定长期依赖方向 |
表 t_order 的 remark 字段从 varchar(200) 扩到 500 | 否 | 局部、可逆、低耦合 |
选用 Go 1.22 的 slog 而不是第三方日志库 | 否 | 局部可替换,代价可控 |
区分标准不是"听起来是否宏大",而是不可逆程度 × 影响范围。可逆且局部的,交给代码评审即可;不可逆或跨团队的,才值得写进架构决策记录。
架构的四个视角
同一套系统,不同角色关心不同侧面,架构描述要能同时支撑他们:
业务方 :能不能支撑明年三倍的订单量,扩容要多久
开发 :新加一个"发票"功能要改几个模块,会不会踩到别人的表
运维 :部署单元有几个,宕一个会怎样,回滚要几分钟
安全与合规 :敏感数据流经过哪些组件,谁能读到明文
视角之间常常冲突:开发想要灵活,运维想要少部署单元,业务想要快。架构的工作不是消灭冲突,而是把冲突显式化并给出取舍。
架构描述的推荐形态
一张好的架构图应当能回答"边界在哪里、依赖往哪走":
┌─────────────┐
│ 客户端 App │
└──────┬──────┘
│ HTTP/JSON
┌──────▼───────────────────────────┐
│ 接入层(gateway) │ 鉴权、路由、限流见 system-design 第 15 章
└──────┬───────────────────────────┘
│ 内部接口
┌──────▼──────────┐ 领域事件 ┌──────────────────┐
│ 订单领域服务 │─────────────▶│ 履约领域服务 │
│ (order) │ │ (fulfillment) │
└──────┬──────────┘ └────────┬─────────┘
│ 仅本域数据 │ 仅本域数据
┌──────▼──────────┐ ┌────────▼─────────┐
│ order_db │ │ fulfillment_db │
└─────────────────┘ └──────────────────┘
图上的箭头方向就是依赖方向,每一条都要能说出理由。画不出箭头的图,通常只是部署清单,不是架构。
一个从零推导的小例子
需求:给现有单体电商加"下单后发短信通知"。
先别急着引入消息队列(MQ 选型见 middleware-interview 相关章节),先过三个问题:
- 结构:通知是独立模块还是独立服务?初期调用量小,先做单体里的一个模块,只暴露
Notifier接口。 - 约束:下单主流程不得因为短信网关超时而失败;通知模块不得直接读订单表,只能接收订单事件。
- 决策:暂不引入 MQ,用进程内事件 + 本地重试表;写下触发条件——"日均通知超过 50 万条或需要多方订阅时,升级为独立服务 + 消息队列"。
这三点写清楚,架构就已经成立了,代码量可能只有 200 行。架构的分量在于约束与决策,不在于组件数量。
架构决策记录的最小格式
标题:通知能力以进程内事件实现,暂不引入消息队列
状态:已接受(2024-06-01)
背景:日均通知 3 万条,仅一个消费者(短信)
决策:order 模块发布领域事件,notify 模块订阅;失败进本地重试表
后果:
- 优点:零新增中间件,事务与事件同库,实现简单
- 缺点:多消费者场景下无法独立扩缩容,重试会占用应用线程
复审条件:日均 > 50 万条,或出现第二个非短信消费者
最后一行"复审条件"最容易被忽略,却最关键——没有复审条件的决策会变成永久教条,后人在不知情下继续叠加复杂度。
架构评审清单
- 每个部件的职责能否用一句话说清,且不与其他部件重叠?
- 依赖方向是否单向、是否有循环依赖?
- 跨边界调用是否只通过接口或事件,而不是直接访问对方存储?
- 关键质量属性是否被翻译成可验收的场景与指标(见下一章)?
- 每个重要选择是否记录了被放弃的备选方案与代价?
- 是否存在"没有它也能上线"的组件?有就说明过度设计。
常见误区
- 把架构等同于技术选型:选框架是结果,不是出发点;先有约束才有选型。
- 架构文档一次成型:架构是持续演进的产物,文档过期比没有文档更危险。
- 只画静态图不讲运行时:扩容、故障、发版时的行为才是架构真正的考验。
- 追求"最先进":先进意味着更少的社区经验、更多的人力成本,多数业务不需要。
- 架构师不写代码:不接触代码就无法知道约束是否真的可执行,规则会迅速脱离现实。
小结:架构 = 结构 + 约束 + 决策,判断标准是不可逆程度与影响范围。落地时先画清边界与依赖方向,把每个重要选择连同代价和复审条件写进架构决策记录,再配上可执行的评审清单。质量属性如何量化,是下一章的主题。