From 68e052543a7434978002072f79b9500f1699f72c Mon Sep 17 00:00:00 2001 From: Jaiyashree Subramanian Date: Thu, 29 Nov 2018 19:36:28 +0530 Subject: [PATCH] Added hooks to support mongo-db transaction --- README.md | 65 ++++++++++++++++++++++++++ lib/index.js | 2 + lib/transaction-manager.js | 93 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 160 insertions(+) create mode 100644 lib/transaction-manager.js diff --git a/README.md b/README.md index 36c358e4..7c1ba518 100644 --- a/README.md +++ b/README.md @@ -265,6 +265,71 @@ students.find({ query, collation }).then( ... ); For more information on MongoDB's collation feature, visit the [collation reference page](https://docs.mongodb.com/manual/reference/collation/). +## Mongo-DB Transaction + +This adapter includes support to enable database transaction to rollback the persisted records for any error occured for a api call. This requires [Mongo-DB v4.x](https://docs.mongodb.com/manual/) installed. + +Start working with transactin enabled by adding the following lines in `app.hooks.js`. + +```js +const TransactionManager = require('feathers-mongoose').TransactionManager; +const isTransactionEnable = process.env.TRANSACTION_ENABLE || false; +const skipPath = ['login']; + +let moduleExports = { + before: { + // ! code: before + all: [], + find: [], + get: [], + create: [ + when(isTransactionEnable, async hook => + TransactionManager.beginTransaction(hook, skipPath) + ) + ], + update: [ + when(isTransactionEnable, async hook => + TransactionManager.beginTransaction(hook, skipPath) + ) + ], + patch: [], + remove: [] + // !end + }, + + after: { + // ! code: after + all: [log()], + find: [], + get: [], + create: [when(isTransactionEnable, TransactionManager.commitTransaction)], + update: [when(isTransactionEnable, TransactionManager.commitTransaction)], + patch: [], + remove: [afterAudit] + // !end + }, + + error: { + // ! code: error + all: [log()], + find: [], + get: [], + create: [when(isTransactionEnable, TransactionManager.rollbackTransaction)], + update: [when(isTransactionEnable, TransactionManager.rollbackTransaction)], + patch: [], + remove: [] + // !end + } + // !code: moduleExports // !end +}; + +// !code: exports // !end +module.exports = moduleExports; + +// !code: funcs // !end +// !code: end // !end +``` + ## License [MIT](LICENSE) diff --git a/lib/index.js b/lib/index.js index 4ab5b014..06066ce8 100644 --- a/lib/index.js +++ b/lib/index.js @@ -1,6 +1,8 @@ const hooks = require('./hooks'); const service = require('./service'); +const transactionManager = require('./transaction-manager'); Object.assign(service, { hooks, service }); module.exports = service; +module.exports.TransactionManager = transactionManager; diff --git a/lib/transaction-manager.js b/lib/transaction-manager.js new file mode 100644 index 00000000..8bc82d89 --- /dev/null +++ b/lib/transaction-manager.js @@ -0,0 +1,93 @@ +const errors = require('@feathersjs/errors'); +const mongoose = require('mongoose'); + +/** + * To start a new session and initiate transaction on the session and set + * mongoose-session in context-params to all consecutive service call if the + * boolean + * + * @param {object} context context and all params + * @param {[string]} skipPath list of paths to exclude from transaction + * - Example: ['login'] + * @return {object} context context with db-session appended + */ +const beginTransaction = async (context, skipPath) => { + try { + // if the current path is not added to skipPath-list + if (skipPath.indexOf(context.path) === -1) { + // if there is no open-transaction appended already + if (context.params && !context.params.transactionOpen) { + const session = await mongoose.startSession(); + session.startTransaction(); + context.params.transactionOpen = true; + context.params.mongoose = { session }; + } + context.enableTransaction = true; // true if transaction is enabled + } else { + context.enableTransaction = false; + } + return context; + } catch (err) { + throw new errors.MongoError(`Error while starting session`); + } +}; + +/** + * To commit a mongo-transaction after save methods of mongo service + * + * @param {object} context context with params, result and DB-session + * @return {object} context context with params, result and DB-session + */ +const commitTransaction = async context => { + try { + // if transaction is enabled during startSession + if (context.enableTransaction) + // if context contains the mongoose session to be committed + if ( + context.params && + context.params.mongoose && + context.params.mongoose.session + ) { + await context.params.mongoose.session.commitTransaction(); + context.params.mongoose = null; + context.params.transactionOpen = false; // reset transaction-open + context.enableTransaction = false; + } + return context; + } catch (err) { + throw new errors.MongoError(`Error while commiting transaction`); + } +}; + +/** + * To rollback a mongo-transaction for any error thrown in service calls + * + * @param {object} context context with params and DB-session + * @return {object} context context with params and DB-session + */ +const rollbackTransaction = async context => { + try { + // if transaction is enabled during startSession + if (context.enableTransaction) + // if context contains the mongoose session to be committed + if ( + context.params && + context.params.mongoose && + context.params.mongoose.session + ) { + await context.params.mongoose.session.abortTransaction(); + context.params.mongoose = null; + context.transactionOpen = false; // reset transaction-open + context.enableTransaction = false; + } + return context; + } catch (err) { + throw new errors.MongoError(`Error while rolling-back transaction`); + } +}; + +module.exports = { + beginTransaction, + commitTransaction, + rollbackTransaction +};