AI辅助编程时,给出两种典型架构错误:要么所有逻辑堆在一个服务里导致等待外部调用(如邮件)阻塞响应,要么过度拆分出五个服务每个内部又包含多个组件,反而增加复杂度。
你让 AI 帮你写一个应用,它把整个系统塞进了一个 Service。我之前也写过这个话题:Service 是一个服务员,接收点单然后带回答案。让 AI 帮你写代码,它会把所有事情都放在那一个"点单"里完成:验证输入、查询数据库、发送邮件,全部在返回响应之前完成。在演示环境里这样是可以工作的。到了生产环境,邮件发送这一步很慢,于是每个用户都得盯着加载动画,而你的服务器在等别人的邮件服务。
让同一个 AI 在你写代码之前先设计这个系统,你会得到截然相反的错误。同一个 AI,同一个应用,不同的错误。你和另一个工程师在做一个合租分摊租金和账单的应用。让 AI 来设计,它给了你五个 Service:认证、计费、通知,前面还有一个 API Gateway,后面还有一个消息队列连接它们。
五个 Service 听起来还可以,直到你真正去数每个 Service 里面有什么。认证不是一件事。它本身是一个 Service、一个存放用户账号的关系型数据库、还有一个存放会话的 Key-Value Store:三个构建块。计费是一个 Service 加上它自己的charges 关系型数据库:又是两个。通知是一个 Service、一个 Queue、和一个发送邮件的 Worker:又是三个。API Gateway 是第四个 Service 放在其他所有东西前面,消息队列本身也是一个 Queue。至少十个构建块,分布在五个独立的部署里,而团队只有两个人。
这个应用实际需要的样子和上面说的完全不一样。有人登录并添加了一笔账单:一个 Service。账单需要知道它属于哪些用户以及如何分摊:一个关系型数据库。有人标记账单已付时,你想给其他室友发邮件,这件事不需要在有人盯着屏幕的时候发生,所以它进入一个 Queue,Worker 稍后把它取出来处理。一个 Service、一个关系型数据库、一个 Queue 和 Worker:整个系统就齐了。四个构建块,一个部署,对比十个构建块分布在五个部署里。
让 AI 写代码,它默认使用最简单可能的形式:一个函数、一个 Service,请求进来、响应出去。它从来不会主动用到 Queue,因为 Queue 是一个关于"什么可以等待"的决策,而不是代码的默认形态。让 AI 自己设计一个系统,它默认使用它读过最多讨论的那种形态,那就是大公司解决问题的方式——而那些问题你并没有。两个错误根源相同:它在匹配你 prompt 的体裁,而不是你实际的约束。
这就是为什么构建块比"Service"这个词更重要。微服务不是一个构建块。它是一个完整的部署,里面可以包含两到三个构建块。用 Service 数量来数,"五个"听起来像是五个要开发的东西。用构建块来数,是十个要开发、要连接、要维护的东西,分布在五个独立运行的部署里。对比四个构建块在一个部署里。
微服务在以下情况下才值得付出它的成本:不同的团队需要互不等待地独立发布、一个部分需要不同的硬件、或者不能允许一个故障拖垮所有其他部分。两个人做一个应用,完全没有这些压力。你需要的是一个可以集中修改的地方、一次部署、晚上十一点可以理清的一件事:单体应用。不是那种你最终要毕业的初学者版本。在这个阶段,它就是正确的设计。
把 AI 给出的答案——无论是构建得太少还是太多——用一个问题检验:我的团队现在是否存在证明这个设计合理的那种压力?多个团队在部署上互相踩踏。某个组件需要不同的硬件。可以衡量的流量,而不是为明年想象的流量。如果那种压力不是真实存在的,就把那个部分去掉,不管它是一个缺失的 Queue 还是一个多余的 Service。
这就是为什么我在第一课里教七个构建块,在任何人画第一个框之前。先知道每个构建块是做什么用的,你就能审视任何答案——AI 给的或者你自己想的——然后问它实际上是为了扛住什么而构建的。
你不需要修复 AI 给出的答案。你要检查它每个部分背后的压力是什么。
P.S. 如果你想要这个问题的可重复版本,我写了一个完整的决策框架:每个构建块一个问题,这样你就能面对任何需求,准确知道它需要什么。https://systemthinkinglab.ai/learn/building-blocks/decision-framework/
这封信每周六通过邮件发送。点击此处订阅,或在 systemthinkinglab.ai/newsletters 阅读完整存档。