Easy transaction management for sequelize
The sequelize
npm package provides an ORM tool to communicate to SQL databases.
In this post I would like to introduce you to zb-sequelize which strongly simplifies the transaction management.
The Problem
Let’s say we have a fooBar()
function which calls a foo()
and a bar()
function. Both of them are executed within the same transaction.
function fooBar() {
try {
const transaction = sequelize.transaction(); foo(transaction);
bar(transaction); transaction.commit();
} catch(err) {
transaction.rollback();
throw err;
}
}
The transaction management takes about 7 lines of code in this example.
Now, what would happen if fooBar()
would become part of a bigger transaction. Let’s call it start()
.
The additional start()
function would look as follows.
function start() {
try {
const transaction = sequelize.transaction(); fooBar(transaction); transaction.commit();
} catch(err) {
transaction.rollback();
throw err;
}
}
But you would also need to change the fooBar()
method to accept a transaction
parameter.
Assuming that the fooBar()
can be called both with or without transaction, things get more tricky.
Alternatively, you could squeeze it in 1 method. It would look as follows. Notice how many if (isLocalTransaction)
checks this requires.
function fooBar(transaction?) {
try {
const isLocalTransaction = transaction == null;
if (isLocalTransaction) transaction = sequelize.transaction(); foo(transaction);
bar(transaction); if (isLocalTransaction) transaction.commit();
} catch(err) {
if (isLocalTransaction) transaction.rollback();
throw err;
}
}
You would perhaps prefer creating 2 methods. One with a transaction parameter, and one without.
The final picture would look as follows.
function start() {
try {
const transaction = sequelize.transaction(); fooBar(transaction); transaction.commit();
} catch(err) {
transaction.rollback();
throw err;
}
}function fooBar() {
try {
const transaction = sequelize.transaction(); fooBarTx(transaction); transaction.commit();
} catch(err) {
transaction.rollback();
throw err;
}
}function fooBarTx(transaction) {
foo(transaction);
bar(transaction);
}
You do need some kind of convention to separate or distinguish transactional functions from non-transactional functions.
- You could put the non-transactional methods in a separate class or module.
- In the example above I used a naming convention to add a
Tx
extension to the name of the function.
The problem is that all of the above is so verbose. It requires so much repetitive code.
The Solution
I want to show you that there actually is a shorter alternative for this, using 2 decorators.
@Transactional
function fooBar(@Tx transaction) {
foo(transaction);
bar(transaction);
}
Oh, and the start()
method would look as follows
@Transactional
function start(@Tx transaction) {
fooBar(transaction);
}
The transaction parameter becomes optional, and will be initialized automatically whenever necessary. It basically allows all methods to be executed individually or as part of a bigger transaction. And you never have to write code to initialize, commit or rollback transactions again.
This requires a dependency:
npm install zb-sequelize
And of course, you have to help zb-sequelize
a bit, by showing it where to find your sequelize
instance.
import { initSequelizeResolver } from 'zb-sequelize';
initSequelizeResolver((args) => sequelize)
Conclusion
So, how did this solution help us ?
For this simple example, it saved us roughly 20 lines of repetitive code.
Have fun ! :-)