Jonathan Oliver's Common Domain Saga Implementation

1,382 views
Skip to first unread message

Jeff Doolittle

unread,
Apr 13, 2011, 12:19:53 PM4/13/11
to DDD/CQRS
I'm having trouble understanding how/when a Saga gets spun up in the
CommonDomain project.
I'm looking at the following files:

https://github.com/joliver/CommonDomain/blob/master/src/proj/CommonDomain.Core/SagaBase.cs
https://github.com/joliver/CommonDomain/blob/master/src/proj/CommonDomain.Persistence.EventStore/SagaEventStoreRepository.cs

My understanding of a Saga is that it manages state transitions
involving multiple aggregate roots by receiving events and then
publishing a command when a certain state is reached. I'm not
understanding how in this implementation a Saga gets spun up in
response to a particular domain event. It looks like they are
retrieved by Saga Id. But how can I know what Saga Id to spin up if
all I have to go on is a Domain Event that doesn't have any knowledge
of the fact that there is a Saga that wants to know about it?

What am I missing? I appreciate any help understanding this.

--Jeff

@yreynhout

unread,
Apr 13, 2011, 1:28:35 PM4/13/11
to DDD/CQRS
I'd presume messaging infrastructure has subscribed to the event. When
an event is received a handler would be called, which in turn creates
the saga (because the event denotes the start of the saga). The
created saga gets persisted, using a saga id (akin to an aggregate id
- an event stream id so to speak).

On 13 apr, 18:19, Jeff Doolittle <jeffdoolit...@gmail.com> wrote:
> I'm having trouble understanding how/when a Saga gets spun up in the
> CommonDomain project.
> I'm looking at the following files:
>
> https://github.com/joliver/CommonDomain/blob/master/src/proj/CommonDo...https://github.com/joliver/CommonDomain/blob/master/src/proj/CommonDo...

@yreynhout

unread,
Apr 13, 2011, 1:32:24 PM4/13/11
to DDD/CQRS
To clarify, the event denotes a new saga should be started. Querying
the repo using a new GUID will create a new stream aka saga.

On 13 apr, 19:28, "@yreynhout" <yves.reynh...@gmail.com> wrote:
> I'd presume messaging infrastructure has subscribed to the event. When
> an event is received a handler would be called, which in turn creates
> the saga (because the event denotes the start of the saga). The
> created saga gets persisted, using a saga id (akin to an aggregate id
> - an event stream id so to speak).
>
> On 13 apr, 18:19, Jeff Doolittle <jeffdoolit...@gmail.com> wrote:
>
>
>
> > I'm having trouble understanding how/when a Saga gets spun up in the
> > CommonDomain project.
> > I'm looking at the following files:
>
> >https://github.com/joliver/CommonDomain/blob/master/src/proj/CommonDo......

Jonathan Oliver

unread,
Apr 13, 2011, 2:45:03 PM4/13/11
to DDD/CQRS
I suppose I'm obligated to chime in because it is my code.

The SagaRepository exposes the generic method: TSaga
GetById<TSaga>(Guid sagaId) where TSaga class, new()

All of your saga-based message handlers are gonna look about like
this:

var mySaga =
repository.GetById<CustomerPurchaseSaga>(message.CorrelationId);
mySaga.Transition(message);
repository.Save(mySaga);

You'll notice that I'm not doing any kind of unit of work. If you
want some kind of unit of work *that spans exactly one saga/aggregate/
stream* you'll need to implement that yourself.

Jonathan

Jeff Doolittle

unread,
Apr 13, 2011, 3:14:40 PM4/13/11
to DDD/CQRS
Are you saying that this Saga implementation cannot be used to handle
transitions that affect multiple aggregates? I'm not sure what the
source is of the "CorrelationId", maybe understanding that would help
me get it. Am I properly understanding what a Saga is for?

--Jeff

On Apr 13, 11:45 am, Jonathan Oliver <jonathan.s.olive...@gmail.com>
wrote:

Jeff Doolittle

unread,
Apr 13, 2011, 2:43:43 PM4/13/11
to DDD/CQRS
I understand how to initially create the Saga. What I can't figure
out is how the correct Saga gets reloaded later when an event is
published that the Saga needs to know about.

--Jeff

@yreynhout

unread,
Apr 13, 2011, 3:45:33 PM4/13/11
to DDD/CQRS
You put a correlation id in the commands the saga emits and when an
aggregate handles that command, it emits events. Make sure they that
contain the given correlation id, thus allowing the event handler to
find the saga by correlation id.
Some would solve this using an envelope, putting things such as the
saga id in its headers, passing it on from input to output.

In contradiction to aggregates, I do feel sagas deserve secondary
indexes (what NServiceBus solves by mapping message fields to saga
state), but I have yet to find a way to integrate that idea with the
eventstore (streamstore ;-)).

Jeff Doolittle

unread,
Apr 13, 2011, 4:31:16 PM4/13/11
to DDD/CQRS
I think I've got it now. As long as I pass the Saga Id forward with
any commands it dispatches, and then include this Saga Id with any
events published based on the processing of the command, then I can
reload the Saga in my event handler. Correct?

I was thinking about how NServiceBus does this differently, and I
think this explains some of my earlier confusion.

Thanks for the help, guys.

--Jeff

Jonathan Oliver

unread,
Apr 13, 2011, 6:32:42 PM4/13/11
to DDD/CQRS
There's actually a pattern for this, it's called the Correlation
Identifier Pattern:
http://www.eaipatterns.com/CorrelationIdentifier.html
http://www.eaipatterns.com/ramblings/09_correlation.html

For multi-aggregate correlation, you need something common between
them.

Jeff Doolittle

unread,
Apr 14, 2011, 11:22:30 AM4/14/11
to DDD/CQRS
perfect. thanks.

On Apr 13, 3:32 pm, Jonathan Oliver <jonathan.s.olive...@gmail.com>
wrote:
> There's actually a pattern for this, it's called the Correlation
> Identifier Pattern:http://www.eaipatterns.com/CorrelationIdentifier.htmlhttp://www.eaipatterns.com/ramblings/09_correlation.html

Jeff Doolittle

unread,
Apr 21, 2011, 12:05:25 PM4/21/11
to DDD/CQRS
@Jonathan (or anyone else who can help)

I have couple more questions regarding the Event Store and Common
Domain Saga implementation.

Let's say I have the following scenario:

An Employee requests leave
Leave Request must be approved by the HR Manager
Leave Request must be approved by the employees Department Manager

Possible results:
Leave Request is granted if both managers approve the request
Leave Request is denied if one manager denies the request
Leave Request is denied if no manager action is taken within one
week of the request

So I'm going to be kicking off a Saga when the employee requests
leave.

Questions:
1) do I create the Saga in a Command Handler? or do I create the Saga
in response to an Event? or does it depend on the scenario?
2) related to #1, should a Saga Repository be accessed from an Event
Handler? If so, doesn't this cross the wire between your read model
and write model and prevent scalability? perhaps you'd set up
separate event store persistence for Aggregates apart from that used
for Sagas? or are you sharing the same event store for both? if the
Saga Repository isn't used in an Event Handler, then where?
3) how do you do "timeouts" if a Saga does not complete by a certain
time limit (as in the third transition listed above)? I know
NServiceBus has Timeout management functionality, but since the Common
Domain saga implementation is different, I'm guessing there has to be
another way to handle this?

I think I'm really close to getting my head around this and hopefully
a slight nudge in the right direction will put me right where I need
to be.

Thanks,

--Jeff

On Apr 13, 3:32 pm, Jonathan Oliver <jonathan.s.olive...@gmail.com>
wrote:
> There's actually a pattern for this, it's called the Correlation
> Identifier Pattern:http://www.eaipatterns.com/CorrelationIdentifier.htmlhttp://www.eaipatterns.com/ramblings/09_correlation.html

@yreynhout

unread,
Apr 21, 2011, 5:29:25 PM4/21/11
to DDD/CQRS
1) LeaveRequestedEventHandler: LeaveRequestedEvent kicks off the Saga.
How you model your aggregates that participate in this saga would be
interesting to know.
2) The SagaRepository is used to load/store the Saga in the
eventstore. Whether you pick the same or separate stores is a
deployment issue. Not every event handler is a front end to a
readmodel (denormalizer).
3) DIY, or something like http://quartznet.sourceforge.net/, you need
some kind of callback (persistent or otherwise)
> > Identifier Pattern:http://www.eaipatterns.com/CorrelationIdentifier.htmlhttp://www.eaipa...

Steve Sheldon

unread,
Apr 22, 2011, 2:28:25 AM4/22/11
to ddd...@googlegroups.com
We went through the same thought process, and came to the conclusion that timeouts are an external subscription based upon time.

Think of it as another type of event, "1 week after this happens, throw a timeout event to let me know".

I'm not sure the best way to implement this, but certainly using something like Quartz.Net as yves suggested would work.

Jonathan Matheus

unread,
Apr 22, 2011, 10:16:48 AM4/22/11
to ddd...@googlegroups.com
You can still use the Timeout manager from NSB. It's just an endpoint that receives timeout requests and sends timeout responses. 

It's perfectly ok for sagas to listen to events from other domains in order to coordinate workflows between different domains. I think someone posted a while back the phrase, "One domains events can be another domain's commands". Saga's can coordinate by listening to events from one domain and convert them into commands in another domain.

What do your aggregates currently look like? 

Jonathan Oliver

unread,
Apr 23, 2011, 11:52:55 AM4/23/11
to DDD/CQRS
I have an external timeout mechanism wake the saga back up. Greg
Young talks about "sending a message to my future self."

Also, I like to keep my domain event storage completely separate from
my saga event storage. It makes a lot of things easier--like
rebuilding your view model because you don't have anything else mixed
into your domain event storage.

On Apr 22, 10:16 am, Jonathan Matheus <jmath...@gmail.com> wrote:
> You can still use the Timeout manager from NSB. It's just an endpoint that
> receives timeout requests and sends timeout responses.
>
> It's perfectly ok for sagas to listen to events from other domains in order
> to coordinate workflows between different domains. I think someone posted a
> while back the phrase, "One domains events can be another domain's
> commands". Saga's can coordinate by listening to events from one domain and
> convert them into commands in another domain.
>
> What do your aggregates currently look like?
>
>
>
>
>
>
>
> On Fri, Apr 22, 2011 at 1:28 AM, Steve Sheldon <st...@sodablue.org> wrote:
> > We went through the same thought process, and came to the conclusion that
> > timeouts are an external subscription based upon time.
>
> > Think of it as another type of event, "1 week after this happens, throw a
> > timeout event to let me know".
>
> > I'm not sure the best way to implement this, but certainly using something
> > like Quartz.Net as yves suggested would work.
>

Jeff Doolittle

unread,
Apr 25, 2011, 12:03:08 PM4/25/11
to DDD/CQRS
Thanks, that's exactly what I was thinking regarding keeping them
separate. I've got all the tools I need now to be dangerous. :)
Thanks everyone for the input and clarification.

--Jeff

On Apr 23, 8:52 am, Jonathan Oliver <jonathan.s.olive...@gmail.com>
wrote:

Mikael Östberg

unread,
Feb 20, 2012, 8:48:49 AM2/20/12
to ddd...@googlegroups.com
Sorry to wake up this old thread, but I'm actually wondering about waking things up. 

What mechanisms do you use to wake sagas up? 

I'm particularly interested in the "If no manager has answered in one week, the request should be denied" case, to refer to Jeffs original problem.

I intend to use the CommonDomain saga, which feeds off of events. This should mean that the the wake up call should be an event, right?

So, what is the best way to schedule an event to my future self? 

Can the Timeout Manager from NSB be used without using the NSB saga? Or do I want to use the NSB saga?

Thanks!

::m

Yevhen Bobrov

unread,
Feb 20, 2012, 8:55:41 AM2/20/12
to ddd...@googlegroups.com
You can write your own Timeout Manager which is not really hard. Rinat has a good example here

Basically, you would just pass a command to timeout service which will be delivered back when a timeout occur.
Then you'll pickup it from queue and handle it using your usual means (CommandBus)

It's an idea that Greg mentioned in his workshop.

Yevhen.

20.02.2012 15:48, Mikael Östberg пишет:

Mikael Östberg

unread,
Feb 20, 2012, 9:31:23 AM2/20/12
to DDD/CQRS
Would that mean that an event could be scheduled as well, since the
Common Domain sagas feeds off of events? Or are there any design
issues with that?

But I guess I technically could use a command to wake the saga as
well. In the Common Domain saga, there is only the Transition methods
that drive it and there's nothing there that states that Transition
couldn't handle commands. But I'm a bit uncertain. I'll have a go at
it.

::m

On Feb 20, 2:55 pm, Yevhen Bobrov <yevhen.bob...@gmail.com> wrote:
> You can write your own Timeout Manager which is not really hard. Rinat
> has a good example here
> <http://abdullin.com/journal/2010/7/20/lokad-cqrs-advanced-task-schedu...>

Yevhen Bobrov

unread,
Feb 20, 2012, 10:35:47 AM2/20/12
to ddd...@googlegroups.com
Fed off != Waked up by.

Look at Sagas as just another kind of Aggregate.

You can create saga in response to an event or a command.
You can then wake up Saga in response to an event or a command as well.
You will load it from repository by using correlation id (Saga ID) specified in a command or event.

Sagas in JOliver's implementation are using events just to restore internal state.
So you do not need to bother creating special mementos like in NSB.

An excerpt from this post: -->

  When a message arrives to be processed it is routed into the appropriate message handler
   whose “Handle” would look something like this:
  public void Handle(OrderReceivedEvent message)
  {    
     var sagaId = message.OrderId; // purchase correlation    
     
     var saga = repository.GetById(sagaId);    
     saga.Transition(message);    
     
     repository.Save(saga);
  }
-->

Read the post I've mentioned above, it shows how all this works (including dispatching of commands produced by Saga)

P.S. In fact NSB load Sagas by using a correlation id, which you need to specify explicitly.

Yevhen

20.02.2012 16:31, Mikael Östberg пишет:
Reply all
Reply to author
Forward
0 new messages