thenativeweb / node-cqrs-domain

Node-cqrs-domain is a node.js module based on nodeEventStore that. It can be very useful as domain component if you work with (d)ddd, cqrs, eventdenormalizer, host, etc.
http://cqrs.js.org/pages/domain.html
MIT License
269 stars 57 forks source link

Added commandAwareAggregateIdGenerator and tests #93

Closed nanov closed 7 years ago

nanov commented 7 years ago

As the command (meta)data may be useful when generating id in some situation ( example order id, that some includes customer id in it ), i wanted to pass the command as a parameter to the defined generator function ( on the aggregate level ).

As this is a breaking change and for backwards compatibility I've added this option under a new definer ( defineCommandAwareAggregateIdGenerator ) on the aggregate level, which, exactly as the other one, can work both synchronously or asynchronously.

I've also added the needed tests to the definition tests.

adrai commented 7 years ago

Finally you want a new function while defining a new aggregate that is would be something like this right?

module.exports = require('cqrs-domain').defineAggregate({
  // optional, default is last part of path name
  name: 'person',

  // optional, default 0
  version: 3,

  // optional, default ''
  defaultCommandPayload: 'payload',

  // optional, default ''
  defaultEventPayload: 'payload',

  // optional, default ''
  defaultPreConditionPayload: 'payload',

  // optional, default false
  // by skipping the history, only the last event will be loaded and defaultly not applyed (just to ensure the revision number increment)
  skipHistory: true,

  // optional, default false
  // only optionally needed when skipHistory is set to true, only the last event will be loaded and applyed
  applyLastEvent: true,

  // optional, default false
  // will publish the events but will not commit them to the eventstore
  disablePersistence: false
},

// optionally, define some initialization data...
{
  emails: ['default@mycomp.org'],
  phoneNumbers: []
})

// optionally, define snapshot need algorithm...
.defineSnapshotNeed(function (loadingTime, events, aggregateData) {
  // loadingTime is the loading time in ms of the eventstore data
  // events are all loaded events in an array
  // aggregateData represents the aggregateData after applying the resulting events
  return events.length >= 200;
})

// optionally, define if snapshot should be ignored
// if true, the whole event stream will be loaded
.defineIgnoreSnapshot({
  version: 0
}, function (data) {
  return true;
})
//.defineIgnoreSnapshot({
//  version: 0
//}, true)
//.defineIgnoreSnapshot({
//  version: 0
//}) // default true

// optionally, define conversion algorithm for older snapshots
// always convert directly to newest version...
// when loaded a snapshot and it's an older snapshot, a new snapshot with same revision but with newer aggregate version will be created
.defineSnapshotConversion({
  version: 1
}, function (data, aggregate) {
  // data is the snapshot data
  // aggregate is the aggregate object

  aggregate.set('emails', data.emails);
  aggregate.set('phoneNumbers', data.phoneNumbers);

  var names = data.name.split(' ');
  aggregate.set('firstname', names[0]);
  aggregate.set('lastname', names[1]);
})
// optionally, define idGenerator function for new aggregate ids
// sync
.defineAggregateIdGenerator(function () {
  return require('uuid').v4().toString();
});
// or async
.defineAggregateIdGenerator(function (callback) {
  setTimeout(function () {
    var id = require('uuid').v4().toString();
    callback(null, id);
  }, 50);
})
// optionally, define idGenerator function for new aggregate ids that are command aware
// if you define it that way, the normal defineAggregateIdGenerator function will be replaced
// sync
.defineCommandAwareAggregateIdGenerator(function (cmd) {
  return require('uuid').v4().toString();
});
// or async
.defineCommandAwareAggregateIdGenerator(function (cmd, callback) {
  setTimeout(function () {
    var id = require('uuid').v4().toString();
    callback(null, id);
  }, 50);
});
adrai commented 7 years ago

I think this can be done more easily... may I suggest something?

nanov commented 7 years ago

Yes of course, please do, just hacked it quickly it would be useful and not break the existing methods.

I was thinking on the option to add the command parameter after the callback one, but it looks kinda ugly so I avoided it.

nanov commented 7 years ago

Another option would be to bind the handlers to the command, then it would be accessable trought "this", but because the aggregate itself is binded alredy i didn't wanted to change it, as it may break exsiting code.

adrai commented 7 years ago

v2.6.0 ok for you?

nanov commented 7 years ago

Perfect! Thank you for the quick response!