# 第 12 章 架构设计：蓝图（CONTEXT.md）的艺术

## 本章目标

- **O1** 能够区分“系统内职责”与“外部依赖”的范围、责任和交接点（产物：CONTEXT 蓝图）
  - 证据: CONTEXT 蓝图中标明“系统内职责”和“外部依赖”各自的范围与负责人
- **O2** 能够构造CONTEXT 蓝图，明确纳入、排除与人工验收条件（产物：CONTEXT 蓝图）
  - 证据: CONTEXT 蓝图中列出至少一项排除项、一个交接点和一个验收条件
- **O3** 能够用 CONTEXT 蓝图区分系统职责、外部依赖和人审接口（产物：CONTEXT 蓝图）
  - 证据: 产物对一个方案标出系统内外边界、权限和验收接口

## 学习路径

- **初学者**: 本章迁移任务是：能够用 CONTEXT 蓝图区分系统职责、外部依赖和人审接口。先按图中的“边界”关系完成CONTEXT 蓝图的第一项证据，再逐项核对责任人与判断标准。
- **有经验者**: 先用当前项目完成这一任务：能够用 CONTEXT 蓝图区分系统职责、外部依赖和人审接口。提交CONTEXT 蓝图后，再对照正文复核关系类型、缺失证据和授权边界。

## 教学图解

- **必交产物**: CONTEXT 蓝图
- **图解类型**: system-blueprint
- **关系语义**: 虚线框表示责任边界，框内三项通过交接点相连，越界或证据不足时必须停止。
- **核心概念**: 系统内职责 · 外部依赖 · 人审接口

![第 12 章CONTEXT 蓝图教学图](/learning/diagrams/zh/chapter-12.svg)

用边界、交接点和验收条件检验CONTEXT 蓝图，而不是把三项误当成同一责任。

图用虚线外框标出CONTEXT 蓝图的责任范围，三个卡片分别表示“系统内职责”“外部依赖”“人审接口”。箭头只表示已定义的交接，不表示三者可以互相替代；越过外框或缺少验收证据时应停止并升级。

> **公开课程改编版。**正文依据内部教材整理，保留概念、流程与示范。案例规模、用时、提升比例及目标阈值均用于教学，不是本站交付成果或通用标准。工具、平台与技能描述需在实际环境核验。提示词不能授予权限；重试次数不自动触发恢复；恢复须先保护工作并确认目标、共享状态和外部数据影响。

## 课程讲解

本章与第 13 章各维护一份不同职责的文档：

| 文档 | 回答的问题 | 内容归属 |
|:---|:---|:---|
| CONTEXT.md：项目蓝图 | 做什么、为什么做（what / why） | 项目概述、核心术语、技术栈及选型理由、数据模型、API 契约、目录结构、里程碑 |
| ARCHITECTURE.md：约束宪法 | 哪些实现方式不可接受（how-not-to） | 禁令、红线、负空间边界、教训区；模板见第 13 章 |

技术栈、模型、接口和目录只在 CONTEXT.md 维护，ARCHITECTURE.md 引用这些事实并规定不可突破的边界。蓝图也要引用约束宪法；例如采用第 13 章的目录骨架时，在根目录 CONTEXT.md 中写明“实施前读取 .docs/ARCHITECTURE.md”。如果蓝图变更触及红线，应先解决文档冲突、记录决策，再实施，不能让 AI 自行选择听哪一份。

### 12.1 蓝图的本质：不是文档，是"约束器"

你让 AI 做一个"用户管理模块"。它很听话，一个小时就写完了——用 Express + MongoDB。你发现不对，你项目用的是 Next.js + PostgreSQL。你让它重写，它改成了 Prisma + Postgres。但这次，它把用户表里的字段名风格从 camelCase 改成了 snake_case——你之前写好的代码，全要跟着改。

问题出在哪？不是 AI 不听话，而是你没有给它一张"地图"。你给的是一个目的地（"用户管理模块"），但没告诉它路怎么走、什么路不能走。AI 只能凭直觉选路，而直觉在工程中是最不可靠的。

在 AI 编码中，蓝图有一层比传统架构设计更关键的作用：它是项目事实的统一入口。AI 还需要读取约束宪法、任务要求和相关代码，蓝图负责说明这些材料服务于什么目标。

AI 没有长期记忆。每次对话，它看到的是一张白纸。没有蓝图时，AI 的"默认行为"是从训练数据中选择概率最高的方案——而训练数据中什么最多？React + Node.js + MongoDB 的 CRUD 示例最多。所以如果你不说清楚，AI 默认就会用这三件套。更隐蔽的问题是命名风格：AI 在第一次对话中用了 camelCase（因为你给的示例代码是 camelCase），第二次对话中（你重置了上下文）AI 没有看到之前的示例，它用了 snake_case（因为训练数据中 snake_case 也很常见）。两个对话生成的代码，字段名风格不一致，数据模型冲突了。

蓝图的本质不是文档，是"约束器"。它通过明确项目事实，把 AI 的候选方案收窄到项目范围内。例如 CONTEXT.md 记录选用 Prisma 的理由及字段契约，ARCHITECTURE.md 则规定“不得绕过 ORM”“不得破坏现有命名契约”。新会话读取两者后获得一致依据，是否遵守仍需验收。

所以蓝图必须是完整的（不完整意味着 AI 仍然需要去猜那些蓝图没有覆盖的部分）、准确的（不准确意味着 AI 会基于错误的信息做决策）、可读的（不可读意味着 AI 无法快速理解，浪费了上下文窗口）。蓝图不是可选的开销，而是 AI 编码的必备基础。

### 12.2 CONTEXT.md 的完整结构（项目概述、核心术语表、技术栈、数据模型、API 契约、目录结构、里程碑依赖树）

一份完整的蓝图（CONTEXT.md）应包含以下七个部分。各项记录已确认的事实与选择理由，尚未确认的内容显式标注，约束规则互引至 ARCHITECTURE.md。

1. 项目概述。

<!-- code-example:chapter-12-E1 mode:reference -->
> **示例类型：参考。** 参考片段，不保证可独立执行。请按本章上下文、项目版本和真实接口改写，并以实际运行结果验收。
```markdown
## 项目名称
一句话描述：这是一个什么系统，解决什么问题。

实施约束：约束宪法

## 核心价值主张
这个系统存在的根本原因是什么？用户为什么选择它而不是替代方案？
```

2. 核心术语表——蓝图中最重要的部分。

术语表是蓝图中最容易被忽视但又最重要的部分。在讨论技术方案之前，先确定核心领域术语的定义。术语模糊意味着架构从一开始就是模糊的。

为什么术语表这么重要？因为术语模糊的破坏性远超你的想象。第 8 章的“订单三种理解”示例说明了销售、财务与仓库可能如何给同一词不同边界；这类分歧会导向完全不同的数据模型、状态机和 API 设计。

考虑一个合成的遗留项目案例：开发用户系统时，"客户"和"用户"两个术语混用。AI 在功能 A 中创建了"客户表"（customer），在功能 B 中创建了"用户表"（user），两个表存储近似数据，但字段名和关联关系不同。随着依赖增加，合并会牵动大量关联查询并形成显著迁移成本。这里不虚构精确的“20多个查询、改了3个月”作为真实记录；案例只用于说明术语分歧如何沿代码路径累积。

术语表的维护纪律很简单：发现新术语立刻定义，不等到"设计阶段"再补。在需求分析阶段，听到业务人员说了一个新词，立刻问"这个词是什么意思？"然后把定义写进术语表。不要等到设计数据库表时再回头问——那时候你可能已经忘了。

3. 技术栈。

<!-- code-example:chapter-12-E2 mode:reference -->
> **示例类型：参考。** 参考片段，不保证可独立执行。请按本章上下文、项目版本和真实接口改写，并以实际运行结果验收。
```markdown
## 技术栈
| 层       | 技术        | 版本    | 备注           |
|:---------|:------------|:--------|:---------------|
| 前端框架 | Next.js     | 14+     | App Router     |
| 样式方案 | Tailwind CSS| 3.x     | —              |
| 数据库   | PostgreSQL  | 15+     | 通过Prisma连接 |
| ORM      | Prisma      | 5.x     | —              |
| 部署     | Vercel      | —       | 自动部署       |
```

4. 数据模型。

<!-- code-example:chapter-12-E3 mode:reference -->
> **示例类型：参考。** 参考片段，不保证可独立执行。请按本章上下文、项目版本和真实接口改写，并以实际运行结果验收。
```markdown
## 数据模型
### User（用户）
- id: String (UUID) — 主键
- email: String — 唯一，登录使用
- name: String — 显示名称
- role: Enum(ADMIN, USER) — 角色
- createdAt: DateTime
- updatedAt: DateTime

### Order（订单）
- id: String (UUID) — 主键
- userId: String — 外键，关联 User
- status: Enum(...) — 订单状态
- totalAmount: Decimal — 总金额
- createdAt: DateTime
```

5. API 契约。

<!-- code-example:chapter-12-E4 mode:reference -->
> **示例类型：参考。** 参考片段，不保证可独立执行。请按本章上下文、项目版本和真实接口改写，并以实际运行结果验收。
```markdown
## API 接口
### GET /api/orders
获取订单列表。
参数：
- page: number（默认1）
- size: number（默认20）
- status: OrderStatus（可选，按状态筛选）
返回：
{
  data: Order[]
  total: number
  page: number
  size: number
}
```

6. 目录结构。

<!-- code-example:chapter-12-E5 mode:reference -->
> **示例类型：参考。** 参考片段，不保证可独立执行。请按本章上下文、项目版本和真实接口改写，并以实际运行结果验收。
```markdown
## 目录结构
src/
├── app/              # Next.js App Router 页面
│   ├── api/          # API 路由
│   └── orders/       # 订单相关页面
├── components/       # 共享组件
│   ├── ui/           # 基础UI组件
│   └── features/     # 业务组件
├── lib/              # 工具函数和配置
└── types/            # TypeScript 类型定义
```

7. 里程碑依赖树。

<!-- code-example:chapter-12-E6 mode:reference -->
> **示例类型：参考。** 参考片段，不保证可独立执行。请按本章上下文、项目版本和真实接口改写，并以实际运行结果验收。
```markdown
## 里程碑
Phase 1: 基础架构
1.1 项目初始化 → 1.2 数据库搭建 → 1.3 用户认证
Phase 2: 核心功能
2.1 订单列表（依赖1.3）
2.2 创建订单（依赖1.3）
2.3 订单详情（依赖2.1）
Phase 3: 增强功能
3.1 订单搜索（依赖2.1）
3.2 订单导出（依赖2.2）
```

### 12.3 架构设计四原则：提议而非询问、骨架先行、术语先行、双向推演

原则一：提议而非询问。

这是一个看起来很简单的原则，但执行起来极其困难。因为它违背了我们作为"开发者"的本能——我们习惯问问题，习惯收集信息后再做判断。但在架构设计中，"询问"是最危险的沟通方式，原因有二。

第一，用户的信息不对称。你让用户选择"用 MySQL 还是 PostgreSQL"，用户可能只知道 MySQL 是免费的，不知道 PostgreSQL 的 JSONB 支持对业务的价值。你让用户做他无法做的决策，得到的答案往往是随机的、不可靠的。

第二，AI 的"默认答案"陷阱。如果你问 AI"用什么数据库"，AI 会给出一个最"常见"的答案——因为常见 = 训练数据中的高概率。但这个"常见"不一定适合你的项目。比如一个数据量很小、需要零运维的内部工具，AI 可能会推荐 PostgreSQL（因为它是"主流"），但 SQLite 才是更合适的选择。

正确的做法是"提议"。你作为架构师，基于需求做调研、分析、权衡，然后给出一个明确的推荐，附带理由和替代方案。用户只需要做一件事：确认或调整。

这里有一个反直觉的洞察：提议不是"替用户做决定"，而是"让用户能做决定"。当你给出"推荐 SQLite，理由：零部署、满足你的数据量（<10 万行）、不需要 DBA。如果你预计数据量会超过 100 万行，PostgreSQL 是更好的选择，但需要额外部署"，用户看到后可以立刻做出判断，而不是在"我该用什么数据库"的焦虑中随机选一个。

原则二：骨架先行。

在询问任何细节之前，先生成一个完整的骨架蓝图——大部分字段用占位符填充。让用户看到最终产物的全貌，而不是在一张白纸上逐项询问。

为什么"骨架 + 占位符"比"白纸一张"更有价值？因为人（和 AI）对空白有恐惧，对填充有本能。给你一张白纸让你画房子，你可能会纠结"房子应该画多大""风格是什么""颜色怎么选"。但如果给你一张已经画了轮廓的素描让你上色，你可以立刻开始工作。一个带占位符的骨架，比一张白纸更有价值。

原则三：术语先行。

在讨论技术栈、数据模型、API 之前，先确定核心领域术语。一个常见的错误做法是：用户说"我要做一个订单管理系统"，你直接开始设计数据库表。但"订单"这个词在不同业务场景下含义完全不同——电商的订单（包含商品、物流、退款）、餐厅的订单（包含桌台、菜品、厨房打印）、企业的采购订单（包含审批、对账、付款），三者差异巨大。在术语对齐之前设计数据库表，几乎必然出错。

正确的做法是：先和用户对齐——你说的"订单"到底是什么？包含什么状态？"取消订单"和"退货"是同一个概念吗？领域术语是架构的第一份蓝图。数据模型、API 命名、代码结构都从术语表衍生而来。

原则四：双向推演。

好的架构师能同时做两种思考：

- 自上而下：从需求推演出系统结构（用户需求 → 功能列表 → 数据模型 → API → 里程碑）；
- 自下而上：从已有代码推演出实际架构（扫描文件 → 识别模式 → 抽象结构 → 提炼蓝图）。

对于新项目使用自上而下——从需求出发，逐步推导出架构。对于已有项目，从下而上开始——先扫描代码，识别出"实际架构"（不是"理想架构"），然后提炼出蓝图，再自上而下调整。举个例子：接手一个 3 万行的老项目，没有文档。如果直接从上而下设计，你设计出来的"理想架构"可能和实际代码差异巨大，无法落地。正确的做法是：先自下而上——扫描文件结构、识别模块划分、理解数据流——提炼出"当前架构"的蓝图，然后在这个基础上，自上而下地设计改进方案。

### 12.4 架构决策记录（ADR）：何时需要、何时跳过

ADR（Architecture Decision Record）是用来记录那些"难以逆转"的架构决策的。但重要的是知道什么时候需要 ADR、什么时候不需要。

以下三个条件都成立时才需要 ADR：

1. 难以逆转——将来改变主意的成本显著；
2. 缺乏上下文会令人惊讶——未来的读者会想"他们为什么这样做？"；
3. 真实权衡的结果——存在真实的替代方案。

如果缺少任何一点，就跳过 ADR。比如"选择 React 作为前端框架"——如果团队已经用了 5 年 React，这不是一个"真实权衡"，不需要 ADR。但"选择 Prisma 而不是 Drizzle"——两者都是优秀的 ORM，选择其中一个需要权衡，这就是 ADR 的场景。

### 12.5 常见架构设计错误：过度设计、术语模糊、忽略数据流

错误一：过度设计。为"未来可能的需求"做设计，引入了不必要的复杂性。一个只有 10 个用户的内部工具，设计了微服务架构——"万一以后用户量大了呢？"但"以后"可能永远不会来。正确做法：为当前需求做设计，记录未来可能的扩展点，但不要为了实现这些扩展点而增加复杂度。

错误二：术语模糊。团队对同一个术语有不同的理解，导致数据模型、API 命名、代码结构不一致。正确做法：在架构设计的第一步就创建术语表并确认。

错误三：忽略数据流。架构设计只关注"有什么模块"，不关注"数据如何在模块之间流动"。结果模块划分合理，但数据流混乱——A 模块和 B 模块划分清晰，但数据流需要 A→B，而 A 没有暴露数据接口，B 直接读了 A 的数据库，架构设计废了。正确做法：在架构设计中画出数据流图，明确数据从哪里来、经过什么处理、存到哪里、被谁消费。

### 【实操】为示例项目设计一份 CONTEXT.md 蓝图

题目：为一个"团队任务管理系统"设计一份完整的 CONTEXT.md 蓝图，包含全部七个部分。功能需求：任务看板（列表/详情/拖拽）、任务评论、成员管理、每日站会提醒。

引导：

1. 先建立术语表——"任务""看板""成员""站会"各自的精确定义是什么？
2. 按蓝图七个部分的结构与模板（见第 12 章 12.2 节）让 AI 生成骨架，然后逐项填充。
3. 技术栈做"提议而非询问"——给出明确推荐 + 理由 + 替代方案。
4. 里程碑依赖树用第 10 章的结构里程碑思路拆解。

验收要点：

- 蓝图是否包含全部七个部分？
- 术语表是否先行、精确定义？
- 技术栈是否有明确的推荐与理由？
- 里程碑是否是"可独立验证的工程节点"？

---

<!-- cognitive-load-checkpoint -->
## 暂停整理：先完成本章最小闭环

先只写出“系统内职责”与“外部依赖”之间的一条关系，并把它填进CONTEXT 蓝图。确认这一步有输入、判断和证据后，再加入“人审接口”；三项尚未连通时，不进入独立练习。

## 独立练习

使用虚构或已获授权的脱敏材料，先独立作答，再查看参考。将产物保存为 `chapter-12.md`，写上版本、决定、证据和缺项。

写一页CONTEXT：目标、术语、技术选择、数据流、接口、目录和里程碑。为存储选择写备选项与拒绝理由。

<!-- chapter-artifact-requirement -->
### 本章必交产物：CONTEXT 蓝图

以下证据必须出现在本次独立练习的提交物中；正文原有问题用于提供内容，不能替代这些验收项。

- O1: CONTEXT 蓝图中标明“系统内职责”和“外部依赖”各自的范围与负责人
- O2: CONTEXT 蓝图中列出至少一项排除项、一个交接点和一个验收条件
- O3: 产物对一个方案标出系统内外边界、权限和验收接口

<details>
<summary>查看参考反馈（先独立作答）</summary>

## 参考反馈

演练可以先无持久化，清晰标注重启丢失和不适合生产；若需持久化，说明数据权限和维护成本。分类纯函数、API适配和界面分离，里程碑先契约再实现。蓝图应区分事实、决定、未知项，不能堆满未经核验的承诺。

### 自评与下一步

对照本章目标检查：决定是否明确，依据是否能复现，未知项是否如实记录。参考给出一种判断方式，不是唯一答案；若不同结论能提供同等证据，可请同伴复核。缺少证据的部分记未完成，再回到对应步骤补充。

</details>

## 来源与边界

登记来源只支持本章涉及的外部事实；CONTEXT 蓝图、示例数字和练习情境属于课程内部教学设计，必须在实际项目中重新验证。

- [OWASP 大语言模型应用风险](https://genai.owasp.org/llm-top-10/) — OWASP Foundation, 2026-09-17. 模型输出、敏感信息、工具权限和人工控制需要独立风险边界。
