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

Sagas #9

Closed bkak closed 11 years ago

bkak commented 11 years ago

Hi, Its me again!

I was going through the sagas pattern.

According to CQRS Journey guide Saga is a Process Manager.
http://msdn.microsoft.com/en-us/library/jj591569.aspx

This Process Manager routes the messages between multiple AR's.

I also came across Clemens Vasters saga explanation : http://blogs.msdn.com/b/clemensv/archive/2012/08/31/sagas.aspx

The Saga implementation that node-cqrs-domain has, is of a Process Manager. Since it is a distributed transaction, we need to take care of compensation actions, if anything fails.

Clemens solution has forward & backward messaging mechanism. Actually I liked the idea of RoutingSlip.

How best we can implement compensation actions in node-cqrs-domain module?

adrai commented 11 years ago

Hi @bkak!

Good input! At the moment I have not so much time to read your links completely, but I think this is really an open task... When I implemented the sagas in the cqrs-domain I decided to just implement a simple version first... and perhaps now it's time to implement a better version for sagas... ;-)

The momentary solution in cqrs-domain is to save some data in the saga and handle these compensation actions manually... (you can save some data by calling this.set()) like here: https://github.com/adrai/cqrs-sample/blob/master/domain/sagas/itemSaga.js

If you want and have more time you are free to propose something via pull request... :-) (I know the code is not really documented, but why not trying...?)

bkak commented 11 years ago

Ok gr8.

I shall give it a try. I am very new to Node, its just a month that I am playing with it.

With your help I should be able to create clemens saga handlers.

Shall buzz u once I get a prototype done.

Thanks a lot.

bkak commented 11 years ago

Hi,

The problem that I am facing in using this is I need Saga capability to issue commands to different AR of another BC in the same internal system, but Business Exception may arise in another BC. If there is any Exception I need to rollback/compensate the commands that were issued in this session. Writing compensation for each and every command will be little difficult. In my domain if there is any Business Exception I can tell the user, that action has failed due to this "Business reason". User can make appropriate changes and issue it again.

Initially I thought to implement RoutingSlip that Clemens Vasters is talking about. Here writing compensation for every command will be tedious. I feel this problem can be solved with Unit of work. If I encapsulate all the commands raised by the User or by the saga in the same session, and if all of them are successful then only on commit push the events to event store and publish all the events generated by them. This way no rollback/compensate will be required.

Can you think of any other solution to this problem?

Your feedback is highly valuable for me.

Thanks, Bhoomi

adrai commented 11 years ago

Hi Bhoomi!

This smells a little like transactions, and this is not CQRS-like... There is no session... and a saga can't react on commands, only on events. If you want to react on commands use commandHandlers... There you can do things like:

var commandHandlerBase = require('cqrs-domain').commandHandlerBase;

module.exports = commandHandlerBase.extend({

    commands: [
          'archive',
          'unarchive',
          'deleteArchive' 
    ],

    aggregate: 'archive',

    unarchive: function(id, cmd) {
        var self = this;

        this.loadAggregate(id, function(err, aggregate, stream) {

            var myObject = aggregate.get('myObject');

            if (myObject) {
                (new self.Command({
                    command: 'addMyObjectToOrganisation',
                    payload: {
                        id: aggregate.get('organisation'),
                        myObject: myObject
                    },
                    head: cmd.head
                })).emit(function(evt) {
                    if (evt.event !== 'commandRejected') {
                        self.finish(id, cmd);
                        (new self.Command({
                            id: cmd.id,
                            command: 'deleteArchive',
                            payload: cmd.payload,
                            head: cmd.head
                        })).emit();
                    } else {
                        self.finish(id, cmd, evt.payload.reason);
                    }
                });
            }

        });
    }
});
bkak commented 11 years ago

Oh no no I am not trying ask Saga to react to Commands.

Ok, my goal is to have Transactional Saga.

I shall give an example :

User places a Sales Order. CreateSalesOrder is the command to be fired on SalesOrder AR. It raises an event SalesOrderCreated. This event is subscribed by Saga and it raises command PostAccountEffect on Accounting AR. Something goes wrong and Accounting AR raises Business Exception. I want all the events to be rolled back instead of doing compensation.

Therefore I thought if I can encapsulate this in one UOW, and commit the UOW when all the commands are executed. On UOW Commit save all the events together in EventStore and then publish it. In case of any Business Exception do not commit the UOW but send Business Exception in response.

adrai commented 11 years ago

Ok, feel free to try it... At least you learned something... :-)

PS. events are published by the eventstore... (so they will be already committed)