feathersjs-ecosystem / feathers-knex

Service adapters for KnexJS a query builder for PostgreSQL, MySQL, MariaDB, Oracle and SQLite3

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

transaction rollback not executed

mohanagy opened this issue · comments

I'm using Knex transactions in hooks and I followed the documentation which says

 module.exports = {
   before: {
    all: [ transaction.start() ],
    find: [],
    get: [],
    create: [],
    update: [],
    patch: [],
    remove: []
  },
  after: {
    all: [ transaction.end() ],
    find: [],
    get: [],
    create: [],
    update: [],
    patch: [],
    remove: []
  },
  error: {
    all: [ transaction.rollback() ],
    find: [],
    get: [],
    create: [],
    update: [],
    patch: [],
    remove: []
  }
};

I'm using multi services and I call services through another service hook
the problem is if service failds ,for example there was duplicated row
it shouden't continue and should rollaback
but this not happen
this is my code :
registration.service.js

// Initializes the `users` service on path `/registeration`
const createService = require('feathers-knex');
const createModel = require('../../../models');
const hooks = require('./registration.hooks');

module.exports = function (app) {
  const Model = createModel(app);
  const cognitoPool = app.get('cognitoPool');

  const options = {
    name: 'Accounts',
    Model,
    paginate:false,
    cognitoPool
  };

  // Initialize our service with any options it requires
  app.use('/registration', createService(options));

  // Get our initialized service so that we can register hooks and filters
  const service = app.service('registration');

  service.hooks(hooks(options));
};

registration.hooks.js

const validate = require('feathers-hooks-validate-joi');
const { registration } = require('../../../validations');
const { hooks } = require('feathers-knex');
const { transaction } = hooks;
const { registration: { beforeRegistration, afterRegistration } } = require('../../../hooks/auth');

module.exports = (options) => ({
  before: {
    all: [transaction.start()],
    find: [],
    get: [],
    create: [
      validate.form(registration.registrationSchema, registration.joiOptions),
      beforeRegistration()
    ],
    update: [],
    patch: [],
    remove: []
  },

  after: {
    all: [transaction.end()],
    find: [],
    get: [],
    create: [afterRegistration(options),]
    ,
    update: [],
    patch: [],
    remove: []
  },

  error: {
    all: [transaction.rollback()],
    find: [],
    get: [],
    create: [],
    update: [],
    patch: [],
    remove: []
  }
});

beforeRegisration.js

const errors = require('@feathersjs/errors');
// Use this hook to manipulate incoming or outgoing data.
// For more information on hooks see: http://docs.feathersjs.com/api/hooks.html

// eslint-disable-next-line no-unused-vars
module.exports = function (options = {}) {
    return async context => {
        try {
            const { data: { company_name, account_type, email, first_name, last_name, phone_number, password } } = context
            const userData = {
                first_name,
                last_name,
                email,
                phone_number,
                password
            }
            const newContext = {
                ...context,
                data: {
                    company_name,
                    account_type,
                    created_by: 1,
                    updated_by: 1
                },
                userData
            }
            return newContext
        } catch (e) {
            throw errors.FeathersError({
                error: e,
                success: false
            })
        }

    };
};


afterRegistration.js

const errors = require('@feathersjs/errors');
// Use this hook to manipulate incoming or outgoing data.
// For more information on hooks see: http://docs.feathersjs.com/api/hooks.html

// eslint-disable-next-line no-unused-vars
module.exports = function (options = {}) {
    return async context => {
        try {
            const { result, userData, params } = context
            if (!result)
                return context
            const { id: account_id } = result
            const data = {
                ...userData,
                account_id,
                created_by: 1,
                updated_by: 1,
            }
            delete data.password
            const newContext = { ...context, data }
            await context.app.service('/users').create(data, { ...params,userData, noToken: true })
            return newContext
        } catch (e) {
            throw errors.FeathersError({
                error: e,
                success: false
            })

        }

    };
};

and this is the users.hooks.js

const validate = require('feathers-hooks-validate-joi');
const { users } = require('../../validations');
const { hooks } = require('feathers-knex');
const { transaction } = hooks;
const { verifyToken } = require('../../hooks/auth')
const { beforeCreateUsers, afterCreateUsers } = require('../../hooks/users');

module.exports = (options) => ({
  before: {
    all: [verifyToken(options), transaction.start()],
    find: [],
    get: [],
    create: [validate.form(users.createUsersSchema, users.joiOptions),beforeCreateUsers()],
    update: [],
    patch: [],
    remove: []
  },

  after: {
    all: [transaction.end()],
    find: [],
    get: [],
    create: [afterCreateUsers(),]
    ,
    update: [],
    patch: [],
    remove: []
  },

  error: {
    all: [transaction.rollback()],
    find: [],
    get: [],
    create: [],
    update: [],
    patch: [],
    remove: []
  }
});

Expected behavior

rollback when error happened

Actual behavior

not rolling back

System configuration

Tell us about the applicable parts of your setup.

Module versions (especially the part that's not working):

dependencies": {
    "@feathersjs/authentication": "^2.1.16",
    "@feathersjs/authentication-jwt": "^2.0.10",
    "@feathersjs/configuration": "^2.0.6",
    "@feathersjs/errors": "^3.3.6",
    "@feathersjs/express": "^1.3.1",
    "@feathersjs/feathers": "^3.3.1",
    "@feathersjs/socketio": "^3.2.9",
    "amazon-cognito-identity-js": "^3.0.9",
    "compression": "^1.7.3",
    "cors": "^2.8.5",
    "feathers-hooks-validate-joi": "^2.0.0",
    "feathers-knex": "^5.0.5",
    "feathers-logger": "^0.3.2",
    "helmet": "^3.15.1",
    "joi": "^14.3.1",
    "joi-date-extensions": "^1.2.0",
    "jsonwebtoken": "^8.5.1",
    "jwk-to-pem": "^2.0.1",
    "knex": "^0.16.3",
    "knex-waitfordb": "^1.1.0",
    "moment": "^2.24.0",
    "mysql": "^2.16.0",
    "mysql2": "^1.6.5",
    "node-fetch": "^2.3.0",
    "parse-error": "^0.2.0",
    "serve-favicon": "^2.5.0",
    "winston": "^3.2.1"
  },
  "devDependencies": {
    "eslint": "^5.13.0",
    "mocha": "^5.2.0",
    "nodemon": "^1.18.10",
    "request": "^2.88.0",
    "request-promise": "^4.2.2"
  }
}

NodeJS version:
v10.15.3

Operating System:
ubuntu

kind Regards

I don't know if this is a cause of your issue. But from your code i see one problem.

In registration.hooks.js you have:

after: {
  all: [transaction.end()],
  ...
  create: [afterRegistration(options)],
  ...
}

And afterRegistration hook calls users service with ...params, i assume to share a transaction. But the problem is that all hooks are executed first and then specific method ones. And in this case a transaction is committed and removed from params with transaction.end hook, so users service will not share the transaction, and if users service fails then you end up with a created registration and no user.

Should be fixed via #206 in v5.1.0. Thanks @vonagam