单体还是拆服务:vibe 项目的架构选型实战
对一到三个人的小团队,默认答案是模块化单体;只有部署冲突、扩容分叉、硬性隔离三个真信号出现两个时,才认真谈拆分。本篇讲清三真三假信号、拆分顺序与一人团队的三条预留接缝;示例均为虚构。

本篇示例中的产品、公司与人物均为虚构,如有雷同纯属巧合;决策方法与检查清单可以直接套用到你自己的真实项目上。

先说默认答案:小团队默认选模块化单体
几乎每个 vibe 项目都会在某个深夜遇到这个问题:代码越写越长,功能越加越多,然后有人——可能是你自己,也可能是某篇技术文章——问了一句:“是不是该拆成微服务了?”这个问题之所以难回答,不是因为答案有多高深,而是因为它问错了时机。架构选型不是一道“单体还是微服务”的二选一考题,而是一组关于团队规模、变更频率和故障代价的判断。选错的代价也不是立刻爆炸,而是未来半年里每一次改动都要多付一点利息。
这篇指南给出的默认答案先说在前面:对一个人或者两三个人的小团队,默认选模块化单体。只有当三个真信号里至少出现两个时,才认真考虑拆服务。剩下的篇幅,就是讲清楚哪三个信号是真的,哪三个是假的,以及真要拆的时候,怎么拆才不会把项目拆散架。
先把概念对齐:单体、模块化单体、微服务
单体不是“把所有代码写进一个文件”。一个健康的单体也可以内部划分清晰的模块:用户模块、订单模块、内容模块,各自有边界,只是它们部署在一起、共享同一个数据库、跑在同一个进程里。模块化单体就是把这个“内部边界”做到位:模块之间只能通过明确的接口调用,不能随手跨模块读写对方的表,不能为了图方便就互相引用内部实现。
微服务则是把模块边界升级成了部署边界和组织边界:每个服务独立部署、独立扩缩容、往往还有自己的数据库,服务之间只能通过网络调用。代价也随之升级:以前一次函数调用能解决的事,现在要经过网络、序列化、重试、超时、版本兼容;以前一个事务能保证的事,现在要靠最终一致性和补偿逻辑。很多团队只看到了微服务的收益——独立部署、独立扩容,却低估了这份代价清单的长度。
虚构的例子可以帮我们把抽象落到地上。假设阿远一个人用 AI 帮手做了一款“松果相册”:上传照片、自动打标签、按时间线浏览。上线三个月,用户量不大,但代码已经有两万行。阿远问自己要不要拆服务。按这篇指南的方法,第一步不是去看微服务的教程,而是先检查:他的代码里模块边界清不清楚?照片处理、标签识别、时间线浏览这三个部分,是不是已经各自独立、互不乱碰对方的数据?如果答案是“还搅在一起”,那么他现在最需要的不是微服务,而是一次模块化整理。拆服务解决不了模块不清的问题,只会把“搅在一起的代码”变成“搅在一起还隔着网络的服务”,调试难度直接翻倍。
三个真信号:出现两个再谈拆分
第一个真信号是部署冲突。团队里不同的人、不同的功能,总是因为要发版而互相等待:你改了一个小文案,也要等另一个人的大功能一起上线;或者某次发版明明只改了 A 模块,却把 B 模块也拖下水了。当“发版”本身变成每周最紧张的会议,说明部署边界已经不够用了。注意,这里的关键是“频繁且痛苦”,而不是“偶尔不方便”。一个月发一次版的小项目,即使发版时有点紧张,也远不到拆服务的程度。
第二个真信号是扩容需求真的分叉了。绝大多数模块的资源需求是差不多的,只有个别模块明显不同:比如图片处理特别吃 CPU,或者某个导出功能偶尔会跑出巨大的内存峰值。当你发现为了伺候一个模块的峰值,不得不把整个应用都扩容到很贵的规格时,拆分才开始有经济账可算。在此之前,单体加一台好一点的服务器,通常比拆服务便宜一个数量级,也简单一个数量级。
第三个真信号是故障隔离或者合规隔离的硬性要求。比如支付相关的数据必须单独存放、单独审计,或者某个模块一旦出问题,绝不能拖垮核心流程。这种要求往往来自外部:合同、法规、客户承诺,而不是技术偏好。硬性要求出现时,拆分就不是“要不要”的问题,而是“怎么拆才合规”的问题,优先级自然最高。
三个信号里出现两个,再认真启动拆分讨论;只出现一个,先用更轻的办法缓解:部署冲突可以用功能开关和更小的发版粒度缓解,扩容分叉可以先把重活抽成后台任务,合规隔离可以先从数据表级别的访问控制做起。直接跳到拆服务,相当于用手术解决感冒。
三个假信号:听到就要警惕
第一个假信号是“大家都用微服务”。大公司的架构文章写的是它们在几百人规模下的问题,不是你的问题。把别人的答案抄到自己的试卷上,是架构选型里最常见的错误。判断标准永远应该是你自己的三个真信号,而不是行业潮流的风向。
第二个假信号是性能焦虑。很多小项目在用户量还不到一千的时候,就开始担心“单体会不会撑不住”。实际上,单体撑不住的门槛远比想象中高:一台配置体面的服务器、合理的数据库索引、把重活放进后台队列,这三板斧足以支撑绝大多数独立项目走到几万用户。过早拆服务省下来的性能,远远抵不上它带来的运维成本。
第三个假信号是简历驱动。不得不坦白说,这一条很多人心里有但嘴上不说:想在简历上写“主导微服务架构”,或者想让项目看起来更“专业”。为简历做架构,是对用户和对自己都不负责的选择。用户不会因为你的架构图好看而多付一分钱,只会因为你的功能稳定好用而留下来。架构的专业程度,体现在它恰好够用,而不是体现在它有多复杂。
真要拆:按这个顺序拆,别按感觉拆
如果三个真信号确实出现了两个,拆分的顺序应该是:先划模块边界,再拆数据库,最后才拆网络调用。这个顺序不能反。第一步是在单体内部把模块边界收紧:明确每个模块的公开接口,禁止跨模块直接读写对方的数据表,补上模块之间的契约测试。这一步做完,你会发现很多“以为要拆服务”的问题,其实在单体里就已经解决了;即使最后还是要拆,这一步也是拆分的前提,不会白做。
第二步是拆数据库,而且要按“先只读、再写入”的节奏来。先让新服务只读旧库,验证数据一致性;再把写入一点点切过去,每一步都有回滚方案。数据库拆分最忌讳的就是“周末一刀切”:周六晚上切库,周日早上发现数据对不上,周一用户已经在投诉了。小团队没有专职 DBA,数据库迁移必须按“每一步都能单独回滚”的粒度来切。
第三步才是把模块变成独立部署的服务,并且用“绞杀者模式”过渡:新请求先走新服务,老请求继续走单体,流量比例一点点挪过去,直到老模块可以下线。绞杀者模式的好处是,任何一步出问题,都可以把流量立刻挪回去,而不需要一次惊险的整体切换。对小团队来说,“能随时回去”比“一步到位”重要得多。
还有一个经常被忽略的点:拆分之后,本地开发体验会变差。以前启动一个进程就能跑全站,现在要启动三个服务、两个数据库、一个消息队列。如果本地一键启动做不出来,团队的日常开发效率会先掉一截,再谈扩容收益就晚了。所以拆分计划里必须包含一句话:本地开发怎么一键跑起来。答不上来,就先别拆。
一人团队的默认姿势:模块化单体加三条接缝
回到阿远的松果相册。按这篇指南,他的最优解是:继续保持单体,但在代码里预留三条“以后可拆的接缝”。第一条接缝是模块接口:照片处理、标签识别、时间线三个模块之间只走公开接口,不互相碰对方的表。第二条接缝是事件而不是直接调用:标签识别完成时发一个事件,时间线模块订阅这个事件,而不是直接去读标签模块的内部状态。这样将来把标签识别拆成独立服务时,调用关系不用重写。第三条接缝是配置与密钥的分离:每个模块的外部依赖(比如对象存储、AI 接口)都走统一的配置入口,而不是散落在代码各处。
这三条接缝的成本很低,几乎不增加今天的开发负担,但它们把“未来拆分”从一次重写变成了一次搬运。这就是模块化单体的精髓:你不是在拒绝微服务,你是在为将来可能需要的微服务保留选择权,同时不为今天不需要的东西付账。很多小团队的架构悲剧,不在于选错了单体或微服务,而在于选了单体却把代码写成了“一锅粥”,将来想拆的时候发现无从下手。
一张决策清单,写下来再动手
部署冲突是不是每周都在发生,并且已经影响了发版节奏?扩容需求是不是真的分叉到了“为一个模块扩容整个应用”的程度?有没有来自合同、法规或客户的硬性隔离要求?三个问题里有几个是“是”?零个或一个:留在模块化单体,把精力花在收紧模块边界上。两个:可以启动拆分讨论,但先按“先边界、再数据库、最后网络”的顺序做计划,并且先把本地一键启动方案写出来。三个:拆分已经是必答题,按绞杀者模式分步推进,每一步保留回滚路径。
最后再提醒一句:架构选型没有一劳永逸的正确答案,只有“在当前规模下最划算”的答案。单体不是落后的代名词,微服务也不是先进的奖状。真正专业的表现,是在团队只有一两个人的时候,敢于说“我们现在不需要微服务”,并且把单体内部的边界做得足够干净,让未来有一天真的需要拆分时,拆分是一次有准备的搬运,而不是一次绝望的重写。
拆分之后的新债:你要多养的五样东西
决定拆分之前,还有一本账值得算清楚:拆分不是一次性手术,它是你每个月都要继续付的运维账单。第一样是跨服务的可观测性:以前一个进程里的报错,现在分散在三个服务的日志里,出了问题要先回答“到底是哪个服务先出的错”,这就需要统一的请求追踪编号和集中的日志查看方式,否则排查时间会从分钟变成小时。第二样是版本兼容:服务之间通过网络调用,任何一方的接口改动都可能让另一方出错,于是你需要接口版本管理、灰度发布和兼容性测试,这些在单体里根本不存在的工作,拆分后全都成了日常。
第三样是本地开发环境。前面已经提过,拆分后本地要启动的东西变多了,如果这个环境经常起不来、或者新成员第一天配不好环境,团队的日常效率就会被持续侵蚀。第四样是分布式事务的替代方案:以前一个数据库事务能保证的“要么都成功、要么都回滚”,拆分后要靠事件、补偿操作和人工对账来模拟,每一种都有自己的失败模式,都需要在设计时就想好。第五样是值班与故障响应:服务越多,半夜可能响起的报警就越多,小团队没有专职运维,值班压力最终会落到写代码的人自己头上。这五样东西没有哪一样是“顺便”就能做好的,它们就是拆分的真实月供。算完这笔账还觉得值得拆,那拆分的决心才算立住了。
给 AI 结对者的特别提醒:先让 Agent 帮你收紧边界
对 vibe 开发者来说,架构选型还有一个时代红利:模块边界的收紧工作,非常适合交给 Agent 来做,而且 Agent 做这件事的性价比很高。你可以给 Agent 一个明确的任务:找出所有跨模块直接读写对方数据表的地方,列成清单;把模块之间的调用收敛到公开接口上;为模块接口补上契约测试。这一类任务边界清晰、验证标准明确——测试通过就是通过,不通过就是不通过——正好是 Agent 擅长的类型。
反过来,有一类任务不适合直接交给 Agent:那就是“要不要拆服务”这个判断本身。Agent 会倾向于给出看起来很专业的答案,比如一份结构工整的微服务拆分方案,因为它在训练数据里见过太多这样的方案。但它不知道你的发版节奏,不知道你的用户量,不知道你的值班压力,也不知道你的合规要求。判断必须由你来做,Agent 只能在你做完判断之后,帮你执行判断的结果。分工搞反了——让人做执行、让 Agent 做判断——是 vibe 开发里最隐蔽的失误之一。
还有一个实操建议:让 Agent 做边界收紧的时候,一定要要求它“每次只改一个模块,并且每改完一个模块就跑一次完整测试”。一次性让 Agent 重构整个代码库,是架构整理里最容易翻车的做法:改动太多,出问题的时候根本定位不到是哪一处引起的。小步快跑、每步可验证,这个原则对人写代码适用,对 Agent 写代码同样适用,甚至更加适用,因为 Agent 不会像人一样在改动中间停下来凭直觉检查一遍。
最后回到起点:架构选型这件事,最怕的不是选错,而是选了之后不复盘。无论你最后留在单体还是拆了服务,都值得在三个月后回头看一眼:当初判断的真信号还在吗?拆分(或者没拆)带来的收益和成本,和预期相符吗?如果不相符,下一阶段是继续、调整,还是合并回去?能回答这三个问题,架构就是活的;回答不了,架构就只是当初拍脑袋留下的一张图。图会过时,复盘的习惯不会。
评论 (0)
相关文章

竞品拆解的目的不是抄得更快,而是少抄一点、抄得更准。本篇给出五步法:定战场、拆公开面、只写差异的对比表、一句定位话、每月一小时复检,并附七问自查清单;示例产品、公司与人物均为虚构。

用户流失常常发生在空白页那一步,而不是产品不好用。本文讲怎么把模板库做到一步可用:数量控制在 6 到 10 个、名字写任务不写概念、预览放真实示例、一键套用、套用后可自由改动,外加数据判断与上线检查表。

页面顶上挂个红色倒计时不等于会做倒计时。本文给一人团队一套做法:按距离选形式、截止时间精确到分钟并注明时区、绝不造假紧迫感、过期页面同样认真设计、外加一份上线检查表。