Ron Ron - 1 year ago 284
Node.js Question

Easy way to handle nested transactions

Suppose there is an "addUser" function, inside we need to insert a record to "Account" table and "User" table, so the two steps have to be within a transaction too, so we will write the following code:

function addUser (userName, password) {
sequelize.transaction(function () {
return AccountModel.create(...)

However, in another "addTeam" function, inside we need to insert a record to "Team" table and create a admin user using the above function. The function also need to be wrapped inside a transaction.

So the problem comes, the "addUser" function sometimes need to begin a new transaction, and sometimes need to use the transaction passed in. The most obvious ways is below:

function addUser (userName, password, transaction) {
let func = function (t) {
return AccountModel.create(..., t)
.then(()=>UserModel.create(..., t)));
if (transaction) func(t);
else sequelize.transaction(x=>func(t));

function addTeam() {
sequelize.transaction(x=> {
TeamModel.create(..., x)

Obviously, it is awful. How to deal with it easily, which let transaction totally transparent to the caller like below:

async function addUser(userName, password) {
await AccountModel.create(...);
await UserModel.create(...);

async function addTeam(...) {
await TeamModel.create(...);
await addUser(...);

Ron Ron
Answer Source

I solved the problem, and feel great.

I used the CLS feature sequelize provides, like the code below:

let namespace = Sequelize.cls = cls.createNamespace('myschool');
export const db = new Sequelize(config.db.url);

export const trans = option => operation => async function () {
    let t = namespace.get('transaction');
    let hasTrans = !!t;
    t = t  || await db.transaction();
    try {
        let result = await operation.apply(null, arguments);
        if (!hasTrans) await t.commit();
        return result;
    catch (e) {
        if (!hasTrans) await t.rollback();
        throw e;

The above code just create a transaction and commit it if there is no transaction in the local context, otherwise just leave it.

And each business function want a transaction just need to use the above high order function to wrap like below:

export const createSchool = trans()( async (name, accountProps) => {
    let school = await SchoolModel.create({name});
    let teacher = await createTeacher({...accountProps, schoolId: school.get('id')});
    return {school, teacher};