Permalink
Browse files

updated eventstore

  • Loading branch information...
2 parents 82888be + 2c334ac commit 21171aca0d70581a4cffb996bfe3159277c73d69 @adrai committed Jan 15, 2014
Showing with 369 additions and 132 deletions.
  1. +1 −1 .travis.yml
  2. +21 −2 README.md
  3. +167 −61 lib/bases/commandHandlerBase.js
  4. +5 −5 lib/bases/sagaHandlerBase.js
  5. +25 −6 lib/domain.js
  6. +11 −1 lib/loaders/commandHandlerLoader.js
  7. +2 −2 package.json
  8. +137 −54 test/integration/domainTest.js
View
@@ -2,8 +2,8 @@ before_script: "npm install --dev"
language: node_js
node_js:
- - 0.6
- 0.8
+ - 0.1
branches:
only:
View
@@ -26,7 +26,9 @@ It can be very useful as domain component if you work with (d)ddd, cqrs, eventde
snapshotThreshold: 10,
forcedQueuing: false,
disableQueuing: false,
- handleUpdispatchedEvents: true
+ handleUpdispatchedEvents: true//,
+ // retryOnConcurrencyTimeout: 800,
+ // commandLock: { type: 'inMemory', collectionName: 'commandlock' }
}, function(err) {
});
@@ -77,10 +79,27 @@ See [tests](https://github.com/adrai/node-cqrs-domain/tree/master/test) for deta
# Release Notes
-## v0.6.1
+## v0.7.3
- updated eventstore
+## v0.7.2
+
+- update dependencies
+
+## v0.7.1
+
+- load sagas always from db
+
+## v0.7.0
+
+- introduced commandLock for distributed domain (handling same aggregate instance on multiple machines)
+
+## v0.6.1
+
+- buffer commands by aggregate id
+>>>>>>> 2c334aca7fffdae5fd1ff21fe0044c1a51100356
+
## v0.6.0
- don't publish in eventstore but publish in domain
@@ -1,9 +1,28 @@
var eventEmitter = require('../eventEmitter'),
async = require('async'),
- _ = require('lodash');
+ _ = require('lodash'),
+ util = require('util'),
+ EventEmitter2 = require('eventemitter2').EventEmitter2,
+ uuid = require('node-uuid').v4;
+
+function randomBetween(min, max) {
+ return Math.round(min + Math.random() * (max - min));
+}
+
+var CommandHandler = function() {
+ EventEmitter2.call(this, {
+ wildcard: true,
+ delimiter: ':',
+ maxListeners: 1000 // default would be 10!
+ });
+
+ this.buffered = {};
+ this.id = uuid().toString();
+};
+
+util.inherits(CommandHandler, EventEmitter2);
-var CommandHandler = {};
-CommandHandler.prototype = {
+_.extend(CommandHandler.prototype, {
defaultHandle: function(id, cmd) {
@@ -58,51 +77,98 @@ CommandHandler.prototype = {
});
},
+ reorderCommandLock: function(id, callback) {
+ var self = this;
+ this.commandLock.find({ aggregateId: id }, function(err, res) {
+ res = res || [];
+ res = _.sortBy(res, function(item) {
+ return item.id === self.id;
+ });
+
+ async.each(res, function(item, callback) {
+ item.destroy();
+ self.commandLock.commit(item, callback);
+ }, callback);
+ });
+ },
+
finish: function(id, cmd, err) {
- if (err) {
- eventEmitter.emit('commandRejected', cmd, err);
+ var self = this;
+
+ function _finish() {
+ if (err) {
+ eventEmitter.emit('commandRejected', cmd, err);
+ }
+ eventEmitter.emit('handled:' + cmd.command, id, cmd);
+ self.emit('handled:' + id + ':' + cmd.id, id, cmd);
+ }
+ if (this.commandLock) {
+ this.reorderCommandLock(id, function() {
+ _finish();
+ });
+ } else {
+ _finish();
}
- eventEmitter.emit('handled:' + cmd.command, id, cmd);
},
commit: function(cmd, aggregate, stream, callback) {
-
var self = this;
- async.concat(aggregate.uncommittedEvents, function(evt, next) {
- evt.commandId = cmd.id;
- if (cmd.head) {
- evt.head = _.extend(_.clone(cmd.head), evt.head);
- }
-
- self.getNewId(function(err, id) {
- evt.id = id;
- stream.addEvent(evt);
- next(err);
- });
- },
- // final
- function(err) {
- if (callback && err) { callback(err); }
- if (!err) {
- stream.commit(function(err, stream) {
- if (err) {
- if (callback) { callback(err); }
- return;
- }
+ function _commit() {
+ async.concat(aggregate.uncommittedEvents, function(evt, next) {
+ evt.commandId = cmd.id;
+ if (cmd.head) {
+ evt.head = _.extend(_.clone(cmd.head), evt.head);
+ }
- async.each(stream.eventsToDispatch,
- function(evtToSetDispatched, clb) {
- self.publisher.publish(evtToSetDispatched.payload);
- self.eventStore.setEventToDispatched(evtToSetDispatched, clb);
- },
- function(err) {
+ self.getNewId(function(err, id) {
+ evt.id = id;
+ stream.addEvent(evt);
+ next(err);
+ });
+ },
+ // final
+ function(err) {
+ if (callback && err) { callback(err); }
+ if (!err) {
+ stream.commit(function(err, stream) {
+ if (err) {
if (callback) { callback(err); }
+ return;
}
- );
- });
- }
- });
+
+ async.each(stream.eventsToDispatch,
+ function(evtToSetDispatched, clb) {
+ self.publisher.publish(evtToSetDispatched.payload);
+ self.eventStore.setEventToDispatched(evtToSetDispatched, clb);
+ },
+ function(err) {
+ if (callback) { callback(err); }
+ }
+ );
+ });
+ }
+ });
+ }
+
+ if (this.commandLock) {
+ this.commandLock.find({ aggregateId: aggregate.id }, function(err, res) {
+ res = res || [];
+ if (res.length !== 1 || res[0].id !== self.id) {
+ // concurrency exception!!!
+ self.reorderCommandLock(aggregate.id, function() {
+ // retry
+ setTimeout(function() {
+ self._handle(aggregate.id, cmd);
+ }, randomBetween(0, self.options.retryOnConcurrencyTimeout));
+ });
+ } else {
+ _commit();
+ }
+ });
+ } else {
+ _commit();
+ }
},
validate: function(ruleName, data, callback) {
@@ -113,38 +179,74 @@ CommandHandler.prototype = {
}
},
- handle: function(id, cmd) {
+ _handle: function(id, cmd) {
if (this[cmd.command]) {
this[cmd.command](id, cmd);
} else {
this.defaultHandle(id, cmd);
}
},
- loadAggregate: function(id, callback) {
+ handle: function(id, cmd) {
var self = this;
- var aggregate = new this.Aggregate(id);
- this.eventStore.getFromSnapshot(id, function(err, snapshot, stream) {
- async.map(stream.events, function(evt, next) {
- next(null, evt.payload);
- }, function(err, events) {
- aggregate.loadFromHistory(snapshot.data, events);
-
- // Check if snapshotting is needed.
- var snapshotThreshold = aggregate.getSnapshotThreshold() || self.options.snapshotThreshold;
- if (stream.events.length >= snapshotThreshold) {
- var streamId = stream.streamId,
- revision = stream.currentRevision(),
- data = aggregate.toJSON();
-
- process.nextTick(function() {
- self.eventStore.createSnapshot(streamId, revision, data);
- });
- }
- callback(null, aggregate, stream);
+ this.buffered[id] = this.buffered[id] || [];
+ this.buffered[id].push({ id: id, cmd: cmd });
+
+ this.once('handled:' + id + ':' + cmd.id, function(id, cmd) {
+ self.buffered[id] = _.reject(self.buffered[id], function(entry) {
+ return entry.id === id && entry.cmd === cmd;
});
+
+ if (self.buffered[id].length > 0) {
+ var nextCmd = self.buffered[id][0];
+ self._handle(nextCmd.id, nextCmd.cmd);
+ }
});
+
+ if (this.buffered[id].length === 1) {
+ this._handle(id, cmd);
+ }
+ },
+
+ loadAggregate: function(id, callback) {
+ var self = this;
+
+ function _loadAggregate() {
+ var aggregate = new self.Aggregate(id);
+ self.eventStore.getFromSnapshot(id, function(err, snapshot, stream) {
+ async.map(stream.events, function(evt, next) {
+ next(null, evt.payload);
+ }, function(err, events) {
+ aggregate.loadFromHistory(snapshot.data, events);
+
+ // Check if snapshotting is needed.
+ var snapshotThreshold = aggregate.getSnapshotThreshold() || self.options.snapshotThreshold;
+ if (stream.events.length >= snapshotThreshold) {
+ var streamId = stream.streamId,
+ revision = stream.currentRevision(),
+ data = aggregate.toJSON();
+
+ process.nextTick(function() {
+ self.eventStore.createSnapshot(streamId, revision, data);
+ });
+ }
+
+ callback(null, aggregate, stream);
+ });
+ });
+ }
+
+ if (this.commandLock) {
+ this.commandLock.get(this.id, function(err, res) {
+ res.set('aggregateId', id);
+ self.commandLock.commit(res, function() {
+ _loadAggregate();
+ });
+ });
+ } else {
+ _loadAggregate();
+ }
},
getNewId: function(callback) {
@@ -176,14 +278,18 @@ CommandHandler.prototype = {
if (module.publish) {
this.publisher = module;
}
+
+ if (module.commit && module.get && module.find) {
+ this.commandLock = module;
+ }
}
-};
+});
module.exports = {
extend: function(obj) {
- return _.extend(_.clone(CommandHandler.prototype), obj);
+ return _.extend(new CommandHandler(), obj);
}
};
@@ -87,8 +87,8 @@ SagaHandler.prototype = {
loadSaga: function(id, callback) {
var self = this;
- var saga = this.sagas[id];
- if (!saga) {
+ // var saga = this.sagas[id];
+ // if (!saga) {
saga = new this.Saga(id);
saga.commit = function(callback) {
self.commit(this, callback);
@@ -99,9 +99,9 @@ SagaHandler.prototype = {
callback(err, saga);
});
});
- } else {
- callback(null, saga);
- }
+ // } else {
+ // callback(null, saga);
+ // }
},
configure: function(fn) {
Oops, something went wrong.

0 comments on commit 21171ac

Please sign in to comment.