Nest / 基础设施 · 第 2 篇
上一篇:同专栏《单体 NestJS 用 Redis Stream 当事件总线》· 请求链路地图见自文档《多端架构与请求链路》
写在前面
自文档 01 讲的是 请求从浏览器走到哪台进程;本文讲 Nest 进程 内部怎么长:两百多个 API 不会平铺在一个 controllers/ 里,也不会为每个 Service 开一个 Nest Module。
本站做法可以概括成三句话:
AppModule很瘦:全局配置 + Guard +AllAppModules- 业务按「能力边界」挂模块:core / security / features / pay / rpg / rag
- 大域内部再按文件夹拆 Service(如 RPG 的 lottery/quest),不必再套一层 Nest Module
一、顶层:registry 比「全写进 AppModule」更清晰
typescript
// modules/registry.ts
export const CoreModules = [RedisModule, BlogEventsModule, RealtimeModule];
export const SecurityModules = [AuthModule, CaptchaModule];
export const FeatureModules = [FeaturesModule.register()];
export const AllAppModules = [
...CoreModules,
...SecurityModules,
...FeatureModules,
PayModule,
RpgModule,
RagModule,
];
AppModule 只负责:ConfigModule、TypeOrmModule.forRoot、ScheduleModule、限流与权限两个 APP_GUARD,然后展开 AllAppModules。
分层含义:
| 层 | 例子 | 角色 |
|---|---|---|
| Core | Redis、BlogEvents、Realtime | 横切基础设施,被多处 import |
| Security | Auth、Captcha | 登录与人机验证 |
| Features | 文章/评论/用户/Admin… | 博客「内容与运营」主业 |
| 垂直能力 | Pay、Rpg、Rag | 可整块关掉或未来拆进程的边界 |
新增垂直能力时:写独立 XxxModule → 挂进 AllAppModules,而不是继续往 Features 里塞。
二、Features:一个动态聚合,内部仍是多 Module
typescript
FeaturesModule.register() // 默认启用全部子模块
内部是 ArticleModule、CommentModule、UserModule… 各自 TypeOrmModule.forFeature + Controller。
聚合的好处:AppModule 一行引入;测试或裁剪部署时可 register([ArticleModule, …]) 子集(预留口子)。
跨域依赖示例:ArticleModule import RpgModule,以便发文侧拿到打赏/文章等级等导出能力——内容域依赖玩法域的 稳定出口,而不是反向去改 RPG 内部私有 Service。
三、RpgModule:一个 Nest 边界,文件夹才是领域
目录(示意):
text
rpg/
├── core/ RpgService、Notify、Punishment、Controller
├── level/ 经验、签到、文章等级
├── quest/ lottery/ achievement/ buff/ pet/ …
├── social/ 打赏、加油砸蛋、声望
├── listeners/ RpgEventConsumer(订阅 blog:events)
├── admin/ 运营后台
├── entity/ 17+ 表
└── rpg.module.ts 唯一 Nest @Module
为什么不拆成 LotteryModule + QuestModule?
- 子域之间调用极密(抽奖 → 任务进度 → 成就 → 加经验),拆成十几个 Nest Module 会爆炸式
imports/exports - 对外只需要一组 HTTP 前缀与一组
exports(给 Article/Comment/Pay 用) - 目录已经表达领域;Nest Module 表达 部署与依赖边界
exports 刻意收紧,例如:BanGuard、PunishmentService、TipService、InventoryService、RechargeService、RpgService…——外部只能依赖这些「门面」,进不了 LotteryService 私有细节。
启动时 onModuleInit 统一 upsert 预置成就/任务/奖池/装扮——种子同步属于模块生命周期,而不是散落在 main.ts。
四、循环依赖:同 Module 内用 forwardRef,跨 Module 靠事件
RPG 内部典型环:
LevelService↔AchievementService(升级发成就,成就再加经验)QuestService↔LotteryService(领奖发券,抽奖推进任务)
处理方式:
typescript
@Inject(forwardRef(() => AchievementService))
private readonly achievementService?: AchievementService,
同属一个 RpgModule 时,forwardRef 足够;不必为拆环再拆 Module。
跨大域(文章 ↔ RPG ↔ RAG)优先:
- 同步调用:只走对方
exports的窄接口 - 异步解耦:
BlogEventPublisher→ Stream → 各自 consumer(见 S1)
评论加经验不在 CommentService 里直接 new LevelService,而是发 blog.comment.created——评论模块甚至可以不 import 大半个 RPG。
Pay ↔ RPG:充值意向在 RPG,履约在 Pay,通过模块 import + 回调/履约钩子衔接,避免支付域了解抽卡细节。
五、多端复用同一套 /api/v1
组织模块的目标之一是:Nuxt / Admin / uni-app(或 Go 对等实现)共用契约。
| 客户端 | 看到的 |
|---|---|
| blog-home-nuxt | 同一前缀下的 article / rpg / rag… |
| blog-admin | 同前缀 + RBAC 菜单;RPG Admin Controller 挂在同一 RpgModule |
| uni-app | 可走 Nest 或 Go;Go 侧用「领域 Module 结构体」镜像 Nest 边界 |
Admin 与 C 端 Controller 可以同 Module、不同路由与 Guard——按用户角色拆 Controller,不按「后台项目」拆后端仓库。这比再起一个 blog-admin-server 省一整条部署链。
全局 PermissionGuard + ThrottlerGuard 挂在 AppModule:横切规则不渗进每个 Feature Module 的 providers 列表。
踩坑与取舍
-
「一个文件夹 = 一个 Nest Module」教条
RPG 证明:文件夹拆域、Module 管边界,更适合高内聚玩法。 -
exports 过大等于没封装
若把LotteryService也 export,文章域迟早直接改奖池。收紧出口。 -
forwardRef 滥用是味道
同 Module 内偶发环可接受;若三个 Module 互相 forwardRef,该引入事件或门面接口了。 -
entity 扫描
__dirname + '/**/*.entity'
新实体放对目录即可被 TypeORM 加载;不要复制一份到dist外路径。 -
Go 对等不是抄 Nest 装饰器
Go 用显式NewModule装配;边界对齐(rpg/rag/blog),实现语言不同。组织原则可迁移,语法不必。
小结
| 层级 | 做法 |
|---|---|
| App | 瘦:配置 + 全局 Guard + registry |
| 能力 | Core / Security / Features / Pay / Rpg / Rag |
| 大域内部 | 目录分 Service;单一 Nest @Module + 窄 exports |
| 解耦 | 同步走 exports;异步走 blog:events |
| 多端 | 同一 /api/v1,Admin/C 端 Controller 共存 |
和 01 文分工:01=外面怎么连进来;本文=进来之后代码怎么摆。和 S1 文衔接:模块切不开的调用,丢给 Stream。
关键路径
blog-server/src/app.module.tsblog-server/src/modules/registry.tsblog-server/src/modules/features/features.module.tsblog-server/src/modules/rpg/rpg.module.tsblog-server/src/modules/rag/rag.module.tsblog-server/src/modules/features/article/article.module.ts

全部评论(0)