For AI agents: the complete documentation index is available at https://liangy0323.github.io/ly-fullstack/llms.txt, the full documentation bundle is available at https://liangy0323.github.io/ly-fullstack/llms-full.txt, and this page is available as Markdown at https://liangy0323.github.io/ly-fullstack/architecture/modular-monolith.md.

为什么是模块化单体

“模块化单体”不是把所有代码写在一个大目录,也不是未来无法拆分的临时方案。它表示:一个应用在运行和部署上保持单体,但内部按照稳定业务领域划分模块,并通过明确依赖控制模块之间的耦合。

四个容易混淆的概念

Monorepo

一个 Git 仓库同时管理多个应用和共享包。它解决版本、依赖、构建和协作问题,不决定运行时是否单体。

多应用

admin-apiapi 是两个独立进程,可以分别部署。它们的认证对象和暴露接口不同,因此应用边界是合理的。

模块化单体

每个 NestJS 应用内部按 authuserrolemenudictionary 等领域组织模块。模块通过显式 import 和服务接口协作,共享一个进程和主要数据库事务边界。

微服务

业务能力通过网络拆成自治服务,通常还需要网关、服务发现、配置管理、容错、幂等、消息、可观测性和独立发布治理。只有多个独立进程,并不代表这些问题已经得到解决。

为什么默认选择模块化单体

对个人开发者和小型团队,最稀缺的通常不是“能拆多少服务”,而是能否稳定完成:

  • 数据模型与业务规则;
  • 前后端契约;
  • 登录认证与权限;
  • 自动测试与部署;
  • 线上故障定位和数据恢复。

模块化单体保留清晰代码边界,同时避免过早承担网络调用、分布式一致性和复杂运维成本。它并不是降低工程要求,反而要求模块职责、依赖方向和数据库变更更加明确。

什么时候继续放在 apps/api

满足大部分以下条件时,优先在同一应用增加领域模块:

  • 与现有业务一起发布没有明显风险;
  • 使用同一数据库事务更简单正确;
  • 团队规模较小,领域由同一组人维护;
  • 性能瓶颈可以通过索引、缓存、队列或多实例解决;
  • 故障不会要求独立熔断和隔离。

什么时候创建额外服务

使用 pnpm new:server 的理由应来自真实约束:

  • 需要独立发布节奏;
  • 负载模型差异显著,需要独立扩缩容;
  • 故障必须与核心 API 隔离;
  • 数据或合规边界要求独立运行;
  • 团队已经形成稳定自治边界。

创建服务只完成了代码和运行骨架,不会自动生成微服务治理能力。跨服务通信、认证传播、幂等、超时、重试、观测和数据一致性仍需单独设计。

推荐的演进顺序

  1. 先把领域拆成清晰 NestJS 模块。
  2. 用真实指标定位数据库、CPU、外部调用或任务处理瓶颈。
  3. 优先补索引、缓存、队列、限流、监控和多实例部署。
  4. 明确独立边界的收益高于网络与运维成本后再拆服务。
  5. 拆分时同时设计数据所有权、调用失败和发布回滚,而不是只移动目录。

因此,LY Fullstack 提供的是可持续演进的起点,不宣称一套默认结构可以覆盖所有规模。