快团团社群团购类系统源码开发架构选型分析:为什么用模块化单体,而不是微服务?

一个预测

如果你做过面向中小企业的交易系统,大概率被客户或同事问过这个问题:"咱们要不要上微服务?"

问的人通常分两种:一种是真的关心系统能不能撑住业务增长;另一种是把"微服务"当成了"技术先进"的同义词——毕竟 Spring Cloud 的全家桶图看起来就比一张单体架构图专业。

这篇给出我的答案,并且不给"看情况"这种和稀泥的答案:这个项目,模块化单体(Modular Monolith),不上微服务。理由摆完你自己判断。同时我会把"如果将来要拆,从哪里拆、什么时候拆"一并写清楚——判断现在不需要 ≠ 不具备拆的能力,这两件事的区别就是架构水平。


技术架构

1. 先看约束:五端一体的业务形态

架构的第一输入不是技术偏好,是业务形态。上一篇推导出的系统长这样:

1.1 五个端

端 用户 载体 特点
消费者端 买家 微信小程序 轻量,浏览/下单/售后;高频低复杂度
帮卖工作台 帮卖团长 小程序内嵌 选品、帮卖、加价、分享
供货商工作台 供货团长 小程序 + PC 商品/团购管理、发货、佣金查看
平台运营后台 平台方 PC Web 审核、监控、结算配置、数据看板
H5 微信外分享场景 浏览器 兜底覆盖,复用消费者端接口

两个端选择上的关键决策值得展开:

决策一:帮卖工作台收进消费者小程序,而不是做独立端。
帮卖的动作发生在微信群里——看到货源、转卖、分享,整个链路不应该离开微信。做独立端意味着帮卖团长要在两个 App/小程序间切换,链路断裂,转化率直接掉。端的划分跟着用户的动作场景走,不跟着组织架构走。

决策二:重操作必须给 PC。
批量上架商品、订单批量发货、佣金对账、数据导出——这些操作在手机上做是灾难。供货商和运营后台必须提供 PC Web 版。小程序端保留轻量操作(看数据、处理单笔订单)作为补充。

1.2 一后多前

五端看似很多,但注意:五个端共享同一个业务内核——商品、团购、订单、佣金的数据和规则只有一份。所以架构上是一个后端服务 + 五个前端界面,按端划分接口聚合层(BFF 思路的轻量版),而不是五套系统。


2. 单体 vs 微服务:三个判断维度

正面回答"要不要微服务"。我把决策依据压缩成三个维度,逐个过:

维度一:团队规模与运维能力

微服务的隐性成本清单:服务注册发现、分布式链路追踪、跨服务事务、容器编排、服务网格、独立 CI/CD 流水线 × N……这些每一项都需要人力持续投入。

一个 5 人以内的团队(这是此类项目最常见的配置),光是让微服务基础设施稳定运行就要吃掉 20%~30% 的工程产能。而这些产能花在单体上,就是实打实的业务功能。

结论:5 人以下团队,微服务的基础设施成本收不回来。

维度二:事务一致性

上一篇说过,这个系统的资金安全起点是"下单事务内同时锁定价格快照和佣金快照"。这件事在单体内是一条 @Transactional 的事务边界,数据库本地事务保证原子性:

@Transactional
public Order createOrder(CreateOrderCmd cmd) {
    // 1. 锁定价格快照(帮卖加价 + 供货价)
    PriceSnapshot price = priceService.lockSnapshot(cmd);
    // 2. 锁定佣金快照(供货团长佣金 + 帮卖佣金)
    CommissionSnapshot commission = commissionService.lockSnapshot(cmd, price);
    // 3. 创建订单
    return orderRepository.save(Order.create(cmd, price, commission));
}

同样的逻辑放到微服务里,价格服务、佣金服务、订单服务三个进程,@Transactional 立刻失效,你要引入 Saga、TCC 或消息最终一致性——为了一个本来一行注解就能解决的一致性问题,引入一整套分布式事务框架。资金场景下,分布式事务的每一个异常分支都是资损隐患。

结论:核心交易链路强一致,单体在这个场景是优势而不是局限。

维度三:业务量与增长预期

坦率评估:社群团购单客户部署的量级,日订单 1 万~10 万是常见区间,峰值 QPS 几十到几百。这个量级——一台 4C8G + 合理的缓存设计就能扛。真实的性能瓶颈通常在慢 SQL 和缓存命中率,不在"单体撑不住"。

微服务解决的是"组织扩展性"问题(几十个团队并行开发互不阻塞),不是"性能"问题。一个 5 人团队不存在组织扩展性问题。

三维度结论

维度 判断
团队规模 5 人以内,微服务运维成本收不回
事务一致性 资金强一致场景,本地事务远优于分布式事务
业务量级 峰值 QPS 数百以内,单体 + 缓存绰绰有余

三个维度全部指向同一个答案:模块化单体。 不是妥协,是最优解。


3. 模块化单体怎么做:7 个模块 = 7 个限界上下文

"模块化单体"的关键在"模块化"——单体只是部署形态,代码内部必须按业务边界严格隔离。模块划分直接沿用上一篇的五条业务链路,加上支撑模块:

groupbuy-system/
├── groupbuy-domain          # 领域层:核心业务规则(重点模块)
├── groupbuy-group           # 团购上下文:团购生命周期、状态机
├── groupbuy-product         # 商品上下文:SPU/SKU、三级价格体系
├── groupbuy-order           # 订单上下文:下单、状态流转、履约
├── groupbuy-commission      # 佣金上下文:帮卖关系、佣金计算、快照
├── groupbuy-payment         # 支付上下文:微信支付、分账、回调
├── groupbuy-settlement      # 结算上下文:冻结、结算单、提现、对账
└── groupbuy-infra           # 基础设施:DB、Redis、消息、外部 API 封装

三条模块纪律,比划分本身更重要:

  1. 模块间只准通过接口(application service)调用,禁止跨模块访问对方的数据表—— commission 模块要读订单数据,调 order 模块的接口,而不是自己写 SQL 查 order 表。这条纪律是未来能拆分的前提;
  2. domain 模块不依赖任何其他模块——资金规则(价格快照、佣金计算、冻结期逻辑)只在 domain 层一处实现,被所有模块引用。全系统只允许有一份资金规则的代码,这是结算能对平的根基;
  3. 模块间的依赖方向单向向下——group → order → commission → payment → settlement 的调用链一旦出现环,立即重构。环是腐化的开始。

4. 演进预案:什么时候拆,先拆谁

模块化单体的完整价值在"未来可拆"。把触发条件和拆分顺序提前写清楚:

4.1 三个量化信号(满足任一开始评估拆分)

  1. 单模块独立部署诉求:结算对账任务(CPU 密集批处理)开始影响在线交易的响应时间——这是最可能先出现的信号;
  2. 团队扩张到 10+ 人,模块间代码冲突成为日常;
  3. 单一模块的业务量级与其余部分出现数量级差异(比如开放平台化后,查询流量暴涨而交易平稳)。

4.2 拆分顺序:先 payment 和 settlement

理由有三:

  • 它们与核心交易的耦合点最少——一个支付回调入口、一个结算发起入口,接口边界天然清晰;
  • 它们有独立的伸缩特征——对账批处理和在线交易资源画像完全不同;
  • 它们有独立演化诉求——对接持牌机构、切换支付渠道时,不应该牵动交易主链路。

而 order 和 commission 之间不要拆:下单事务的强一致性依赖(价格快照 + 佣金快照同事务锁定)拆开后要用分布式事务重写,收益为负。这个"不拆"的判断和"要拆"的判断同样重要。

回头看第 3 节的模块图:每个模块的边界就是未来微服务的拆分线,依赖关系图就是未来服务调用图。模块化单体不是微服务的妥协版,是微服务的第一步。


5. 技术栈与分层:把选择讲完整

5.1 技术栈清单及理由

层 选型 一句话理由
小程序/H5 uni-app 一套代码出微信小程序 + H5,帮卖端与消费者端同仓开发
后端框架 Spring Boot 3.x(Java 17) 生态最成熟,支付/微信 SDK 完备,招人容易
ORM MyBatis-Plus 团队熟悉度 + SQL 可控性(资金 SQL 必须手写可审)
数据库 MySQL 8(一主一从) 量级以内单库足够,读写分离保查询
缓存 Redis 7 防超卖 Lua 预扣、幂等 token、缓存、排行榜四合一
任务调度 XXL-Job 截单、冻结期扫描、结算批处理;可视化管理 + 分片
消息 先用 Redis Stream,量大再上 RocketMQ 订单超时关单、异步通知;不为几百 QPS 上 MQ 集群
部署 Docker Compose 单机起步 对应第 2 节的量级判断,K8s 等有真实多机需求再上

选型总原则一句话:交付型系统选生态最成熟的,不选最新的。 每一个"新"都要付学习成本和踩坑成本,而这类项目的预算通常不支持。

5.2 分层规矩

经典四层(controller / service / domain / infrastructure),只强调三条铁律:

  1. 资金规则只在 domain 层实现,service 层只做编排,controller 层零业务逻辑——全系统资金规则只允许有一份代码;
  2. 金额计算禁用 double/float,统一 long(分为单位)或 BigDecimal,并写进 Checkstyle 规则强制;
  3. 所有外部调用(支付/短信/内容安全)必须在 infra 层封装成防腐层,微信 API 改版时只改一处。

6. 两个最常见的反模式:模块化单体的"死法"

模块化单体做失败的项目,几乎都不是架构选型错了,而是纪律失守。列两个我在实际代码评审里最常见到的反模式,如果你们团队正在做单体,可以对照自查:

反模式一:Maven 多模块,逻辑上却是"一锅粥"

典型症状:Maven 划了七个模块,但 commission 模块里直接写着 SELECT * FROM t_order WHERE ...——跨模块查表;order 模块 import 了 commission 的内部类;改一个佣金规则,订单模块跟着编译报错。

这种项目的模块划分只是代码目录,不是业务边界。检验方法很简单:把任意一个模块单独抽出去编译,能不能通过? 抽不出去的模块,就没有为未来拆分做过任何准备。对应的解法就是第 3 节的三条纪律,其中"跨模块只走接口"要靠 ArchUnit 这类架构测试工具固化成 CI 卡点,靠口头约定守不住:

// ArchUnit:禁止 commission 模块直接访问 order 模块的内部实现
@ArchTest
static final ArchRule modulesMustOnlyDependOnInterfaces =
    noClasses().that().resideInAPackage("..commission..")
        .should().dependOnClassesThat()
        .resideInAPackage("..order.domain..");

反模式二: premature optimization 的镜像版——premature decomposition

另一个极端是业务还没跑通,先把"未来可能要拆"的服务按微服务的方式建了一堆 API 网关、消息总线、分布式配置中心。结果需求一变,跨五个服务的接口联调改一轮,两周的活干成两个月。

判断标准还是那三个维度(团队规模/事务一致性/业务量级)。基础设施的复杂度要匹配当前业务的真实需要,预留的应该是"边界清晰的模块",而不是"提前建好的服务"。边界是设计出来的,服务是长出来的——先有前者,后者才有意义。


7. 接口聚合:五端怎么共用一个后端

五端共享一个业务内核,但每个端需要的接口形态不一样:小程序要"小而快"(一次请求带回团购详情+我的帮卖状态+佣金预估),PC 后台要"复杂查询"(多条件分页、导出)。如果让前端各自拼装底层接口,小程序端会出现 N+1 请求问题;如果底层接口为大端定制,又会互相污染。

我的做法是引入一层轻量的端聚合层(facade),注意这不是微服务架构里的 BFF 独立进程,只是同一个应用内的接口分层:

controller/
├── mini/        # 消费者小程序聚合接口(每屏一接口,响应做字段裁剪)
├── leader/      # 团长端聚合接口(帮卖/供货共用,按角色鉴权裁剪数据)
├── admin/       # 运营 PC 接口(复杂查询、批量操作)
└── open/        # H5 与未来开放平台(独立鉴权体系)

三条聚合层纪律:

  1. facade 只做编排和裁剪,零业务规则——出现 if/else 判断资金逻辑,就是分层失守的信号,立即下沉到 domain;
  2. 每个端独立鉴权与限流配置——小程序端用户量大但请求轻,运营端用户极少但请求重,限流阈值和 token 有效期分开配置;
  3. 接口入参出参按端隔离——禁止两个端共用同一个 DTO。共用 DTO 是"改一个小程序字段、PC 端莫名报错"这类事故的根源。

这层facade未来如果要拆(比如小程序流量独立扩容),每个 facade 包就是一个现成的 BFF 服务——和第 4 节的模块演进预案是同一套思路:边界先设计好,拆分只是部署形态的变化。


8. 本篇小结

  1. 五个端一个内核——端的划分跟用户动作场景走:帮卖收进小程序,重操作给 PC;
  2. 模块化单体是这个场景的最优解,不是妥协——团队规模、事务一致性、业务量级三个维度全部指向它;
  3. 模块纪律先于模块划分——接口调用、domain 不被依赖、依赖无环,三条纪律守住,单体就永远是"可以随时拆的单体";
  4. 演进预案提前写——三个量化信号触发评估,先拆 payment/settlement,order/commission 强一致绑定不拆。

架构讲完,从下一篇开始进入编码实战:《数据库设计(上):团购、商品、订单三张主表的演进过程》——你会看到业务规则是如何一步步"长"成表结构的。


(本系列所有文章为独立技术实践记录,代码均经过本地运行验证。转载请注明出处。)

posted @ 2026-09-07 14:35  15889726201  阅读(12)  评论(0)    收藏  举报