eventric
Version:
Build JavaScript applications with Behaviour-driven Domain Design. Based on DDD, BDD, CQRS and EventSourcing.
247 lines (232 loc) • 10.2 kB
JavaScript
var Projection, eventric,
__bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
eventric = require('eventric');
Projection = (function() {
function Projection() {
this._applyDomainEventToProjection = __bind(this._applyDomainEventToProjection, this);
this.log = eventric.log;
this._handlerFunctions = {};
this._projectionInstances = {};
this._domainEventsApplied = {};
}
Projection.prototype.initializeInstance = function(projectionObj, params, context) {
return new Promise((function(_this) {
return function(resolve, reject) {
var ProjectionClass, aggregateId, diFn, diName, projection, projectionId, projectionName, _ref;
projectionName = 'whoami';
if (projectionObj.name) {
projectionName = projectionObj.name;
}
if (projectionObj["class"]) {
ProjectionClass = projectionObj["class"];
projection = new ProjectionClass;
}
if (projectionObj.object) {
projection = projectionObj.object;
}
if (context._di) {
_ref = context._di;
for (diName in _ref) {
diFn = _ref[diName];
projection[diName] = diFn;
}
}
projectionId = eventric.generateUid();
aggregateId = null;
projection.$subscribeHandlersWithAggregateId = function(_aggregateId) {
return aggregateId = _aggregateId;
};
_this.log.debug("[" + context.name + "] Clearing Projections");
return _this._clearProjectionStores(projection.stores, projectionName, context).then(function() {
_this.log.debug("[" + context.name + "] Finished clearing Projections");
return _this._injectStoresIntoProjection(projectionName, projection, context);
}).then(function() {
return _this._callInitializeOnProjection(projectionName, projection, params, context);
}).then(function() {
var eventName, eventNames, key, value;
_this.log.debug("[" + context.name + "] Replaying DomainEvents against Projection " + projectionName);
eventNames = [];
for (key in projection) {
value = projection[key];
if ((key.indexOf('handle')) === 0 && (typeof value === 'function')) {
eventName = key.replace(/^handle/, '');
eventNames.push(eventName);
}
}
return _this._applyDomainEventsFromStoreToProjection(projectionId, projection, eventNames, aggregateId, context);
}).then(function(eventNames) {
_this.log.debug("[" + context.name + "] Finished Replaying DomainEvents against Projection " + projectionName);
return _this._subscribeProjectionToDomainEvents(projectionId, projectionName, projection, eventNames, aggregateId, context);
}).then(function() {
_this._projectionInstances[projectionId] = projection;
context.publish("projection:" + projectionName + ":initialized", {
id: projectionId,
projection: projection
});
return resolve(projectionId);
})["catch"](function(err) {
return reject(err);
});
};
})(this));
};
Projection.prototype._callInitializeOnProjection = function(projectionName, projection, params, context) {
return new Promise((function(_this) {
return function(resolve, reject) {
if (!projection.initialize) {
_this.log.debug("[" + context.name + "] No initialize function on Projection " + projectionName + " given, skipping");
return resolve(projection);
}
_this.log.debug("[" + context.name + "] Calling initialize on Projection " + projectionName);
return projection.initialize(params, function() {
_this.log.debug("[" + context.name + "] Finished initialize call on Projection " + projectionName);
return resolve(projection);
});
};
})(this));
};
Projection.prototype._injectStoresIntoProjection = function(projectionName, projection, context) {
return new Promise((function(_this) {
return function(resolve, reject) {
if (!projection.stores) {
return resolve();
}
if (projection["$store"] == null) {
projection["$store"] = {};
}
return eventric.eachSeries(projection.stores, function(projectionStoreName, next) {
_this.log.debug("[" + context.name + "] Injecting ProjectionStore " + projectionStoreName + " into Projection " + projectionName);
return context.getProjectionStore(projectionStoreName, projectionName, function(err, projectionStore) {
if (projectionStore) {
projection["$store"][projectionStoreName] = projectionStore;
_this.log.debug("[" + context.name + "] Finished Injecting ProjectionStore " + projectionStoreName + " into Projection " + projectionName);
return next();
}
});
}, function(err) {
if (err) {
return reject(err);
}
return resolve();
});
};
})(this));
};
Projection.prototype._clearProjectionStores = function(projectionStores, projectionName, context) {
return new Promise((function(_this) {
return function(resolve, reject) {
if (!projectionStores) {
return resolve();
}
return eventric.eachSeries(projectionStores, function(projectionStoreName, next) {
_this.log.debug("[" + context.name + "] Clearing ProjectionStore " + projectionStoreName + " for " + projectionName);
return context.clearProjectionStore(projectionStoreName, projectionName, function() {
_this.log.debug("[" + context.name + "] Finished clearing ProjectionStore " + projectionStoreName + " for " + projectionName);
return next();
});
}, function(err) {
return resolve();
});
};
})(this));
};
Projection.prototype._applyDomainEventsFromStoreToProjection = function(projectionId, projection, eventNames, aggregateId, context) {
return new Promise((function(_this) {
return function(resolve, reject) {
var findEvents;
_this._domainEventsApplied[projectionId] = {};
if (aggregateId) {
findEvents = context.findDomainEventsByNameAndAggregateId(eventNames, aggregateId);
} else {
findEvents = context.findDomainEventsByName(eventNames);
}
findEvents.then(function(domainEvents) {
if (!domainEvents || domainEvents.length === 0) {
return resolve(eventNames);
}
return eventric.eachSeries(domainEvents, function(domainEvent, next) {
return _this._applyDomainEventToProjection(domainEvent, projection, function() {
_this._domainEventsApplied[projectionId][domainEvent.id] = true;
return next();
});
}, function(err) {
if (err) {
return reject(err);
}
return resolve(eventNames);
});
});
return findEvents["catch"](function(err) {
return reject(err);
});
};
})(this));
};
Projection.prototype._subscribeProjectionToDomainEvents = function(projectionId, projectionName, projection, eventNames, aggregateId, context) {
return new Promise((function(_this) {
return function(resolve, reject) {
var domainEventHandler, eventName, subscriberId, _base, _i, _len;
domainEventHandler = function(domainEvent, done) {
if (_this._domainEventsApplied[projectionId][domainEvent.id]) {
return done();
}
return _this._applyDomainEventToProjection(domainEvent, projection, function() {
_this._domainEventsApplied[projectionId][domainEvent.id] = true;
context.publish("projection:" + projectionName + ":changed", {
id: projectionId,
projection: projection
});
return done();
});
};
for (_i = 0, _len = eventNames.length; _i < _len; _i++) {
eventName = eventNames[_i];
if (aggregateId) {
subscriberId = context.subscribeToDomainEventWithAggregateId(eventName, aggregateId, domainEventHandler, {
isAsync: true
});
} else {
subscriberId = context.subscribeToDomainEvent(eventName, domainEventHandler, {
isAsync: true
});
}
if ((_base = _this._handlerFunctions)[projectionId] == null) {
_base[projectionId] = [];
}
_this._handlerFunctions[projectionId].push(subscriberId);
}
return resolve();
};
})(this));
};
Projection.prototype._applyDomainEventToProjection = function(domainEvent, projection, callback) {
if (!projection["handle" + domainEvent.name]) {
this.log.debug("Tried to apply DomainEvent '" + domainEvent.name + "' to Projection without a matching handle method");
return callback();
}
if (projection["handle" + domainEvent.name].length === 2) {
return projection["handle" + domainEvent.name](domainEvent, callback);
} else {
projection["handle" + domainEvent.name](domainEvent);
return callback();
}
};
Projection.prototype.getInstance = function(projectionId) {
return this._projectionInstances[projectionId];
};
Projection.prototype.destroyInstance = function(projectionId, context) {
var subscriberId, _i, _len, _ref;
if (!this._handlerFunctions[projectionId]) {
return eventric.log.error('Missing attribute projectionId');
}
_ref = this._handlerFunctions[projectionId];
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
subscriberId = _ref[_i];
context.unsubscribeFromDomainEvent(subscriberId);
}
delete this._handlerFunctions[projectionId];
return delete this._projectionInstances[projectionId];
};
return Projection;
})();
module.exports = new Projection;