Skip to content

Transaction Support #109

Description

@ritch

/to @raymondfeng @altsang
/cc @bajtos

In the interest of LoopBack reliability, I'd like to start a discussion on making LoopBack ACID compliant. After brushing off earlier attempts to look into the issue I had an idea.

I think Transactions + Multiversioning will get us 80% or more to ACID.

Transactions

function Transaction(dataSource) {
  this.domain = domain.create();
  this.dataSource = dataSource;
  this.id = this.getHashCode();
  this.snapshots = [];

  domain.transaction = this;
  domain.on('error', this.onError.bind(this));
}

Transaction.prototype.commit = function(callback) {
  this.domain.run(function run() {
    this.dataSource.startTransaction(this);
  }.bind(this));
}

Transaction.prototype.snapshot = function(name, callback) {
  this.snapshots.push(this.dataSource.snapshot(name, this, callback));
}

Transaction.prototype.rollback = function(name, fn) {
  this.currentSnapshot = name ? this.getSnapshot(name) : this.snapshots.pop();
  if(this.currentSnapshot) {
    this.dataSource.applySnapshot(this.currentSnapshot)
  }
}

Transaction.prototype.onError = function() {
  this.rollback(function rollback(err) {
    this.dispose();
    throw err;

  }.bind(this));
}

Transaction.prototype.dispose = function(callback) {
  this.domain.dispose();
  this.dataSource.endTransaction(this, callback);
}

Transaction.getCurrent = function() {
  return domain.active.transaction;
}

// strong-remoting hooking
remotes.before('**', function(ctx, next, method) {
  var t = ctx.transaction = new Transaction(method.constructor.dataSource);
  t.domain.add(ctx.req);
  t.domain.add(ctx.res);
  t.commit(next);
});

remotes.after('**', function(ctx, next) {
  ctx.transaction.dispose(next);
});

The above requires the dataSource / connector to implement methods that delegate to driver transactions, or multiversioning. To support multiversioning, an outer wrapper of the connector could be devised that essentially tracks each method call and model instance. As well as changing the behavior slightly.

Multiversioning

By default all update operations during a transaction would create copies of the given model instance and tag them with the transaction.id. Model's created during the transaction would also be tagged with the transaction.id.

By default, all queries would exlclude instances tagged with transaction.ids.

Failure / Cleanup

If the transaction fails. All models tagged with a transaction.id are removed. This could also be ensured on startup / by a job of some sort to essentially garbage collect tagged instances.

If the transaction completes without error all tagged model instances would replace matching untagged models.

Concerns
During the final phase of a succesful transaction, since transactions aren't isolated by locking, there is a potential for cleanup to happen out of order. If the transaction.id is sortable we might be able to avoid this.

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions