架构设计不是画图:在约束条件下做决策,让系统持续演进
很多技术人第一次接触“架构设计”这四个字是从面试题开始的。面试官问“说说你做过的最有挑战的架构设计”你绞尽脑汁想起自己画过的那张模块划分图然后讲了一个拆分服务、引入消息队列的故事。等真正到了项目里才发现架构设计远不是画图那么简单。另一个更常见的场景是团队里代码写得好的人不少但没人能拍板“这个功能应该放哪个模块”“这个新需求要不要改数据模型”“这轮重构的边界到底划在哪里”。于是每个人按自己的理解加代码半年后模块边界模糊一个改动牵一发动全身。这时候大家才意识到缺的不是写代码的能力而是架构设计的能力。这篇文章想说的核心判断是架构设计不是“画图 写文档”而是在约束条件下做决策并且让这个决策能在后续几个月甚至几年的迭代里持续生效。它不是架构师的专属技能而是每一个想从“写代码的人”进阶到“设计系统的人”的开发者都必须刻意训练的核心能力。读完这篇文章你会弄清楚架构设计到底在设计什么、有哪些基本原理可以复用、一个需求从开始到形成架构方案要经过哪些步骤以及最重要的——如何避开那些看起来合理、实则让系统越来越难维护的典型误区。1. 这篇文章真正要解决的问题1.1 架构设计为什么值得重新审视在很多团队里架构设计长期处于一种“看似重要、实则缺席”的状态。说它重要是因为每个技术负责人都会在立项时强调架构先行说它缺席是因为真正落到代码里的架构决策往往是在开会时随口定的或者干脆被遗忘了。等系统出了问题翻遍文档也找不到当初为什么这么设计。这种状态带来的直接后果是系统越来越难改。新增一个字段要改五个服务改一个查询逻辑要问三个模块的负责人线上出问题不知道影响范围到底有多大。这些都是架构设计缺位的典型信号而不是单纯的代码质量问题。架构设计真正要解决的是三个问题系统的边界在哪里模块之间如何协作以及这种结构能不能支撑未来的变化。它关注的是长期演进的成本而不是某个功能的短期交付。这也是为什么架构设计能力本质上是“在不确定性中做决策”的能力。1.2 哪些人最需要提升架构设计能力如果你符合下面任意一条这篇文章就是写给你的工作 3-5 年的后端开发者代码能力已经过关但开始负责模块设计需要理解“为什么这个接口这么设计”“为什么这个逻辑要放这里”。团队里的技术 Leader 或兼职架构师需要在多人协作的背景下做技术决策并且要让其他成员理解并执行这些决策。正在从单体向微服务/模块化演进的团队骨干要处理拆分、边界、数据一致性等复杂问题。面试准备者不只是为了背概念而是为了建立一套可以应对开放式问题的思考框架。判断自己是否需要补这块能力有一个简单的自测方式当你接到一个需求第一反应是“用哪个技术方案实现”还是“这个需求落在哪个模块/边界上最合适”如果是前者说明你还在功能实现思维如果是后者你已经进入了架构设计思维。2. 架构设计的核心概念与适用场景2.1 到底什么是架构设计为了避免概念上的含糊先给出一个可操作的定义架构设计是围绕系统的质量属性可维护性、可扩展性、可用性、性能、安全性等在资源约束下对系统结构做出的关键决策集合。拆开来看有三个要素。第一目标不是功能。功能是需求决定的架构设计的对象是质量属性。同一个下单功能可以用单体实现也可以拆成订单、支付、库存三个服务。不同的架构方案功能完全一样差异在维护成本、故障隔离、扩展方式。第二约束条件决定方案。团队规模、技术栈、时间窗口、运维能力、成本预算都是约束。脱离约束谈架构很容易给出“理论上最优但团队根本落地不了”的方案。第三架构是一组决策而且多数是关键决策。比如数据库选型、服务拆分粒度、缓存策略、一致性方案。这些决策一旦落地就很难回退所以需要谨慎评估而不是凭感觉决定。2.2 架构设计与系统设计的区别很多开发者会把这两个概念混在一起导致在做架构设计时一头扎进细节。维度架构设计系统设计/详细设计关注层次系统级结构、模块划分、技术选型模块内部的类、方法、数据结构决策对象关键技术决策、边界、协议实现细节、算法、具体 API影响范围整个系统跨团队单个模块或服务内部变更成本高难以回退相对低可以重构时间视角面向长期演进面向当前迭代一个典型的错误是在做架构设计时就纠结“订单状态用 int 还是 enum 存”“方法名应该叫 createOrder 还是 submitOrder”。这些是详细设计阶段的问题放到架构阶段去抠细节只会让关键的边界决策被淹没。2.3 常见架构风格对比架构风格是长期实践中沉淀出来的通用范式。理解它们的区别能帮助你在面对需求时快速缩小选型范围。架构风格核心特征适合场景主要风险单体分层架构一个应用内分层Controller/Service/DAO中小型系统、团队规模小模块边界容易腐化部署耦合模块化单体单进程内强模块边界模块间通过接口通信规模适中想保留单体部署优势需要很强的代码纪律微服务架构独立部署、独立扩展服务间网络通信大团队、高并发、多团队并行分布式复杂度陡增事件驱动架构通过异步事件解耦生产者和消费者流式处理、系统间解耦事件链路难追踪一致性处理复杂分层 整洁架构业务核心与基础设施解耦业务规则复杂、需要长期演进抽象层级多学习成本高这里给一个明确建议不要因为微服务流行就选微服务。架构风格不是越新越好而是越匹配当前团队和业务阶段越好。很多公司把单体拆成微服务后不仅没有解决问题反而把分布式事务、链路追踪、运维成本这些新问题引入了系统。3. 架构设计必须掌握的基础原则3.1 分层每一层只做一件事分层是软件架构里最基础、也最容易做歪的原则。合理的分层每一层只解决一类问题。以常见的后端三层为例Controller 层负责协议转换和参数校验Service 层负责业务逻辑编排DAO/Repository 层负责数据访问。调用方向是单向的不允许下层反向依赖上层。但很多项目的分层是假的。Controller 里直接写 SQL、Service 里拼 HTML、领域逻辑散落在各个层里。表面上看“我们分了 Controller/Service/DAO”实际上边界在第一个迭代就失守了。判断分层是否合理可以问一个简单问题这一层拿到数据和逻辑后是否需要理解上层业务的语义才能完成任务如果需要说明边界划错了。3.2 高内聚、低耦合这是架构设计里最常被引用、也最容易被误解的一组概念。内聚度衡量的是模块内部元素属于同一职责的程度。一个负责“订单创建”的模块把创建、校验、价格计算、库存预占都放进来这是高内聚。如果它同时还处理用户登录这就是低内聚。耦合度衡量的是模块之间的依赖程度。两个模块通过明确的接口通信是低耦合。两个模块共享同一个数据库表、互相调用对方的内部方法、通过全局变量传递状态都是高耦合。在实际项目中真正的难点不是记住这两个概念而是识别出“看似低耦合、实际高耦合”的设计。比如两个服务共用一个数据库表代码上完全隔离但表结构一改动双方都得跟着变这就是典型的隐式耦合。用 DDD领域驱动设计中频繁出现的说法来总结边界不是看代码放在哪个包而是看修改会不会互相影响。3.3 SOLID 原则在架构层面的含义SOLID 原本是面向对象设计原则但放在架构层面同样成立只是解释的粒度变了。单一职责原则在架构层面一个模块应该只有一个变化的理由。订单服务不要同时负责库存扣减否则一个变化引发两处修改。开闭原则架构应该对扩展开放、对修改关闭。新增一个支付渠道时应该是增加一个策略实现而不是改动核心的支付流程代码。里氏替换原则在接口设计上所有实现类都应该能无差别替换。常见反例是接口定义“创建订单”某个实现却在内部直接抛异常或默默跳过核心逻辑。接口隔离原则调用方不应该依赖它不用的方法。宁愿拆成多个小接口不要聚合一个大而全的服务接口。依赖倒置原则高层模块不应该依赖低层模块两者都应该依赖抽象。在架构层面就是业务核心不要依赖具体的数据库驱动、消息队列客户端而应该依赖仓储接口、事件发布接口。这里最容易被忽视的是依赖倒置。因为大多数项目的依赖方向是自然的Service 依赖 DAODAO 依赖数据库连接。一切都合理但从整个系统的演进来看业务核心绑定在具体技术实现上将来换数据库、换消息中间件改动面会非常大。3.4 演进式架构面向变化的系统传统架构设计思维容易变成“一次性大设计”花两个月做一个完美的整体方案然后按图施工。问题是业务变化的速度远超架构方案的生命周期等你设计完需求已经变了。演进式架构的核心思想是架构设计不是终点而是一组支持后续演进的结构选择。你不需要一开始就把所有模块都设计到最合理状态但必须保证系统在多次迭代之后仍然有调整空间。实践上体现为三点模块边界要清晰方便未来单独演进或替换。关键技术决策要写下理由和触发条件比如“当 QPS 超过 X 时启动缓存”。接受技术债但要显式记录而不是假装不存在。演进式架构不等于不做设计。它强调的是设计要服务于“未来可以改”而不是“未来不用改”。4. 架构设计流程拆解从需求到可落地的方案4.1 第一步识别真实需求与约束架构设计最常见的失败原因是需求没搞清楚就开始了。这里的“搞清楚”不是指功能列表而是指三个层面的信息。第一业务目标。这个系统要解决谁的什么问题问题是否真的存在第二质量属性。最看重的是快速上线、高可用、高并发、还是可维护性第三约束条件。团队几个人技术栈是什么上线时间运维能力在实践中推荐把约束条件明确写下来例如- 团队人数6 人其中 2 人熟悉 Java1 人熟悉 Python - 上线时间8 周 - 运维能力无专职 SRE使用云平台托管服务 - 预估 QPS初期 50双十一目标 2000 - 数据量日增订单 10 万这些约束会直接筛选掉大量“理论上很好但做不了”的方案。比如团队没有专职 SRE、初期 QPS 只有 50那么自建一套微服务治理体系就完全没有必要。4.2 第二步确定架构风格与技术选型有了约束之后选择架构风格就不是凭喜好了。工作量法则是在满足可预见的未来需求前提下选择让团队协作成本最低的架构。初期 50 QPS 的业务单体分层足够如果团队内部业务线之间边界清晰且团队规模超过 20 人模块化单体可能比微服务更合适如果确实有两个业务线需要完全独立发布和扩展才需要考虑微服务。技术选型遵循同样的逻辑默认选择团队已经熟悉的技术栈。只有在当前技术确实成为瓶颈时才引入新技术并且要为新技术准备试用、回滚、迁移方案。很多人在这里会陷入一个误区把架构风格和技术选型当成展示技术能力的机会。这个项目用了 Redis、Kafka、Kubernetes听起来很高级但每引入一个组件都意味着团队的认知负担和运维成本上升。架构设计的专业判断恰恰体现在“不引入什么”上。4.3 第三步划分模块与边界这是架构设计流程中技巧性最强的一步。边界划分的基本方法是先找“业务的自然边界”。一个电商系统的订单、支付、库存、用户本身就有天然的职责边界。把这些边界映射到代码模块时遵循一个原则一个模块内部的变化应该尽可能是局部的一个模块的变化不应该强迫其他模块跟着变。边界确定之后要定义模块之间的交互方式。是同步调用还是异步事件接口参数是什么数据一致性要求是什么这些都是要在边界划分这一步明确下来的因为它们是模块之间唯一的契约。这里推荐一个经验做法开始写代码之前先把模块间的接口签名列出来。如果两个模块的接口设计改起来很痛苦说明边界没划好如果接口很稳定说明边界基本合理。4.4 第四步设计接口与数据模型模块边界确定后进入接口设计和数据模型设计。这一层最容易出现的问题是“接口跟着表结构走”。很多团队定义接口就是先把数据库表建出来然后 Controller 层直接返回表的映射对象。这样做的问题在于接口被数据库表绑死了。前端需要的数据结构、外部系统需要的数据格式全部受制于表结构。表一改接口就得改接口一改所有调用方都得跟着改。正确的方式是接口面向业务模型设计数据模型面向存储设计两者通过转换层映射。接口层的 OrderDTO 可以和数据库的 order 表字段不同这是正常的也是必要的。4.5 第五步评估风险与容量规划架构方案形成后不能直接进入开发要先做一轮风险识别。常见的架构风险包括单点故障、数据一致性、性能瓶颈、安全漏洞、依赖的第三方服务不稳定、团队能力短板。针对每一项要给出应对策略哪怕是“接受风险并定期复查”也要写下来。容量规划不一定需要非常精确但至少要有一个量级判断。比如预估 QPS 2000单机能扛多少需要几台机器数据库连接数够不够这些数字不需要精算但可以帮助你判断方案是否需要预留扩展能力。4.6 第六步写架构文档最后把决策和理由写下来。这一步的重要性远超多数人的预期。架构文档的第一读者是“六个月后的自己”。当你忘记了当初为什么选择这个方案当团队新人需要理解系统结构当未来的某个需求触发了技术选型的重新评估这份文档就是唯一可信的来源。文档不需要很长但要包含背景与约束、方案描述、关键决策及理由、备选方案、风险与应对、演进触发条件。5. 实战示例一个订单系统的架构设计过程下面用一个简化的订单系统来演示从需求到架构决策的完整过程。这里不做超大规模设计而是展示“约束条件下做决策”的思考路径。5.1 业务需求与分析假设需求如下用户可以在 App 下单购买商品。下单后需要创建订单、扣减库存、生成支付单。支付完成后需要通过回调通知商家系统。初期业务量不大但后续可能会接入多个商家。约束条件团队 5 人熟练 Java 和 Spring Boot。上线时间 6 周。无专职运维使用云数据库和托管中间件。基于这个背景几个关键决策可以这样分析决策一单体还是微服务团队 5 人、6 周上线、初期业务量不大显然单体应用更合适。但为了防止将来模块腐化采用“模块化单体”的策略代码上严格分为 order、inventory、payment 三个模块模块之间只通过内部接口通信不允许跨模块访问 DAO。将来如果某个模块需要独立部署可以按边界拆出去。决策二库存扣减如何实现最简单的实现是下单时直接扣减库存。但考虑到后续接入多个商家、可能出现并发抢购这里选择用一个独立的 InventoryService 接口封装库存操作内部先用数据库行锁保证扣减安全。将来如果并发量上来了再引入 Redis 预扣减方案。关键点在于调用方只依赖 InventoryService 接口将来替换实现不影响上层。决策三用不用消息队列支付回调通知商家系统如果直接使用 HTTP 同步调用回调方会因为商家系统慢而阻塞。这里引入一个简单的事件机制用消息队列解耦。由于团队对 Kafka 不熟悉初期选择云托管的 RocketMQ 或 RabbitMQ。决策要记录当订单量达到一定规模时再评估是否需要替换为其他消息中间件。5.2 模块划分结果order-system/ ├── order-module # 订单模块创建订单、查询订单、订单状态流转 ├── inventory-module # 库存模块库存占用、扣减、释放 ├── payment-module # 支付模块生成支付单、处理支付回调 ├── common-module # 公共模块统一响应、工具类、领域模型 └── infrastructure # 基础设施数据库配置、消息队列配置、启动类模块依赖方向order-module 依赖 inventory-module 和 payment-module 的接口但这两个模块不依赖 order-module。所有模块依赖 common-module 中的公共模型。5.3 接口定义示例在写具体实现之前先把核心接口定义出来。下面用 Java 接口演示模块之间的契约。// 文件路径order-module/src/main/java/com/example/order/api/OrderService.java package com.example.order.api; import com.example.common.dto.OrderDTO; import com.example.common.dto.CreateOrderRequest; public interface OrderService { /** * 创建订单 * * param request 下单请求包含商品和数量 * return 创建成功的订单信息 */ OrderDTO createOrder(CreateOrderRequest request); }// 文件路径inventory-module/src/main/java/com/example/inventory/api/InventoryService.java package com.example.inventory.api; public interface InventoryService { /** * 尝试占用库存成功返回 true库存不足返回 false */ boolean occupy(String skuId, int quantity); }// 文件路径payment-module/src/main/java/com/example/payment/api/PaymentService.java package com.example.payment.api; public interface PaymentService { /** * 创建支付单返回支付流水号 */ String createPayment(String orderId, long amount); }注意最后两个接口都返回了明确的业务结果而不是只返回一个 void。这是刻意为之的occupy返回布尔值是为了让 order-module 在处理库存不足时能走自己的业务分支createPayment返回流水号是为了后续对账和状态查询有依据。5.4 配置文件示例模块化单体虽然只有一个应用但不同模块的配置仍然应该分开管理避免在配置上互相干扰。以下是一个 Spring Boot 的配置示例。# 文件路径src/main/resources/application.yml spring: datasource: url: jdbc:mysql://localhost:3306/order_system username: order_app password: ${DB_PASSWORD} application: name: order-system order: status-flow: # 订单状态流转路径后续可以按业务调整 allowed-transitions: - from: CREATED to: PAID - from: PAID to: SHIPPED - from: SHIPPED to: COMPLETED inventory: occupy-strategy: row-lock # row-lock: 数据库行锁后面并发高时切换为 redis-pre-deduct payment: retry: max-attempts: 3 initial-interval-ms: 1000这里有两个值得留意的设计。第一密码通过环境变量${DB_PASSWORD}注入而不是写在配置文件里。第二order.status-flow和inventory.occupy-strategy都属于“可调整的业务策略”把它们放到配置中是为了在业务规则变化时不改代码就能调整。5.5 架构决策记录ADR示例每一次关键架构决策都应该有记录。下面是一个 ADRArchitecture Decision Record的简化模板。# ADR-001模块化单体作为订单系统的初期架构 ## 状态 已接受 ## 背景 订单系统需要 6 周内上线团队 5 人初期并发量低。 但业务规划中后续会接入多个商家需要保持模块可演进。 ## 决策 采用模块化单体架构单进程部署代码层严格划分 order / inventory / payment 三个模块模块间通过 Java 接口通信 禁止跨模块访问 DAO 或数据库表。 ## 后果 - 优点部署简单开发效率高模块边界清晰未来可拆分 - 缺点进程内仍有资源竞争单应用故障会影响所有模块 - 风险如果模块边界维护不严会退化成普通单体 ## 触发切换条件 - 订单模块需要独立扩容或独立发布 - 团队规模超过 20 人模块间协作成本明显上升 - 单应用故障导致多个业务同时不可用的概率不可接受ADR 的价值不在于模板本身而在于它把“为什么这么决策”固定下来了。当未来有人提出“要不我们把订单服务拆出来”可以直接 review 这份 ADR 的触发条件而不是凭感觉讨论。6. 架构设计中的关键权衡与常见误区6.1 过度设计与设计不足架构设计里最难的其实是“度”的把握。过度设计表现为为永远不会出现的并发量引入分布式架构为只有两种类型的商品建一套规则引擎为一周后才写的代码提前做抽象。这类设计会让系统在初期就背上复杂度包袱开发速度明显下降。设计不足则表现为完全不考虑扩展性模块之间随意依赖数据库表设计只满足当前一个需求。短期内开发很快但每次新需求都会引发连锁修改半年后代码已经没人敢动了。如何判断自己是过度还是不足一个经验法则是在当前迭代 一个可预见的迭代内任何抽象和拆分都应该有实际用途。如果不确定“未来会不会用到”默认不做如果确定“下个迭代会用到”就要提前留出扩展点。6.2 微服务不是银弹过去几年微服务被过度神化导致很多公司在没必要的情况下把单体拆成了微服务。微服务带来的真实收益是独立部署、独立扩展、故障隔离。但它同时引入了一整套新问题服务发现、负载均衡、链路追踪、分布式事务、跨服务的数据一致性、环境复杂度上升。这些问题加起来远比单体应用里的模块间调用要难处理。一个更稳妥的判断方式是如果你的团队还不能自然地把业务划分为多个有效自治域说明微服务的边界条件不成立。这时候强行拆分只会得到多个耦合在一起的“分布式单体”。6.3 常见的错误判断在架构评审中以下几类判断反复出现值得单独拿出来说。第一把可扩展性当成万能目标。有些系统最需要的是快速开发、快速验证却被要求预留大量扩展点结果业务没跑通代码先被抽象拖死了。第二过度追求“统一”。所有服务必须共用一套代码模板、所有接口必须有同样的包装、所有表必须有同样的字段。统一是手段不是目标。过度统一会扼杀局部优化空间。第三忽视团队能力约束。架构师设计了一套微服务治理体系但团队没人会用。方案再先进落地不了就是空转。第四把性能优化前移到架构阶段。系统还没上线就在考虑千万级并发怎么处理最后把简单系统设计成巨复杂系统。6.4 性能设计的正确姿势性能是架构设计的重要考量但不是唯一考量也不是所有系统的主要矛盾。正确的姿势是先确定一个合理的性能目标再判断当前架构能否达到而不是一开始就为极端情况设计。绝大多数系统在初期的主要矛盾是业务验证和迭代速度而不是性能。性能问题通常表现为局部热点等通过监控数据发现真正的瓶颈后再做针对性优化远比提前设计更高效。如果一定要在架构阶段做性能预留优先做“水平扩展能力”的预留而不是提前优化单点逻辑。7. 架构设计文档与评审方法7.1 一个好的架构文档写什么很多架构文档写成了“把代码结构画成图”这样的文档对决策没有任何帮助。真正有用的架构文档应该包含五个部分背景与目标为什么做这个系统核心约束是什么。架构概览模块划分和核心交互流程是后续讨论的 Base Map。关键决策每项决策的背景、备选方案、选择理由。风险与应对已知风险和对应的缓解措施。演进触发条件什么情况下需要重新评估这些决策。文档用文字描述为主辅以必要的图。图的层次不要超过三级否则读者会迷失在细节里。这里不用担心没有工具先用文字把模块、依赖、决策讲清楚比画一张复杂的图更有价值。7.2 架构评审清单架构评审不是“方案宣讲”而是“挑毛病”的过程。一份合理的评审清单至少应该包含这些检查项评审维度检查项需求理解是否明确了质量属性和约束条件而不是只列功能边界划分模块间是否有明确的接口契约是否存在隐式依赖技术选型每个选型是否有理由是否考虑了团队能力数据设计数据模型是否服务于业务而不是直接绑定接口风险处理单点、一致性、安全、容量是否评估过演进能力关键决策是否记录是否定义了触发切换的条件落地可行性团队是否能在约束时间内交付是否需要拆期评审时最容易忽略的是“回滚方案”。如果一个架构决策失败如何回到上一版本这个问题的答案会促使你思考新方案的侵入性和可逆性。7.3 演进式架构的文档维护架构文档最怕“写了就忘”。我建议把每个关键决策做成 ADR并放进代码仓库和代码一起评审。当代码变更触发了 ADR 中的某个假设变化时更新 ADR 或提出新的 ADR。这样架构决策就和代码同步演进而不是成为一份永远过时的静态文档。8. 架构师的能力模型与成长路径8.1 架构设计能力不是技术广度的堆砌很多人以为架构师就是技术栈广、什么都会一点这是一个误区。技术广度是基础但架构设计能力的核心是建模能力——从复杂业务中提取核心概念梳理概念之间的关系并转化成系统结构的能力。同样一个订单需求初级开发看到的是“要建一张订单表、一个下单接口”架构师看到的是“订单是这个业务域里的核心聚合它关联着库存、支付、物流等多个子域需要定义清晰的边界和交互协议”。两种视角的差距不是看几本书就能拉平的需要通过大量的实际项目锻炼。8.2 业务理解能力被严重低估技术出身的架构师很容易把架构设计理解成纯技术工作设计模式、中间件选型、高并发方案。但真正决定架构成败的往往是对业务的理解。一个订单系统是否要做改单流程取决于业务上允不允许用户修改订单一个库存系统是否要做超卖防护取决于业务能不能接受超卖后的补偿一个数据同步方案是否要做删除同步取决于业务上是否真的会删除数据。这些都不是技术问题而是业务问题。技术方案只是对业务规则的实现。提升业务理解能力没有捷径就是多问“为什么”为什么这个状态要存在为什么这个操作不能并发为什么这个数据要保留这么久。8.3 沟通与推动能力架构设计能力不仅仅是技术能力还包含“让方案落地”的能力。再好的架构方案如果团队不理解、不认可执行起来一定会走样。推动方案落地需要注意三点。第一把复杂决策拆成可执行的步骤而不是一次性输出一个大方案。第二在决策前充分听取团队反馈特别是那些负责实现的人。第三对方案的风险和代价坦诚不要只讲收益不讲成本。8.4 在项目里刻意练习的三种方式不用等做了架构师再开始练在现有项目里就可以刻意训练。每次新需求先画模块图画出这个需求会影响到哪些模块边界在哪里接口要怎么改。参与代码评审时关注结构不看具体实现先看“这个改动是不是发生在它应该在的模块里”“有没有跨过已定义的边界”。写 ADR哪怕是小的技术选型也按 ADR 的格式记录下来。写理由的过程就是训练决策能力的过程。9. 架构设计中的常见问题与排查思路问题现象可能原因排查方式解决方案新增需求要改动大量模块模块边界划分不合理检查每次改动涉及的文件列表区分哪些是必要改动重新梳理业务边界回归“高内聚低耦合”原则多个模块共享同一张表隐式耦合分析表的读写方画出依赖关系逐步拆分数据访问优先让写方拥有表结构接口频繁变更调用方跟着改接口设计绑定数据表结构对比接口字段和数据库表字段的对应关系接口面向业务模型增加 DTO 映射层架构文档没人看文档过于细节或与代码脱节统计文档更新时间与代码版本是否同步改为 ADR 形式随代码评审维护只记录关键决策团队不执行架构约束约束只存在于文档没有代码层面保障检查是否有自动化检查模块依赖的流程引入 ArchUnit 或自定义依赖检查脚本技术选型频繁更换选型时只评估功能没有评估运维成本回顾每次切换的真实原因选型时加入运维成本、团队熟悉度评估维度系统扩展时发现结构不支持初始设计没考虑演进触发条件复盘历史需求找出被阻塞的变更补充演进触发条件明确“何时需要重新设计”这里特别说一下依赖检查。很多架构约束在文档里写得清清楚楚但代码一旦多起来就会有人图方便跨模块调用。技术手段上Java 项目可以用 ArchUnit 做自动化规则检查把“inventory-module 不能依赖 order-module”这类规则直接写进测试用例CI 阶段自动拦截。// 文件路径src/test/java/com/example/architecture/ArchitectureRuleTest.java import com.tngtech.archunit.junit.AnalyzeClasses; import com.tngtech.archunit.junit.ArchTest; import com.tngtech.archunit.lang.ArchRule; import com.tngtech.archunit.lang.syntax.ArchRuleDefinition; AnalyzeClasses(packages com.example) public class ArchitectureRuleTest { ArchTest static final ArchRule inventory_should_not_depend_on_order ArchRuleDefinition.noClasses() .that().resideInAPackage(..inventory..) .should().dependOnClassesThat().resideInAPackage(..order..); ArchTest static final ArchRule order_module_should_not_access_dao_of_inventory ArchRuleDefinition.noClasses() .that().resideInAPackage(..order..) .should().accessClassesThat().resideInAPackage(..inventory.dao..); }配置这样的依赖规则后CI 会在合并代码前执行测试。一旦有人违反了架构边界构建直接失败而不是等问题在线上爆发。10. 最佳实践与工程建议10.1 从最小可行架构开始不要试图在第一天就设计出完美的架构。先把当前需求跑通再根据可预见的迭代方向留出扩展点。这个“最小可行架构”应当包含正确的模块边界和接口契约但不包含过多的抽象层和基础设施。10.2 把架构决策和文化沉淀下来团队协作中架构决策如果只在开会时口头确认后续执行一定走样。建议每次关键决策都产出一份 ADR并在代码库中单独建一个docs/adr目录。新人入职时第一件事就是读这些 ADR 理解系统的演进历史。10.3 用自动化工具守护架构边界人类靠自觉维护架构边界是不可靠的一定要引入自动化手段。Java 生态用 ArchUnit其他语言也可以自己写脚本扫描 import 关系和目录依赖。CI 阶段强制执行才能让架构约束成为“事实标准”而不是“建议”。10.4 定期做架构复盘每半年或每次大版本迭代后做一次架构复盘回答四个问题当前架构和文档描述的一致吗哪些模块的修改频率最高符合预期吗有哪些架构决策被证明是错的下一阶段最需要调整的结构是什么复盘不是为了追责而是为了在问题还没有变成灾难之前修正方向。10.5 给团队预留架构演进的学习空间最后一条写给团队负责人架构能力的提升不能只靠架构师一个人。每个模块负责人要学会在自己的边界内做设计决策整个团队要有“提出疑问”的通道。营造一个能安全讨论架构问题的环境比任何方法论都重要。架构设计是一个“越早知道边界条件越少踩坑”的能力。看完这篇文章建议你从手头正在做的项目开始选一个新需求画出它会影响的模块图写一份 ADR然后对照“第 7 节”的评审清单检查一遍。坚持三个月你对架构设计的理解一定会发生质变。这套方法本身不需要额外的工具成本只需要开始练习。