Agent X-Ray
RuntimeNotesAbout
Notes/产品经理/AI native 软件工程教程/第7章

构建一个新系统的设计和思考

12 分钟 · 更新于 2026-09-01

构建一个新系统的设计和思考

构建新系统时,最容易出现的偏差,是过早讨论框架、数据库和目录结构。技术选型当然重要,但它们应当回答已经明确的问题,而不是代替问题定义。

一套更稳妥的设计顺序是:

目标与约束 → 领域边界 → 质量属性 → 技术决策记录 → 架构与依赖方向 → 目录结构 → 风险验证 → 验收清单

这条主线的重点不是一次性设计出“完美架构”,而是让每项选择都能追溯到目标,让不确定性尽早暴露,并为后续人工开发或 AI 辅助开发提供稳定上下文。

本文使用一个中性示例贯穿说明:为一个约 80 人的组织构建“共享设备预约与借还系统”。员工可以预约会议摄像机、测试手机和便携投影仪,管理员负责设备登记、借出、归还和异常处理。

一、目标与约束:先定义要解决的问题

系统设计的起点不是“采用什么技术”,而是“系统为什么存在,以及什么条件不能被忽略”。

1. 明确目标

目标应描述业务结果,而不是功能堆叠。示例系统可以定义三个目标:

  1. 员工能够查询设备在指定时间段内是否可用,并完成预约。
  2. 管理员能够准确记录借出、归还、损坏和停用状态。
  3. 管理者能够查看设备利用率,判断是否需要新增或淘汰设备。

“增加预约页面”“建立设备表”都不是目标,它们只是可能的实现方式。目标需要说明系统产生的可观察价值。

2. 识别用户与关键场景

至少区分直接使用者、业务管理者和系统维护者:

  • 员工:搜索设备、预约、取消预约、查看自己的记录。
  • 设备管理员:登记设备、确认借出与归还、处理异常。
  • 组织管理者:查看利用率和异常统计。
  • 系统维护者:部署、监控、备份和恢复系统。

随后为每类用户写出最重要的场景。例如:“员工选择设备和时间段,系统确认无冲突后生成预约记录。”这类场景比抽象的“实现预约模块”更容易转化为规则和测试。

3. 写清约束

约束会直接影响架构选择。示例系统可能存在以下条件:

  • 首期只服务一个组织,不需要多租户。
  • 日活用户不足 100,人力投入有限。
  • 预约数据需要保留两年。
  • 登录必须接入组织现有身份系统。
  • 首期部署在现有云环境中,不新增复杂基础设施。
  • 必须在六周内交付可用版本。

这些条件意味着:系统无需一开始采用微服务,也不必引入复杂的消息平台;但身份集成、数据保留和快速交付属于不可回避的设计输入。

输出物 完成本阶段后,应形成一页简短说明:系统目标、主要用户、核心场景、范围边界、已知约束和暂不处理事项。

二、领域边界:决定哪些问题由谁负责

目标明确后,需要把业务问题拆成职责稳定的领域,而不是直接拆成页面或数据库表。

1. 先找业务对象和行为

示例系统中的主要业务对象包括:

  • 设备:编号、类别、状态、存放位置;
  • 预约:预约人、设备、开始时间、结束时间、状态;
  • 借还记录:实际借出时间、实际归还时间、经办人;
  • 异常事件:损坏、遗失、超期、维修、停用。

对象本身还不够,还要识别围绕它们发生的行为:

  • 判断某台设备是否可预约;
  • 创建、取消或过期预约;
  • 将已预约设备交付给预约人;
  • 登记归还结果;
  • 将损坏设备切换为不可用状态。

2. 按业务职责划分边界

可以把示例系统划分为四个领域模块:

  • inventory:设备档案、分类、位置和可用状态;
  • reservation:时间段冲突检查、预约创建、取消和过期;
  • handover:借出、归还、超期和异常登记;
  • reporting:利用率与异常统计。

身份认证、日志、通知和持久化不属于核心业务领域,它们是支撑多个领域的技术能力。

3. 用规则检验边界

判断边界是否合理,可以问三个问题:

  1. 这个模块能否用一句话说清自己的职责?
  2. 一条业务规则是否只在一个模块中拥有权威实现?
  3. 修改一个领域的规则时,是否会迫使无关模块一起变化?

例如,“同一设备的有效预约时间不能重叠”应由 reservation 负责。页面可以展示冲突提示,数据库可以增加约束,但规则的业务含义不能散落在控制器、页面组件和定时任务中各写一份。

4. 明确暂不进入系统的范围

边界也包括“不做什么”。首期可以明确排除:

  • 设备采购审批;
  • 财务折旧管理;
  • 面向外部组织的租赁;
  • 基于历史数据自动推荐采购数量。

主动排除低优先级能力,可以防止架构为假设中的未来需求提前付出成本。

三、质量属性:把“系统要好用”变成可衡量条件

功能描述系统“做什么”,质量属性描述系统“做到什么程度”。如果质量属性没有优先级,团队往往会默认同时追求高性能、高可用、强一致、低成本和快速交付,最终无法判断方案是否合适。

1. 选择真正重要的质量属性

示例系统首期可以优先考虑:

  • 正确性:不能产生相互冲突的有效预约。
  • 可追溯性:设备状态变化必须能查到时间、操作者和原因。
  • 可维护性:两名开发者能够理解并修改任一业务模块。
  • 可恢复性:误操作或部署故障后能够恢复关键数据。
  • 安全性:普通员工不能执行管理员操作。

高并发并不是首要属性,因为使用规模有限。忽略真实规模而过度优化吞吐量,会挤占更重要的规则验证和审计能力。

2. 使用场景化表达

质量属性应尽量写成可验证场景:

属性场景可验证标准
正确性两名员工同时预约同一设备的重叠时段最多一笔预约成功,另一笔收到明确冲突结果
可追溯性管理员将设备标记为损坏能查询操作者、操作时间、变更前后状态和备注
性能员工查询未来七天可用设备正常负载下 95% 请求在 1 秒内返回
可恢复性数据库发生误删或实例故障关键数据可在约定恢复时间内还原
安全性普通员工调用设备停用接口请求被拒绝,并记录安全审计信息

3. 明确取舍顺序

当属性冲突时,需要预先约定优先级。示例系统可以采用:

预约正确性 > 权限与审计 > 交付速度 > 查询性能 > 技术扩展性

这意味着团队可以接受报表稍慢,却不能接受为了减少一次数据库操作而放松预约冲突控制。

四、技术决策记录:记录选择、原因和代价

技术决策不应只存在于会议记忆或聊天记录中。建议使用简短的 Architecture Decision Record(ADR),让每项关键选择都可以追溯。

1. ADR 的基本结构

一条 ADR 至少包含:

  • 背景:当前要解决什么问题;
  • 决策:选择了什么方案;
  • 备选项:还考虑过哪些方案;
  • 理由:为什么当前方案更适合;
  • 后果:获得什么,同时承担什么限制;
  • 复审条件:什么变化发生时需要重新评估。

2. 示例:采用模块化单体

背景:系统规模小、交付周期短,但预约、借还和统计之间存在明确业务边界。

决策:首期采用模块化单体,在一个应用进程和一个代码仓库中交付,各领域模块通过公开接口协作。

备选项:微服务、无明确模块边界的传统单体。

理由:模块化单体保留清晰边界,同时避免分布式事务、服务发现、跨服务调试和独立部署带来的额外成本。

后果:部署和本地开发较简单;团队必须通过代码规则维护模块边界,不能依赖网络隔离。

复审条件:某一模块出现独立扩缩容、独立发布或数据隔离的明确需求,并且收益大于拆分成本。

3. 示例:预约冲突由数据库事务兜底

背景:仅在应用层执行“先查询、再写入”,无法可靠处理并发请求。

决策:应用层先做友好校验,写入阶段使用事务和数据库约束保证最终正确性。

后果:实现稍复杂,但并发情况下仍能守住核心规则;错误处理需要把数据库冲突转换为可理解的业务结果。

ADR 的价值不在于文档数量,而在于减少重复争论。只有影响长期结构、质量属性或团队协作方式的选择,才值得单独记录。

五、架构与依赖方向:让业务规则不被外部细节控制

架构设计的核心不是画出多少层,而是规定依赖可以朝哪个方向流动。

1. 建立稳定的依赖方向

示例系统可以采用以下方向:

text
入口层 → 应用层 → 领域层
                  ↑
基础设施层通过接口适配领域或应用层定义的能力

各层职责如下:

  • 入口层:HTTP 接口、管理页面、命令行任务;负责接收输入和返回结果。
  • 应用层:组织用例,例如“创建设备预约”“确认设备归还”。
  • 领域层:保存业务规则、状态变化和领域对象。
  • 基础设施层:数据库、身份系统、消息通知、日志等具体实现。

关键规则是:领域层不依赖 Web 框架、数据库驱动或外部身份平台。外部能力通过接口接入,使业务规则可以独立测试。

2. 用一个用例检查依赖

以“创建预约”为例:

  1. 入口层接收设备、时间段和当前用户信息。
  2. 应用层调用预约用例。
  3. 领域层判断时间是否合法、设备状态是否允许预约。
  4. 应用层通过仓储接口查询和保存预约。
  5. 基础设施层实现仓储接口,并处理事务与数据库约束。
  6. 入口层把结果转换为成功响应或冲突提示。

如果页面组件直接执行 SQL,或者数据库模型直接调用通知服务,依赖方向就被打乱了。短期看似省事,长期会增加测试和替换成本。

3. 控制跨模块协作

模块之间只通过公开能力协作。例如:

  • reservation 可以读取 inventory 提供的设备可预约状态;
  • handover 可以读取已确认的预约信息;
  • reporting 可以消费稳定的查询模型或领域事件;
  • reporting 不应反向修改预约规则。

对于当前规模,不必为了“解耦”而强制引入消息中间件。进程内事件或明确的应用服务调用已经足够,技术复杂度应与实际问题匹配。

六、目录结构:让代码布局反映设计

目录结构不是架构本身,但它能强化或破坏既定边界。好的目录让开发者和 AI 都能快速判断:规则应放在哪里、允许依赖谁、修改会影响哪些范围。

示例项目可以采用按领域组织的结构:

text
src/
├── modules/
│   ├── inventory/
│   │   ├── domain/
│   │   ├── application/
│   │   ├── infrastructure/
│   │   └── public.ts
│   ├── reservation/
│   │   ├── domain/
│   │   ├── application/
│   │   ├── infrastructure/
│   │   └── public.ts
│   ├── handover/
│   └── reporting/
├── interfaces/
│   ├── http/
│   ├── admin-ui/
│   └── jobs/
├── shared/
│   ├── auth/
│   ├── observability/
│   └── database/
├── config/
└── bootstrap/

tests/
├── unit/
├── integration/
└── acceptance/

docs/
└── decisions/

1. 目录规则

  • modules 按业务领域划分,不按“所有控制器”“所有服务”横向堆放。
  • 每个模块通过 public.ts 或等价入口暴露能力,外部代码不直接引用模块内部文件。
  • domain 保存业务规则,不引入 Web 和数据库实现。
  • application 编排用例和事务边界。
  • infrastructure 实现仓储、第三方适配器和持久化映射。
  • interfaces 保存各类系统入口,不承载核心规则。
  • shared 只放真正跨领域且稳定的能力,不能成为无归属代码的收容区。
  • docs/decisions 保存 ADR,并与实际实现保持一致。

2. 为什么不追求过细目录

目录层级越多,不代表设计越成熟。若一个模块只有少量文件,可以暂时合并层级;当职责和文件数量增长后再拆分。结构的目标是降低定位成本,而不是展示形式上的完整性。

3. 为 AI 辅助开发提供明确线索

当结构、命名和依赖规则一致时,AI 更容易完成以下判断:

  • 修改冲突规则时优先检查 reservation/domain
  • 新增预约接口时组合 interfaces/httpreservation/application
  • 替换数据库实现时主要修改 infrastructure
  • 编写业务单元测试时不必启动完整应用;
  • 检查跨模块调用时只需关注公开入口。

项目说明文件应记录代码无法自行表达的规则,例如禁止的依赖、必要命令、数据安全要求和 ADR 位置,而不是重复介绍每个目录中已经清楚可见的文件。

七、风险验证:先验证最可能推翻设计的假设

设计完成后,不应立即把全部功能铺开。先列出高风险假设,并用最低成本验证它们。

1. 建立风险清单

示例系统的主要风险可能包括:

风险影响验证方式
并发预约产生重复占用核心业务失效编写并发集成测试,验证事务与约束
组织身份系统集成方式不明确登录与权限无法按期交付制作最小登录原型,验证用户标识和角色字段
设备管理员实际流程比预期复杂借还模型需要重构用可交互流程与管理员走查典型及异常场景
报表查询影响日常操作使用体验下降使用模拟数据进行查询计划和响应时间测试
备份存在但无法恢复数据风险不可控在隔离环境执行一次完整恢复演练

2. 优先验证高不确定、高影响事项

验证顺序不应由实现难度决定,而应由“失败后是否会推翻设计”决定。身份集成和并发冲突可能影响整体方案,应在早期验证;按钮样式和报表布局通常可以延后。

3. 使用技术探针而不是半成品功能

技术探针是一段用于回答具体问题的最小实现,例如:

  • 20 个并发请求能否保证只有一个预约成功?
  • 身份平台能否提供稳定用户编号和角色信息?
  • 数据库能否表达时间段互斥约束?
  • 备份文件能否在目标时间内完成恢复?

探针的产物可以被丢弃。它的目的不是提前交付功能,而是减少关键未知量。

4. 把验证结果反馈到设计

每次验证后,应更新对应内容:

  • 假设成立:保留决策,并记录证据;
  • 假设不成立:修改 ADR、边界或质量目标;
  • 仍不确定:缩小问题,安排下一轮验证;
  • 风险可接受:记录接受原因和后续监控条件。

系统设计不是单向流程。验证结果可能迫使团队返回前面的任一步骤重新判断。

八、验收清单:判断是否可以进入持续实现

在开始大规模编码前,可以使用以下清单检查设计是否已达到“足够明确”的状态。

目标与约束

  • 系统目标描述的是业务结果,而不是技术功能。
  • 已识别主要用户及其关键场景。
  • 已写明范围内、范围外和首期暂缓事项。
  • 时间、人员、部署、合规和集成约束已经记录。

领域边界

  • 核心业务对象和行为已经识别。
  • 每条关键规则都有明确归属模块。
  • 模块职责可以用简短句子说明。
  • 跨模块协作通过明确接口完成。

质量属性

  • 已选择当前阶段最重要的质量属性。
  • 每项重要属性都有可验证场景和标准。
  • 属性发生冲突时有清晰的优先级。
  • 没有为缺乏证据的规模假设提前增加复杂度。

技术决策

  • 关键技术选择记录了背景、方案、理由和代价。
  • 重要备选项已经比较,而不是凭偏好排除。
  • 每项长期决策都有可识别的复审条件。
  • 文档中的决策与实际代码保持一致。

架构与目录

  • 依赖方向明确,并能通过代码规则或测试检查。
  • 领域规则不依赖具体 Web 框架和数据库实现。
  • 目录结构能够反映业务边界与层次职责。
  • 公共模块没有变成无边界的工具集合。
  • 新成员或 AI 能根据结构判断代码应放置的位置。

风险验证

  • 已列出高影响、高不确定的设计假设。
  • 关键风险有对应的原型、探针或测试计划。
  • 并发、权限、恢复和外部集成得到实际验证。
  • 验证结果已反馈到 ADR、架构或验收标准中。

结语

构建新系统的设计过程,本质上是不断减少模糊性的过程:先确认目标和限制,再划分业务责任;先确定需要保障的质量,再选择技术;先规定依赖方向,再让目录结构承载这些规则;最后用实验和清单检验设计是否经得起实现。

对人工团队和 AI 辅助开发而言,最有价值的不是复杂术语,而是一组一致、可追溯、可验证的工程约束。当目标、边界、决策和代码结构相互印证时,系统才能在持续变化中保持可理解、可维护和可演进。


  • 如何快速构建一个初始化项目?
  • 工程问题与科学问题的区别?
  • AI native 软件工程师课程目录

本章目录
一、目标与约束:先定义要解决的问题二、领域边界:决定哪些问题由谁负责三、质量属性:把“系统要好用”变成可衡量条件四、技术决策记录:记录选择、原因和代价五、架构与依赖方向:让业务规则不被外部细节控制六、目录结构:让代码布局反映设计七、风险验证:先验证最可能推翻设计的假设八、验收清单:判断是否可以进入持续实现结语Related Documents
苏ICP备2025204887号-2