NestJS 单体应用跨服务共享事务:nestjs-cls @Transactional 装饰器零侵入实践

📅 发布时间:2026/8/24 9:14:12
NestJS 单体应用跨服务共享事务:nestjs-cls @Transactional 装饰器零侵入实践 NestJS 单体应用跨服务共享事务nestjs-cls Transactional 装饰器零侵入实践【免费下载链接】nestjs-clsA continuation-local storage (async context) module compatible with NestJSs dependency injection.项目地址: https://gitcode.com/gh_mirrors/ne/nestjs-clsnestjs-cls 是一款兼容 NestJS 依赖注入的继续本地存储CLSAsync Context模块其官方的nestjs-cls/transactional插件能让数据库事务跨服务无缝共享用一个Transactional装饰器开启事务其他 Service 无需任何参数传递即可自动加入同一个事务实现零侵入的事务管理。本文将从安装、注册到传播模式带你完整走一遍这套 NestJS 事务共享方案。痛点手动传递事务引用有多难受在 NestJS 单体应用中一个业务动作往往横跨多个 Service创建用户 → 同时创建账户 → 同时写入审计日志这些写操作必须要么全部成功要么全部回滚传统做法是把事务对象如 Prisma 的$transaction、Knex 的trx作为参数一层层往下传OrderService.create() └─ PaymentService.charge(tx) └─ InventoryService.deduct(tx) └─ AuditService.log(tx)调用链每加一层方法签名就多一个tx参数——封装被破坏、测试困难、代码冗长。这正是 nestjs-cls 要解决的问题把事务引用存进 CLS 上下文任何同请求链路内的代码都能直接取到参数列表彻底解放。它如何工作CLS 上下文原理一句话版nestjs-cls 基于 Node.js 的AsyncLocalStorage在整个请求生命周期内提供一块公共黑板请求进入时初始化上下文中间件/拦截器完成事务插件开启事务后把事务引用写入上下文任何 Service 里的TransactionHost.tx读取到的都是同一个事务回调正常结束 → 提交抛出异常 → 回滚核心逻辑位于 transaction-host.ts 的TransactionHost类装饰器实现见 transactional.decorator.ts。快速上手3 步完成安装与注册第 1 步安装事务插件 对应数据库适配器插件与 ORM 解耦通过适配器支持主流数据库库适配器适用库PrismaPrismaTypeORMTypeORMKnex / KyselyKnex、KyselyDrizzle ORMDrizzle ORMPg-promisepg-promiseMongoDB / MongooseMongoDB、Mongoose第 2 步在ClsModule.forRoot的 plugins 中注册ClsModule.forRoot({ plugins: [ new ClsPluginTransactional({ imports: [PrismaModule], adapter: new TransactionalAdapterPrisma({ prismaInjectionToken: PrismaClient, }), }), ], }),注册后会得到一个全局可用的TransactionHostProvider负责开启事务与读写事务引用。第 3 步给方法打上Transactional装饰器Injectable() class UserService { constructor(private readonly txHost: TransactionHostTransactionalAdapterPrisma) {} Transactional() async createUser(name: string) { const user await this.txHost.tx.user.create({ data: { name } }); await this.accountService.createAccountForUser(user.id); // 自动加入同一事务 return user; } }AccountService内部完全不用知道自己处在事务里只需使用this.txHost.tx执行查询——事务共享是自动的这就是零侵入的关键。事务如何在服务间自动共享关键就在TransactionHost的两个成员见 transaction-host.tstx当前活跃事务的引用无事务时回退到普通非事务客户端业务代码永远有合法对象可用withTransaction(callback)无法用装饰器场景如普通函数的手动开启方式回调成功即提交、抛错即回滚Transactional本质上就是对withTransaction的语法糖封装它用Proxy包裹被装饰方法调用时自动执行TransactionHost.withTransaction(...)并支持传入传播模式与隔离级别等选项。事务传播模式嵌套调用的行为控制当一个Transactional方法调用了另一个Transactional方法如何决策加入还是新开Propagation枚举propagation.ts提供了与 Spring 一致的 7 种模式模式行为Required默认有事务就复用没有就新建RequiresNew无论是否有事务都新开一个独立提交Mandatory必须复用已有事务否则抛异常Never必须在无事务下运行有事务则抛异常Supports有就复用没有就裸奔运行NotSupported强制脱离事务运行Nested创建子事务需适配器支持否则回退为Required// 日志服务强制独立提交即使外层订单事务回滚日志也保留 Transactional(Propagation.RequiresNew) async logOrder(...) { ... } // 校验方法强制要求在外层事务中运行 Transactional(Propagation.Mandatory, { isolationLevel: Serializable }) async validate(...) { ... }多数据源场景命名连接项目同时使用多个数据库甚至多个 ORM时注册多个ClsPluginTransactional并各给一个connectionName即可注入与装饰器同步指定连接名Injectable() class UserService { constructor( InjectTransactionHost(prisma-connection) private readonly txHost: TransactionHostTransactionalAdapterPrisma, ) {} Transactional(prisma-connection) async createUser(...) { ... } }单元测试友好不依赖真实数据库装饰器式事务对单测的友好度常被低估Mock 装饰器用jest.mock把Transactional替换为 no-op方法退化为普通方法No-op 适配器NoOpTransactionalAdapter不真正开启事务但完整保留tx的传播链路可直接塞入 mock 客户端完整可运行示例就在 packages/transactional/test/ 目录下官方插件文档见 docs/docs/06_plugins/01_available-plugins/01-transactional/index.md。总结何时选择这套方案场景推荐度单体应用多 Service 协作写库⭐⭐⭐⭐⭐ 完美契合多个 ORM / 多数据源混合⭐⭐⭐⭐⭐ 命名连接轻松覆盖已有手动传参、想渐进迁移⭐⭐⭐⭐InjectTransaction支持平滑过渡微服务跨进程事务⭐ 不适用CLS 是进程内上下文一句话回顾nestjs-cls 的 Transactional 插件用 CLS 上下文承载事务引用Transactional装饰器声明式开启事务服务间共享零参数、零侵入——把传事务从业务代码里彻底抹掉让 NestJS 团队可以把精力真正花在业务逻辑上。【免费下载链接】nestjs-clsA continuation-local storage (async context) module compatible with NestJSs dependency injection.项目地址: https://gitcode.com/gh_mirrors/ne/nestjs-cls创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考