Saga vs correlation id vs nested sagas

785 views
Skip to first unread message

Maxim Cherednik

unread,
Apr 18, 2013, 4:33:20 AM4/18/13
to ddd...@googlegroups.com
Hi

I am currently looking into CommonDomain SagaBase implementation and as far as I understand there is only one correlation id, which I can use to retrieve my Saga from event store.
I've faced some issues using this approach. Will try to describe my situation:

1. UI sends CreateUser()

2. UserCommandHandler handles this and save new User aggregate with some event, like: UserCreationRequestPlaced:

3. UserCreationRouter handles this event and route this into UserCreationSaga like this:
            
            var sagaId = event.UserId;
            var saga = _repository.GetById<UserCreationSaga>(sagaId);
            if (saga.Id == Guid.Empty)
            {
                saga = new UserCreationSaga();
                saga.Transition(event);
                _repository.Save(saga,cmd.Id,null);
            }
            else
            {
                Log.WarnFormat("Command CreateUser arrived more than once. Cmd id: {0}" ,sagaId);
            }

4. UserCreationSaga emits some external command, say ExternalSystemCreateUser using source command id as a correlation id, so that I could find my saga later.

5. UserCreationRouter handles 2 kind of events:  ExternalSystemUserCreated, ExternalSystemUserCreationFailed and routes this events respectively to the saga: 

          var sagaId = ev.UserId;
            var saga = _repository.GetById<UserCreationSaga>(sagaId);
            if (saga.Id != Guid.Empty)
            {
                saga.Transition(event);
                _repository.Save(saga, cmd.Id, null);
            }
            else
            {
                Log.WarnFormat("For some reason I can't find saga: {0}" , sagaId);
            }

6.  UserCreationSaga emits a command MarkUserAsCreated or MarkUserAsFailed

7. UserCommandHandler recieves those command and everything is fine


All is simple and straight forward. Correct me if something wrong here.

So my question:

Imagine that I have a need to create a couple of users, one by one. I've got a dedicated command for this, say CreateUsers(firstUserName, secondUserName).

How would you do this, having in mind my previous saga.

1. As I can see, we will need one more SagaRouter and one more Saga, right?

2. Do I have to have one more aggregate for CreateUsers command? So that I could save it first and it emitted the event, some thing like: TwoUsersCreationRequestPlaced - this one will start the saga

3. So we aggreed that TwoUsersCreationRequestPlaced  will be our initiating event with some id which we took from the initial command.

4. And here is I should send a couple of CreateUser() commands to initiate my first sagam if to be recise 2 instances of them

5. I should wait for couple events which will be emmited by User aggregate after this commands MarkUserAsCreated or MarkUserAsFailed.




And here I got lost: I can't find how correlation ids work here. :( 


Maxim Cherednik

unread,
Apr 18, 2013, 5:38:42 AM4/18/13
to ddd...@googlegroups.com
I'd like to extend my thoughts here, as they are coming out of my head :) 

Why our first case is fine and seems to be working? I guess cause we do have a pass-through correllation id which I originally took from the first command. 

The problem with my second case is that I can't use the same approach. 
First, because of I like the idea of generating CommandID in its constructor like Id= Guid.NewGuid();
Second, even if I could use the same ID, that would mean, I emit 2 almost equal commands and id no longer an id here.
Ok, I thought. Why don't I have some special field in the command, say SagaID. Wow, that's cool, I thought. I can wait for the appropriate event(ID, SagaID)

Ok, I need this in my first scenario. Original command can have 2 constructors now  CreateUser() and  CreateUser(SagaID), in case want to use it as nested and stand-alone.

Let's replay it:

1. UI sends CreateUser(SagaID)

2. UserCommandHandler handles this and save new User aggregate with some event, like: UserCreationRequestPlaced:

 new User(SagaID)
{
    ApplyEvent(new UserCreationRequestPlaced(SagaID)
}

repo.Save(user);


Stop, stop, stop. I have to take it everywhere from now on and make my innocent user aggregate know it is a part of big process. I don't think so...

Maxim Cherednik

unread,
Apr 18, 2013, 6:05:44 AM4/18/13
to ddd...@googlegroups.com
Moooore.

Since taking saga id everywhere with me is totally inappropriate, we gonna store it somewhere. 

Imagine,

SampleCommand... when I emit it I know where I am at the moment, e.g. I know SagaID. So let's store it into the special place Called command store:) What fields we would need there, command id of course, current sagaId, and say status(ok or failed).

So every time we emit a command we should store it with saga id or without if it's stand alone command. Then when appropriate event arrives it contains command id, which we use to find out if we part of a saga or not.

Ok, let's reconsider step 5.

5. UserCreationRouter handles 2 kind of events:  ExternalSystemUserCreated, ExternalSystemUserCreationFailed and routes this events respectively to the saga: 

var command = _repository.GetById<ExternalSystemCreateUser>(ev.Id);

          var sagaId = command.SagaId;// <------------------------------------------- tadah!!!
            var saga = _repository.GetById<UserCreationSaga>(sagaId);
            if (saga.Id != Guid.Empty)
            {
                saga.Transition(event);
                _repository.Save(saga, cmd.Id, null);
            }
            else
            {
                Log.WarnFormat("For some reason I can't find saga: {0}" , sagaId);
            }


No? :) 

Maxim Cherednik

unread,
Apr 19, 2013, 1:56:00 AM4/19/13
to ddd...@googlegroups.com
I've found some topics here related to my question and seems like comunity prefer to pass by corellation id either inside event or its envelope - somewhere in message headers.

Maxim Cherednik

unread,
Apr 19, 2013, 2:50:15 AM4/19/13
to ddd...@googlegroups.com
Btw, it seems that I cant use 2 different sagas which react on the same type of event. It will break on deserialization:

1 saga(UserCreationSaga):
public void When(AccountInitialized ev)
        {
            
            var saga = _repository.GetById<UserCreationSaga>(ev.SagaId);
            if (saga.Id != Guid.Empty)
            {
                saga.Transition(event);
                _repository.Save(saga, cmd.Id, null);
            }
            else
            {
                Log.WarnFormat("For some reason I can't find saga: {0}" , sagaId);
            }

}

2 saga(AnotherSaga):
public void When(AccountInitialized ev)
        {
            var saga = _repository.GetById<AnotherSaga>(ev.SagaId);

}

Yevhen Bobrov

unread,
Apr 19, 2013, 4:52:25 AM4/19/13
to ddd...@googlegroups.com
Yes, you're completely right. 

With JOliver's saga store implementation you can't have more that one instance of saga (type) per correlation id being active at a time. I found that issue and it made me think that something is wrong with the approach in general.

The thing is that the SagaId != CorrelationId. Sometimes you can afford such an oversimplification but generally each Saga framework out there (NSB, MT) treat them distinctly. 

So there is usually a secondary index which you can query to get back all active saga instances for a particular correlation id.

As the quick fix, for those who are using JOliver Common Domain, you can derive saga id from correlation id by hashing type of saga and correlation id and deriving guid from that. Here, I assume that having multiple active saga instances of the same type per correlation id is non-sensical, so you can

19 апр. 2013, 09:50, Maxim Cherednik <maxim.c...@gmail.com> написал(а):

--
You received this message because you are subscribed to the Google Groups "DDD/CQRS" group.
To unsubscribe from this group and stop receiving emails from it, send an email to dddcqrs+u...@googlegroups.com.
For more options, visit https://groups.google.com/groups/opt_out.
 
 

Yevhen Bobrov

unread,
Apr 19, 2013, 4:57:24 AM4/19/13
to ddd...@googlegroups.com
... so you can have multiple active saga instances per correlation id but they will be of different type.

Sent from mobile device,
Yevhen

19 апр. 2013, в 11:52, Yevhen Bobrov  <yevhen...@gmail.com> написал(а):

Maxim Cherednik

unread,
Apr 19, 2013, 5:02:40 AM4/19/13
to ddd...@googlegroups.com
Oh, eventually someone :) 

Thanks a lot for the response. The idea of having surrogate key for my saga is interesting, I was thinking about, but obviously not to deep :) Will muse about.

wbr, Maxim Cherednik


--
You received this message because you are subscribed to a topic in the Google Groups "DDD/CQRS" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/dddcqrs/0vHTL3MLRkI/unsubscribe?hl=en.
To unsubscribe from this group and all its topics, send an email to dddcqrs+u...@googlegroups.com.

João Bragança

unread,
Apr 19, 2013, 10:28:21 AM4/19/13
to ddd...@googlegroups.com
You can still use common domain + jes for this. Just use one database per saga.

Maxim Cherednik

unread,
Apr 19, 2013, 11:15:42 AM4/19/13
to ddd...@googlegroups.com
Hah. That would be to much I think. But still thanks for the option:)

wbr, Maxim Cherednik
Reply all
Reply to author
Forward
0 new messages