@orchestrated-io/cqrs-domain
Version:
Node-cqrs-domain is a node.js module based on node-eventstore. It can be very useful as domain component if you work with (d)ddd, cqrs, eventdenormalizer, host, etc.
630 lines (511 loc) • 16 kB
JavaScript
var debug = require('debug')('domain:structureLoader'),
_ = require('lodash'),
path = require('path'),
structureParser = require('./structureParser'),
Context = require('./../definitions/context'),
Aggregate = require('./../definitions/aggregate'),
Command = require('./../definitions/command'),
Event = require('./../definitions/event'),
BusinessRule = require('./../definitions/businessRule'),
PreCondition = require('./../definitions/preCondition'),
PreLoadCondition = require('./../definitions/preLoadCondition'),
LoadingEventTransformer = require('./../definitions/loadingEventTransformer'),
CommittingEventTransformer = require('./../definitions/committingEventTransformer'),
CommandHandler = require('./../definitions/commandHandler');
function isSchema (item) {
return item.fileType === 'json' && item.value.title;
}
function isContext (item) {
if (item.fileType === 'json') {
return false;
}
return item.value instanceof Context;
}
function isAggregate (item) {
if (item.fileType === 'json') {
return false;
}
return item.value instanceof Aggregate;
}
function isCommand (item) {
if (item.fileType === 'json') {
return false;
}
return item.value instanceof Command;
}
function isEvent (item) {
if (item.fileType === 'json') {
return false;
}
return item.value instanceof Event;
}
function isPreCondition (item) {
if (item.fileType === 'json') {
return false;
}
return item.value instanceof PreCondition;
}
function isPreLoadCondition (item) {
if (item.fileType === 'json') {
return false;
}
return item.value instanceof PreLoadCondition;
}
function isLoadingEventTransformer (item) {
if (item.fileType === 'json') {
return false;
}
return item.value instanceof LoadingEventTransformer;
}
function isCommittingEventTransformer (item) {
if (item.fileType === 'json') {
return false;
}
return item.value instanceof CommittingEventTransformer;
}
function isBusinessRule (item) {
if (item.fileType === 'json') {
return false;
}
return item.value instanceof BusinessRule;
}
function isCommandHandler (item) {
if (item.fileType === 'json') {
return false;
}
return item.value instanceof CommandHandler;
}
function defineNameOfSchema (item) {
var name = item.value.title;
if (!name) {
var splits = item.dottiedBase.split('.');
name = splits[splits.length - 1];
}
item.name = name;
}
function defineName (item, invert) {
var name = item.value.name;
if (name === '') {
item.name = name;
return;
}
function defineNameByDir () {
if (!name) {
var splits = item.dottiedBase.split('.');
name = splits[splits.length - 1];
}
if (!name) {
var tmp = item.path.substring(0, item.path.lastIndexOf(path.sep + item.fileName));
name = tmp.substring(tmp.lastIndexOf(path.sep) + 1);
}
}
function defineNameByFileName () {
if (!name) {
name = item.fileName.substring(0, item.fileName.lastIndexOf('.'));
}
}
if (invert) {
defineNameByDir();
defineNameByFileName();
} else {
defineNameByFileName();
defineNameByDir();
}
item.name = name;
}
function scan (items) {
var res = {
schemas: [],
contexts: [],
aggregates: [],
commands: [],
events: [],
preConditions: [],
preLoadConditions: [],
businessRules: [],
loadingEventTransformers: [],
committingEventTransformers: [],
commandHandlers: []
};
items.forEach(function (item) {
if (isSchema(item)) {
debug('found schema at: ' + item.path);
defineNameOfSchema(item);
res.schemas.push(item);
return;
}
if (isContext(item)) {
debug('found context at: ' + item.path);
defineName(item, true);
item.value.name = item.name;
res.contexts.push(item);
return;
}
if (isAggregate(item)) {
debug('found aggregate at: ' + item.path);
defineName(item, true);
item.value.name = item.name;
res.aggregates.push(item);
return;
}
if (isCommand(item)) {
debug('found command at: ' + item.path);
defineName(item);
item.value.name = item.name;
res.commands.push(item);
return;
}
if (isEvent(item)) {
debug('found event at: ' + item.path);
defineName(item);
item.value.name = item.name;
res.events.push(item);
return;
}
if (isBusinessRule(item)) {
debug('found businessRule at: ' + item.path);
defineName(item);
item.value.name = item.name;
res.businessRules.push(item);
return;
}
if (isLoadingEventTransformer(item)) {
debug('found loadingEventTransformer at: ' + item.path);
defineName(item);
item.value.name = item.name;
res.loadingEventTransformers.push(item);
return;
}
if (isCommittingEventTransformer(item)) {
debug('found committingEventTransformer at: ' + item.path);
defineName(item);
item.value.name = item.name;
res.committingEventTransformers.push(item);
return;
}
if (isPreCondition(item)) {
debug('found preCondition at: ' + item.path);
defineName(item);
if (!_.isArray(item.name)) {
item.name = [item.name];
}
item.value.name = item.name;
res.preConditions.push(item);
return;
}
if (isCommandHandler(item)) {
debug('found commandHandler at: ' + item.path);
defineName(item);
item.value.name = item.name;
res.commandHandlers.push(item);
return;
}
if (isPreLoadCondition(item)) {
debug('found preLoadCondition at: ' + item.path);
defineName(item);
if (!_.isArray(item.name)) {
item.name = [item.name];
}
item.value.name = item.name;
res.preLoadConditions.push(item);
return;
}
});
return res;
}
function analyze (dir, useLoaderExtensions, callback) {
structureParser(dir, useLoaderExtensions, function (items) {
return _.filter(items, function (i) {
return isSchema(i) || isContext(i) || isAggregate(i) || isCommand(i) || isEvent(i) || isBusinessRule(i) || isPreCondition(i) || isPreLoadCondition(i) || isCommandHandler(i) || isLoadingEventTransformer(i) || isCommittingEventTransformer(i);
});
}, function (err, items, warns) {
if (err) {
return callback(err);
}
var res = scan(items);
callback(null, res, warns);
});
}
function reorderExternallyLoadedContexts (obj, ordered) {
obj.contexts.forEach(function (ctxItem) {
if (!ctxItem.value.externallyLoaded)
return;
ordered[ctxItem.name] = ctxItem.value;
});
}
function reorderAggregates (obj, ordered) {
var generalContext = new Context({ name: '_general' });
obj.aggregates.forEach(function (aggItem) {
var foundCtx = _.find(obj.contexts, function (ctx) {
if (ctx.value.externallyLoaded) {
return false;
}
if (aggItem.dottiedBase.indexOf('.') >= 0) {
return aggItem.dottiedBase.indexOf(ctx.dottiedBase + '.') === 0;
} else {
return aggItem.dottiedBase === ctx.dottiedBase;
}
});
if (!foundCtx) {
foundCtx = _.find(obj.contexts, function (ctx) {
if (ctx.value.externallyLoaded) {
return false;
}
return ctx.dottiedBase === '';
});
}
var ctxName = '_general';
if (foundCtx) {
ctxName = foundCtx.name;
ordered[ctxName] = ordered[ctxName] || foundCtx.value;
} else {
ordered[ctxName] = ordered[ctxName] || generalContext;
}
ordered[ctxName].addAggregate(aggItem.value);
// mark context for aggregate
aggItem.context = ctxName;
});
}
function reorderDefault (obj, ordered, what) {
obj[what + 's'].forEach(function (objItem) {
var foundAggr = _.find(obj.aggregates, function (aggr) {
if (objItem.dottiedBase.indexOf('.') >= 0) {
return objItem.dottiedBase.indexOf(aggr.dottiedBase + '.') === 0;
} else {
return objItem.dottiedBase === aggr.dottiedBase;
}
});
if (!foundAggr) {
return;
}
var whatCap = what.charAt(0).toUpperCase() + what.slice(1);
var agg = ordered[foundAggr.context].getAggregate(foundAggr.name);
agg['add' + whatCap].call(agg, objItem.value);
// mark context and aggregate
objItem.context = foundAggr.context;
objItem.aggregate = foundAggr.name;
});
}
function reorderCommands (obj, ordered) {
reorderDefault(obj, ordered, 'command');
}
function reorderEvents (obj, ordered) {
reorderDefault(obj, ordered, 'event');
}
function reorderBusinessRules (obj, ordered) {
reorderDefault(obj, ordered, 'businessRule');
}
function reorderCommandHandlers (obj, ordered) {
reorderDefault(obj, ordered, 'commandHandler');
}
function reorderLoadingEventTransformers (obj, ordered) {
reorderDefault(obj, ordered, 'loadingEventTransformer');
}
function reorderCommittingEventTransformers (obj, ordered) {
reorderDefault(obj, ordered, 'committingEventTransformer');
}
function preorderPreConditions (obj, ordered) {
obj.preConditions.forEach(function (objItem) {
var foundAggr = _.find(obj.aggregates, function (aggr) {
if (objItem.dottiedBase.indexOf('.') >= 0) {
return objItem.dottiedBase.indexOf(aggr.dottiedBase + '.') === 0;
} else {
return objItem.dottiedBase === aggr.dottiedBase;
}
});
if (!foundAggr) {
return;
}
// mark context and aggregate
objItem.context = foundAggr.context;
objItem.aggregate = foundAggr.name;
});
}
function reorderPreConditions (obj, ordered) {
preorderPreConditions(obj, ordered);
obj.preConditions.forEach(function (pc) {
var foundCmds = _.filter(obj.commands, function (cmd) {
return pc.context === cmd.context &&
pc.aggregate === cmd.aggregate &&
(pc.name.indexOf(cmd.name) >= 0 || pc.name.indexOf('') >= 0) &&
(pc.name.indexOf('') >= 0 || pc.version === cmd.version);
});
if (!foundCmds || foundCmds.length === 0) {
if (pc.name.length > 0) {
debug('no cmd found for ', pc.name);
return;
}
return;
}
foundCmds.forEach(function (foundCmd) {
foundCmd.value.addPreCondition(pc.value);
});
});
}
function preorderPreLoadConditions (obj, ordered) {
obj.preLoadConditions.forEach(function (objItem) {
var foundAggr = _.find(obj.aggregates, function (aggr) {
if (objItem.dottiedBase.indexOf('.') >= 0) {
return objItem.dottiedBase.indexOf(aggr.dottiedBase + '.') === 0;
} else {
return objItem.dottiedBase === aggr.dottiedBase;
}
});
if (!foundAggr) {
return;
}
// mark context and aggregate
objItem.context = foundAggr.context;
objItem.aggregate = foundAggr.name;
});
}
function reorderPreLoadConditions (obj, ordered) {
preorderPreLoadConditions(obj, ordered);
obj.preLoadConditions.forEach(function (plc) {
var foundCmds = _.filter(obj.commands, function (cmd) {
return plc.context === cmd.context &&
plc.aggregate === cmd.aggregate &&
(plc.name.indexOf(cmd.name) >= 0 || plc.name.indexOf('') >= 0) &&
(plc.name.indexOf('') >= 0 || plc.version === cmd.version);
});
if (!foundCmds || foundCmds.length === 0) {
if (plc.name.length > 0) {
debug('no cmd found for ', plc.name);
return;
}
return;
}
foundCmds.forEach(function (foundCmd) {
foundCmd.value.addPreLoadCondition(plc.value);
});
});
}
function reorderValidationRules (obj, ordered, validatorExtension) {
var allSchemas = {};
var cmdSchemas = [];
obj.schemas.forEach(function (schemaItem) {
allSchemas['/' + schemaItem.name] = schemaItem.value;
var foundCtx = _.find(obj.contexts, function (ctx) {
return schemaItem.dottiedBase.indexOf(ctx.dottiedBase) === 0;
});
if (foundCtx) {
schemaItem.context = foundCtx.name;
} else {
schemaItem.context = '_general';
}
var foundAggr = _.find(obj.aggregates, function (aggr) {
if (schemaItem.dottiedBase.indexOf('.') >= 0) {
return schemaItem.dottiedBase.indexOf(aggr.dottiedBase + '.') === 0;
} else {
return schemaItem.dottiedBase === aggr.dottiedBase;
}
});
if (foundAggr) {
schemaItem.aggregate = foundAggr.name;
}
if (!schemaItem.context || (schemaItem.context === '_general' && schemaItem.dottiedBase === '' && !schemaItem.aggregate)) {
// it's a general schema
return;
}
if (foundCtx && foundCtx.dottiedBase === schemaItem.dottiedBase && schemaItem.context !== '_general') {
// it's a context schema
return;
}
if (!schemaItem.aggregate) {
debug('no aggregate found for schema: ' + schemaItem.path);
// skip
return;
}
if (foundAggr.dottiedBase === schemaItem.dottiedBase) {
var foundPossibleCommand = _.find(foundAggr.value.commands, 'name', schemaItem.value.title);
if (!foundPossibleCommand) {
// it's an aggregate schema
return;
}
}
cmdSchemas.push(schemaItem);
});
var formats = {};
var getValidatorFn = require('./../validator');
var validator = {
addFormat: function (key, value) {
if (!key) {
var err = new Error('Please pass valid arguments!');
debug(err);
throw err;
}
if (!value) {
_.forOwn(key, function (v, k) {
formats[k] = v;
});
} else {
formats[key] = value;
}
},
addSchema: function (key, value) {
if (!key) {
var err = new Error('Please pass valid arguments!');
debug(err);
throw err;
}
if (!value) {
_.forOwn(key, function (v, k) {
allSchemas[k] = v;
});
} else {
allSchemas[key] = value;
}
},
validator: function (fn) {
if (!fn || !_.isFunction(fn)) {
var err = new Error('Please pass a valid function!');
debug(err);
throw err;
}
getValidatorFn = fn;
}
};
validatorExtension(validator);
cmdSchemas.forEach(function (schemaItem) {
// check for all commands, if nothing found continue...
obj.commands.forEach(function (cmdItem) {
if (cmdItem.name === schemaItem.name &&
cmdItem.aggregate === schemaItem.aggregate &&
cmdItem.context === schemaItem.context &&
((schemaItem.value.version === undefined || schemaItem.value.version === null) || cmdItem.value.version === schemaItem.value.version)) {
var cmd = ordered[schemaItem.context].getAggregate(schemaItem.aggregate).getCommand(schemaItem.name, schemaItem.value.version);
// it's a command schema
cmd.defineValidation(getValidatorFn({ schemas: allSchemas, formats: formats }, schemaItem.value));
}
});
});
}
function reorder (obj, validatorExtension) {
var ordered = {};
reorderExternallyLoadedContexts(obj, ordered);
reorderAggregates(obj, ordered);
reorderCommands(obj, ordered);
reorderEvents(obj, ordered);
reorderBusinessRules(obj, ordered);
reorderCommandHandlers(obj, ordered);
reorderValidationRules(obj, ordered, validatorExtension);
reorderPreLoadConditions(obj, ordered);
reorderPreConditions(obj, ordered);
reorderLoadingEventTransformers(obj, ordered);
reorderCommittingEventTransformers(obj, ordered);
if (!ordered || _.isEmpty(ordered)) {
debug('analyzed: ', obj);
debug('ordered: ', ordered);
}
return ordered;
}
function load (dir, validatorExtension, useLoaderExtensions, callback) {
analyze(dir, useLoaderExtensions, function (err, dividedByTypes, warns) {
if (err) {
return callback(err);
}
var structured = reorder(dividedByTypes, validatorExtension);
callback(err, structured, warns);
});
}
module.exports = load;