4、系统架构设计文档:架构图绘制规范(C4模型)、模块接口定义(IDD)、系统约束与假设
好,咱们今天聊聊系统架构设计文档。说实话,这章是整门课里我最想跟你细聊的部分之一。为什么?因为我在项目里见过太多「画图一时爽,维护火葬场」的案例了。架构图画得天花乱坠,结果三个月后连画图的人自己都看不懂——嗯,我自己就干过这种事。
所以今天咱们把话说透:架构图怎么画才不坑人?接口怎么定义才不打架?约束和假设怎么写才不背锅?
4.1 架构图绘制规范:C4模型实战
先说说C4模型。这玩意儿不是什么高深理论,说白了就是一套「从远到近、从粗到细」的画图套路。我习惯把它理解成「地图缩放」——你看高德地图,先看全国,再看城市,再看街道,最后看具体建筑。C4模型也是这个道理。
C4模型的四个层次:
- Context(系统上下文图):系统在哪儿?跟谁打交道?
- Container(容器图):系统由哪些「大块头」组成?比如Web服务器、数据库、消息队列。
- Component(组件图):每个容器里有哪些「小零件」?比如登录模块、支付模块。
- Code(代码图):具体到类、接口、函数——这个我一般只在关键模块画。
我在项目里遇到过最典型的翻车案例:有人直接画了个「系统架构图」,里面既有服务器又有函数调用,还有数据库表结构。你想想看,这图给项目经理看,他看不懂;给开发看,又嫌太粗。所以C4模型的核心价值就是——不同的人看不同的图。
4.1.1 系统上下文图(Context)
这张图最简单,也最容易被人忽略。我建议你把它放在文档的第一页。它回答三个问题:
- 我们的系统叫什么?
- 谁在用这个系统?(用户、其他系统)
- 系统跟外部怎么交互?(API、文件、消息)
画法要点:中间一个大方块代表你的系统,周围画小人或方块代表外部角色,箭头表示交互方向。别画太复杂,一张A4纸能放下就行。
我的小技巧:用不同颜色区分「人」和「系统」。人用蓝色,系统用灰色。这样一眼就能看出哪些交互需要做UI,哪些是后台对接。
4.1.2 容器图(Container)
这张图是技术选型的核心。每个「容器」就是一个可独立部署的单元。比如:
- 前端App(React Native)
- 后端API服务(Spring Boot)
- 数据库(MySQL)
- 缓存(Redis)
- 消息队列(RabbitMQ)
画图时注意:每个容器都要标注技术栈。我曾经接手过一个项目,图上只写了「数据库」三个字,结果部署时才发现用的是MongoDB还是PostgreSQL都不知道——你说坑不坑?
4.1.3 组件图(Component)
到了这一层,咱们开始聊「模块」了。比如你的后端API容器里,可能有:
- 用户管理组件
- 订单处理组件
- 支付网关组件
- 通知推送组件
画组件图时,我习惯用分层布局:上层是接口层(Controller),中间是业务层(Service),下层是数据层(Repository)。这样谁依赖谁,一目了然。
注意:组件图不要画成「面条图」。如果两个组件之间有超过3条连线,说明你的模块划分有问题。我见过最夸张的一个项目,组件图里全是交叉线,最后重构时发现80%的代码都在互相调用——说白了就是没有解耦。
4.1.4 代码图(Code)
这一层我一般只在关键模块画。比如支付模块、权限校验模块。画的是类图、接口图、时序图。别把整个系统的类都画上去——没人看,也没人维护。
4.2 模块接口定义(IDD)
接口定义文档(Interface Definition Document),简称IDD。这东西写得好,团队协作效率翻倍;写得烂,联调时天天吵架。
我个人的习惯是:每个接口都要包含以下6个要素:
| 要素 | 说明 | 示例 |
|---|---|---|
| 接口名称 | 唯一标识,建议用动词+名词 | createOrder |
| 输入参数 | 参数名、类型、是否必填、取值范围 | userId (String, 必填) |
| 输出结果 | 成功/失败时的返回结构 | { code: 0, data: {...} } |
| 错误码 | 每个错误码的含义 | 1001: 参数无效 |
| 调用方式 | 同步/异步、REST/gRPC/消息 | POST /api/v1/orders |
| 性能要求 | 响应时间、吞吐量 | P99 < 200ms |
这里我要特别强调一下错误码。很多团队写IDD时只写「成功」的情况,失败的情况一笔带过。结果联调时,调用方不知道「参数错误」该返回什么码,「服务超时」又该返回什么码。我建议你从一开始就把错误码定义清楚,最好做成一个枚举表。
接口定义示例(伪代码):
// 接口:创建订单
// 调用方式:POST /api/v1/orders
// 请求体:
{
"userId": "string (必填)",
"productId": "string (必填)",
"quantity": "int (必填, 1-100)",
"couponCode": "string (可选)"
}
// 成功响应:
{
"code": 0,
"message": "success",
"data": {
"orderId": "string",
"totalAmount": "float",
"status": "string"
}
}
// 错误码:
// 1001 - 参数无效(具体字段在message中说明)
// 1002 - 库存不足
// 1003 - 用户余额不足
// 2001 - 系统内部错误
4.3 系统约束与假设
这部分很多人觉得「没啥好写的」,其实恰恰相反——约束和假设是甩锅神器,也是保护自己的护身符。
什么叫约束?就是「我们必须在这些条件下干活」。比如:
- 硬件约束:CPU主频1GHz,内存512MB,Flash 8MB
- 网络约束:带宽10Mbps,延迟<50ms
- 时间约束:系统必须在100ms内响应
- 安全约束:必须支持TLS 1.2以上
什么叫假设?就是「我们默认这些条件成立」。比如:
- 假设网络连接稳定,丢包率<0.1%
- 假设第三方服务可用性>99.9%
- 假设用户输入符合规范(不做极端恶意输入测试)
- 假设系统运行在Linux x86_64环境下
我曾经踩过的坑:有个项目,我们假设「数据库连接池默认20个连接就够了」。结果上线后用户量暴增,连接池被打满,系统直接挂了。后来复盘时发现,这个假设根本没写在文档里。从那以后,我要求团队把所有假设都白纸黑字写下来,哪怕看起来再理所当然。
写约束和假设时,我建议你遵循「SMART原则」:
- Specific(具体):别写「性能要好」,要写「响应时间<200ms」
- Measurable(可测量):别写「要安全」,要写「通过OWASP Top 10安全测试」
- Achievable(可实现):别写「永不宕机」,要写「可用性99.9%」
- Relevant(相关):只写跟系统设计直接相关的
- Time-bound(有时限):别写「未来扩展」,要写「2025年Q2前支持10万并发」
最后说一句:文档是活的,不是死的。架构图、接口定义、约束假设,这些都要随着项目进展持续更新。我见过太多项目,文档写完之后就再也没人碰过——那还不如不写。
好,这一章就聊到这儿。下一章咱们讲详细设计文档,到时候我会分享一些「画时序图不翻车」的实战经验。