NestJs中跨服务实现Mongo共享事务的方案咨询
优化MongoDB跨服务事务的方案
方案一:让事务方法接受可选的外部Session参数
不用拆分出两个版本的方法,直接给原事务方法添加一个可选的externalSession参数,内部根据是否传入该参数来决定事务的生命周期管理逻辑:
async function tranA(externalSession) { // 确定使用的Session:外部传入则复用,否则自行创建 const session = externalSession || await this.connection.startSession(); const isOwnSession = !externalSession; try { // 仅自行创建Session时才启动事务(外部Session的事务由调用方控制) if (isOwnSession) { session.startTransaction(); } // 执行核心业务逻辑,统一传入Session await this.executeA(session); // 仅自行创建Session时才提交事务 if (isOwnSession) { await session.commitTransaction(); } } catch (err) { // 仅自行创建Session时才回滚事务 if (isOwnSession) { await session.abortTransaction(); } // 抛出错误让上层处理(外部Session场景下,由调用方负责回滚) throw err; } finally { // 仅自行创建Session时才结束Session if (isOwnSession) { await session.endSession(); } } } async function executeA(session) { // 核心查询逻辑,必须传入Session await this.collection.findOne({ /* 查询条件 */ }, { session }); }
优势
- 无需维护两套方法,同一个
tranA既可以单独调用(自动处理事务生命周期),也能被控制器传入外部Session参与全局事务 - 逻辑清晰,调用方可以明确控制事务范围
方案二:使用AsyncLocalStorage实现事务上下文传递
通过Node.js的AsyncLocalStorage(异步本地存储)实现Session的隐式传递,业务方法无需显式接收Session参数,简化代码:
第一步:实现事务上下文管理类
const { AsyncLocalStorage } = require('async_hooks'); class TransactionContext { constructor() { this.als = new AsyncLocalStorage(); } // 启动事务并执行回调,回调内所有方法可获取当前Session async runInTransaction(connection, callback) { const session = await connection.startSession(); try { session.startTransaction(); // 将Session存入异步上下文 await this.als.run(session, callback); await session.commitTransaction(); } catch (err) { await session.abortTransaction(); throw err; } finally { await session.endSession(); } } // 获取当前上下文的Session getCurrentSession() { return this.als.getStore(); } } // 全局实例,供所有服务复用 const transactionContext = new TransactionContext(); module.exports = transactionContext;
第二步:改造服务方法
const transactionContext = require('./transaction-context'); async function tranA() { const session = transactionContext.getCurrentSession(); // 若存在上下文Session,直接执行业务逻辑;否则启动独立事务 if (session) { await this.executeA(); } else { await transactionContext.runInTransaction(this.connection, () => this.executeA()); } } async function executeA() { // 从上下文自动获取Session,无需显式传递 const session = transactionContext.getCurrentSession(); await this.collection.findOne({ /* 查询条件 */ }, { session }); }
第三步:控制器中调用全局事务
async function controllerAction() { // 在同一个事务上下文内调用两个服务方法 await transactionContext.runInTransaction(connection, async () => { await serviceA.tranA(); await serviceB.tranB(); }); }
优势
- 业务方法无需传递Session参数,代码更简洁
- 适合大型项目,减少参数传递的冗余,降低维护成本
总结
- 小型项目或需要明确控制事务逻辑的场景,优先选择方案一,实现成本低、逻辑直观
- 大型项目或追求代码简洁性的场景,可选择方案二,通过上下文隐式传递Session
内容的提问来源于stack exchange,提问作者Minh Nghĩa
相关产品推荐
相关产品推荐

