1.0.5 • Published 1 year ago

multiple-transaction-manager v1.0.5

Weekly downloads
-
License
MIT
Repository
github
Last release
1 year ago

multiple-transaction-manager

Multiple transaction manager library provides sequential execution of provided tasks from various transactional contexts. All contexts commit if the workflow completes without any exception, otherwise, all contexts are signalled to rollback in the presented order.

Features

  • A lightweight plain TypeScript library
  • Powered by provided contexts
  • Easy to deploy/integrate

Install

npm install multiple-transaction-manager

See the context pages for their installation commands.

Usage

The basic library provides the FunctionContext class which can be used to create a basic workflow. However more complex scenarios can be handled by using the additional contexts. Please refer the example project for a more detailed use case of the library. (https://github.com/kaplanke/mtxn-example)

    // init manager & context
    const txnMngr: MultiTxnMngr = new MultiTxnMngr();
    const functionContext = new FunctionContext(txnMngr);

    // Add a task for selecting Bob, Dave, or Kevin randomly
    const randTask: Task = functionContext.addTask((task) => {
        task.result = ["Bob", "Kevin", "Dave"][Math.floor(Math.random() * 3)];
        logger.info(task.result + " is selected");
        return Promise.resolve(task);
    });

    // Add a pop task that will add the proper task on the fly... 
    functionContext.addCondTask((_) => {
        if (randTask.getResult() === "Kevin") { // It' OK Kevin
            return CondTaskRet.BREAK();
        } else if (randTask.getResult() === "Dave") { // Bad Dave
            return CondTaskRet.ROLLBACK("No worries, this is expected for Dave...");
        }
        return CondTaskRet.CONTINUE(); // Bob 
    });

    // Add third step. Should not execute if Dave is selected
    functionContext.addTask(
        (task) => { return new Promise((resolve, _) => { console.log("Executing 3"); resolve(task) }); },
        null, // optional params
        (task) => { return new Promise((resolve, _) => { console.log("On Txn Commit 3"); resolve(task); }); },
        (task) => { return new Promise((resolve, _) => { console.log("On Txn Rollback 3"); resolve(task); }); }
    );

    txnMngr.exec().then(_ => {
        expect(randTask.getResult()).not.toBe("Dave");
    }).catch(err => { 
        logger.debug(err);
        expect(randTask.getResult()).toBe("Dave");
    });

The expected output:

If Dave is randomly selected

[INFO] default - Dave is selected
[DEBUG] MultiTxnMngr - Transaction quitting with rollback!
[ERROR] MultiTxnMngr - Transaction chain failed. Please see previous errors.
[INFO] MultiTxnMngr - Transaction chain rollbacked.
[DEBUG] default - No worries, this is expected for Dave...

If Kevin is randomly selected

[INFO] default - Kevin is selected
[DEBUG] MultiTxnMngr - Transaction quitting without rollback...
[INFO] MultiTxnMngr - Transaction chain completed.

If Bob is randomly selected

[INFO] default - Bob is selected
[DEBUG] MultiTxnMngr - Condition OK. Continuing transaction...
Executing 3.
On Txn Commit 3
[INFO] MultiTxnMngr - Transaction chain completed.

API

Interfaces

multiple-transaction-manager has two interfaces. Although the basic library provides FunctionContext and additional contexts are already available for different transaction providers like MySQL and MongoDB, you can create your own transaction provider by implementing those interfaces.

Context

export interface Context {
    init(): Promise<Context>;
    commit(): Promise<Context>;
    rollback(): Promise<Context>;
    isInitialized(): boolean;
    getName(): string;
}

init()

Called once by the transaction manager for initializing the transaction.

  • Returns: {Promise\} A promise that resolves to the context instance.

commit()

Called once by the transaction manager after successful completion of all tasks.

  • Returns: {Promise\} A promise that resolves to the context instance.

rollback()

Called once by the transaction manager if any of the tasks failed.

  • Returns: {Promise\} A promise that resolves to the context instance.

isInitialized()

Called by transaction manager before each task execution to check if the transaction is already initialized.

  • Returns: {Promise\} A promise that resolves to the context instance.

getName()

  • Returns: {string} The unique name for the context instance.

Task

export interface Task {
    getContext(): Context;
    exec(): Promise<Task>;
    getResult(): any;
}

getContext()

  • Returns: {Context} The task's context.

exec()

Calls the function provided in the execFunc property of the task.

  • Returns: {Promise\} A promise that resolves to the task instance.

getResult()

  • Returns: {any} the result of the task, if any.

Classes

MultiTxnMngr

constructor()

  • Returns: {MultiTxnMngr} The created MultiTxnMngr instance.

FunctionContext

constructor(txnMngr)

  • txnMngr: {MultiTxnMngr} The multiple transaction manager to bind with the context.
  • Returns: {FunctionContext} The created FunctionContext instance.

addTask(execFunc, params, commitFunc, rollbackFunc)

Adds a function task to the transaction manager.

  • execFunc: {(task: FunctionTask) => Promise\} The function to execute during the workflow.
  • params: {Function | Object} Parameter to pass to the fnExec function. Can be null.
  • commitFunc: {(task: FunctionTask) => Promise\} Optional function to execute during commit phase.
  • rollbackFunc: {(task: FunctionTask) => Promise\} Optional function to execute during rollback phase.
  • Returns: {FunctionTask} The created FunctionTask instance.

addPopTask(popTask)

Adds a task which will generate an array of additional tasks to inject to the task list of the transaction manager. This function is useful to add conditional tasks to the execution list.

  • popTask: {(task: PopTask) => Task[]} The function that will generate additional tasks to add to the task list.

FunctionTask

constructor(context, execFunc, params, commitFunc, rollbackFunc)

  • context: {Context} The FunctionContext to to bind with the task.
  • execFunc: {(task: FunctionTask) => Promise\} The main function to execute.
  • params: {unknown} Optional parameter object.
  • commitFunc: {(task: FunctionTask) => Promise\} Optional function to execute during commit phase.
  • rollbackFunc: {(task: FunctionTask) => Promise\} Optional function to execute during rollback phase.
  • Returns: {FunctionTask} The created FunctionTask instance.

PopTask

constructor(context, popFunc)

  • context: {Context} The FunctionContext to to bind with the task.
  • popFunc: {(task: PopTask) => Task[]} The function that will generate additional tasks to add to the task list.
  • Returns: {PopTask} The created PopTask instance.

CondTask

constructor(context, condFunc)

  • context: {Context} The FunctionContext to to bind with the task.
  • condFunc: {(task: CondTask) => CondTaskRet} The function that will evaluate the current condition and return the proper CondTaskRet.
  • Returns: {CondTask} The created CondTaskRet instance.

CondTaskRet

CondTaskRet.CONTINUE()

  • Returns: {CondTaskRet} The created CondTaskRet instance which allows the transaction flow without any intervention.

CondTaskRet.BREAK(params)

  • params: {object} Additional parameters which will be available as the result of the task after execution.
  • Returns: {CondTaskRet} The created CondTaskRet instance which makes the transaction to commit immediately without executing any consecutive tasks.

CondTaskRet.ROLLBACK(msg, params)

  • msg: {Context} The FunctionContext to to bind with the task.
  • params: {object} Additional parameters which will be available as the result of the task after execution.
  • Returns: {CondTaskRet} The created CondTaskRet instance which makes the transaction to rollback immediately with the provided message.

Contexts

Currently the following contexts are available;

You can leave comment to vote for development of a new context. https://github.com/users/kaplanke/projects/2

Discussions

Although multiple-transaction-manager manages the transactions under normal circumstances, there are still some critical point to consider during the execution of the workflow;

  • If an error occurs during the commit phase of a context, the rollback action is triggered for the entire MultiTxnMngr which may not be able to rollback already committed contexts.
  • You may still have deadlocks during the execution of the workflow if you use the same transaction provider for multiple contexts. (For example if you use Sequelize Context and MySql context at the same MultiTxnMngr, you may block both contexts during a reciprocal update statement.)
  • If you like to pass parameters that depends on the execution of the previous tasks, you should use a function ( ()=>{} ) instead of an object. (There is an exception for Redis context, please see the context page for details.)