causation or correlation id?

1,995 views
Skip to first unread message

esr

unread,
Mar 29, 2015, 10:06:44 AM3/29/15
to event...@googlegroups.com
Here https://groups.google.com/d/msg/event-store/9dyhwgL2hVA/wFU62XSQtCYJ James Nugent say that when storing commands, events produced by it could have the command id as correlation id.

What would a causation id be then? To me it seems that a command causes an event to happen, and the event would then have a causation id that is the commandId.

I guess it's up to me what I want to call it. But are there cases when you would use both, if so how?

Greg Young

unread,
Mar 29, 2015, 10:33:17 AM3/29/15
to event...@googlegroups.com
Causation id correlation Id would set the events correlationid to the correlationid of the command
--
You received this message because you are subscribed to the Google Groups "Event Store" group.
To unsubscribe from this group and stop receiving emails from it, send an email to event-store...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.


--
Studying for the Turing test

esr

unread,
Mar 29, 2015, 11:22:21 AM3/29/15
to event...@googlegroups.com
hm. That was actually a bit confusing :) Causation id and? or? correlation id would set the events correlationid to the correlationid of the command?

So I assume the unique id of the command becomes the commands correlationid.
Here (Journey 7) there is also a SourceId. I guess that's not a correlationId, but maybe a CausationId?

What is the causationid/sourceId for, when is it used?

Greg Young

unread,
Mar 29, 2015, 1:39:44 PM3/29/15
to event...@googlegroups.com
The are both really simple patterns I have never quite understood why
they end up so misunderstood.

Let's say every message has 3 ids. 1 is its id. Another is correlation
the last it causation.

The rules are quite simple. If you are responding to a message, you
copy its correlation id as your correlation id, its message id is your
causation id.

This allows you to see an entire conversation (correlation id) or to
see what causes what (causation id).

Cheers,

Greg

João Bragança

unread,
Mar 29, 2015, 2:10:57 PM3/29/15
to event...@googlegroups.com
What about messages that may need to correlate with more than one process? Or does that even exist?

esr

unread,
Mar 29, 2015, 2:11:15 PM3/29/15
to event...@googlegroups.com
Makes it perfectly clear. Thank you. 

Greg Young

unread,
Mar 29, 2015, 2:11:45 PM3/29/15
to event...@googlegroups.com
These aren't my patterns I just made up one day.

esr

unread,
Mar 29, 2015, 2:21:05 PM3/29/15
to event...@googlegroups.com
I tried to find a concise explaining of the pattern in a code design context, but there was mostly philosophical and statistical results. Not like searching for "Memento pattern", which yields simple comprehensible summaries on first, and every consequent page. So, thanks again!
Message has been deleted

pd

unread,
Apr 11, 2015, 9:55:41 AM4/11/15
to event...@googlegroups.com
I have a DomainEvent base class that takes correlationId and causationId as constructor parameters.
So every event that I create has those two as first constructor parameters.

When persisting to eventstore the metadata is set with those.
The ctrs of the events can get rather long though.

An event ctr could look like this:

public OrderPlaced(Guid correlationId, Guid commandId, Guid orderId, Guid accountId, int actionCode)
 
: base(correlationId, commandId)

Where the causationId is of course the commandId.

Maybe this is a bit whacky? Haven't seen anyone else design their events like this.

(Note: There is no need for order lines in this example, as an order of this sort always has one line only.)


Greg Young

unread,
Apr 11, 2015, 10:52:36 AM4/11/15
to event...@googlegroups.com

Why are they part of the event as opposed to metadata associared with the event?

pd

unread,
Apr 11, 2015, 11:10:26 AM4/11/15
to event...@googlegroups.com
Good question.

Better use some Envelope<Event> instead, with the metadata, like in Conference demo.

I think it would result in this:
For the CommonDomain repository I would then get all envelopes when I get the events of the aggregate, (unless I unwrap them before saving to aggregate list of events). The body of the ES events to save, would then include that metadata. And of course, the same metadata would be put into the headers of the ES event to save. Would that redundancy be ok?

Greg Young

unread,
Apr 11, 2015, 11:11:21 AM4/11/15
to event...@googlegroups.com
Why put in both body and metadata just put in metadata
--
You received this message because you are subscribed to the Google Groups "Event Store" group.
To unsubscribe from this group and stop receiving emails from it, send an email to event-store...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

pd

unread,
Apr 11, 2015, 11:40:29 AM4/11/15
to event...@googlegroups.com
I want to keep that metadata with the event throughout the entire domain. If I store them in the aggregate as envelopes, they become - with current implementation of CommonDomain eventstore repository - the body of the ES event, i.e. including the metadata. I can of course modify that repository implementation easily, and remove the update headers action from the parameter list, and instead extract the headers from the envelopes I get when calling aggregate.GetUncommitedEvents(), and also extract the clean event from the envelope, for saving in ES. (Still looking for how to use that update headers action passed as parameter anyway)

Greg Young

unread,
Apr 11, 2015, 11:45:57 AM4/11/15
to event...@googlegroups.com

Maybe common domain is the problem.

You shouldnt put such things in event body they dont belong there

On 11 Apr 2015 18:40, "pd" <pyd...@gmail.com> wrote:
I want to keep that metadata with the event throughout the entire domain. If I store them in the aggregate as envelopes, they become - with current implementation of CommonDomain eventstore repository - the body of the ES event, i.e. including the metadata. I can of course modify that repository implementation easily, and remove the update headers action from the parameter list, and instead extract the headers from the envelopes I get when calling aggregate.GetUncommitedEvents(), and also extract the clean event from the envelope, for saving in ES. (Still looking for how to use that update headers action passed as parameter anyway)

--

pd

unread,
Apr 11, 2015, 12:09:10 PM4/11/15
to event...@googlegroups.com
True.

I just realized btw that CommonDomain rep news up the eventId in the actual save method (var eventsToSave = newEvents.Select(e => ToEventData(Guid.NewGuid(), e, commitHeaders)).ToList();). I have up until now created the eventId with the event and stored it as it's Id to have during its lifetime in the domain.

Also, I see now that my interpretation of correlationId might be off. Example from a ProcessManager in Conference demo:

if (envelope.CorrelationId != null)
{
 
if (string.CompareOrdinal(this.SeatReservationCommandId.ToString(), envelope.CorrelationId) != 0)
 
{
   
// skip this event
   
Trace.TraceWarning("Seat reservation response for reservation id {0} does not match the expected correlation id.", envelope.Body.ReservationId);
   
return;
 
}
}

Ok, so, even though it's supposed to be a really simple pattern, I find myself wondering about it.
Seems like correlationId is sometimes the commandId. I have been suspecting this, without fully wrapping my head around it, and thinking that this id is always a Guid that is newed up at the start of a process, solely purposed to keep a line of correlation in the (figuratively speaking) "stream of events". So, instead, CorrelationId could be any id of choice, it would depend on situation, and sometimes the correlation to something is best described with a command's id.
I notice that I'd like to have a more strict rule for what a correlationId is composed of, so that the plethora of Guids used can be more easily categorized upon a quick glanze. Maybe that's just a vane attempt to minimize the so abundant "it depends"..

CausationId though, is always the id of what ever directly caused the event/command.

Greg Young

unread,
Apr 11, 2015, 12:16:33 PM4/11/15
to event...@googlegroups.com
Seems like correlationId is sometimes the commandId. I have been suspecting this, without fully wrapping my head around it, and thinking that this id is always a Guid that is newed up at the start of a process, solely purposed to keep a line of correlation in the (figuratively speaking) "stream of events". So, instead, CorrelationId could be any id of choice, it would depend on situation, and sometimes the correlation to something is best described with a command's id.

Why would the command not have a correlation I'd so you can just follow he same pattern as opposed to making many patterns?

On Saturday, April 11, 2015, pd <pyd...@gmail.com> wrote:
--
You received this message because you are subscribed to the Google Groups "Event Store" group.
To unsubscribe from this group and stop receiving emails from it, send an email to event-store...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

pd

unread,
Apr 11, 2015, 2:01:48 PM4/11/15
to event...@googlegroups.com
Geez, that might be what's confusing things so much. These are separate patterns?
You supposed to use correlation or causation id?

Causation id pattern would be like a linked list. Correlation id pattern would just group messages together, without order.
I thought they played well together though, having a single discriminator for group when projecting and also knowing the exact cause of a message.

(sidenote: updateHeaders is just for adding custom headers in code before calling save, code block would look like the dictionary creation in ToEventData method)

Greg Young

unread,
Apr 11, 2015, 2:10:14 PM4/11/15
to event...@googlegroups.com

They are separate patterns and you hse both of them.

But just always copy correlation id correlation id is never commands message id

pd

unread,
Apr 12, 2015, 5:35:46 AM4/12/15
to event...@googlegroups.com
Allright, thanks.
That CQRS Journey example confused me about it.


Den lördag 11 april 2015 kl. 20:10:14 UTC+2 skrev Greg Young:

pd

unread,
Apr 12, 2015, 6:45:41 AM4/12/15
to event...@googlegroups.com
Oh, also: This thing about handling envelopes with correlationId etc. separate from body (the actual domain event) leads to some conflicts.

In the Aggregate:
public void OpenAccount(Guid correlationId, Guid commandId, Guid accountId, Guid ownerId)
{
   
if (_opened) // or some other sort of validation
     
throw new InvalidOperationException("Account already opened");
   
ApplyEvent(new NewAccountOpened(correlationId, commandId, accountId, ownerId));
}

This is the pattern I've read is encouraged; Passing the members of the command as parameters.

Handled like this in abstract Aggregate class:

protected virtual void Apply(dynamic e) { }

public void ApplyEvent(object @event)
{
  ((dynamic)this).Apply(@event);
  _uncommittedEvents.Add(@event);
  this._version++;
}


With envelope, this pattern would not be followed, instead it would be like:
public void OpenAccount(Envelope<ICommand> envelope)
{
   
if (_opened) // or some other sort of validation
     
throw new InvalidOperationException("Account already opened");
   
var command = envelope.Body;
   
if (envelope.Body.GetType() == typeof(OpenNewAccount))
   
{
     
var @event = new NewAccountOpened(command.AccountId, command.OwnerId));
     
var eventEnvelope = Envelope.ForEvent(@event, envelope.CorrelationId, command.Id);
     
ApplyEvent(eventEnvelope);
   
}
   else
      throw new ArgumentException("Wrong command type");

}

and in the abstract aggregate base class:

public void ApplyEvent(Envelope<IDomainEvent> envelope)
{ 
  
((dynamic)this).Apply(envelope.Body); 
   _uncommittedEvents
.Add(envelope); 
   
this._version++;
}


Then when saving to repository and calling the aggregate GetUncommittedEvents(), we would extract the body and the metadata separately from the envelope.

Is this how it is done then (more or less)?

Greg Young

unread,
Apr 12, 2015, 6:47:26 AM4/12/15
to event...@googlegroups.com

Why not use a "context" plus a unit od work. The aggregate doesnt need the envelope.

Message has been deleted

Greg Young

unread,
Apr 12, 2015, 7:48:37 AM4/12/15
to event...@googlegroups.com
public class Context {
public void Enlist(Event e) {
_events.Add(e);
}

public void Set(Command c) {
//remember
}


public void GetEvents() {
return _events.Select( ..... ); build envelopes here if you want
}

public static Context Current //or use DI etc
}


Now in a composite command handler

public void AddContext<T>(T command, Action<T> next) {
Context.Current().Set(command);
next(command);
}



On Sun, Apr 12, 2015 at 2:43 PM, pd <pyd...@gmail.com> wrote:
> True, it felt awkward to give the aggregate an envelope.
> I changed things a bit.
>
> I put this in the Envelope<T> class:
>
> public Action<IDictionary<string, object>> GetHeaders()
> {
> var action = new Action<IDictionary<string, object>>(
> (headers) =>
> {
> var customHeaders = new Dictionary<string, object>(headers)
> {
> { "MessageId", MessageId },
> { "CorrelationId", CorrelationId },
> { "CausationId", CausationId }
> };
> });
>
> return action;
> }
>
>
> Now I have this in the AccountCommandHandler
>
> public async Task<HttpStatusCode> Handle(Envelope<ICommand> envelope)
> {
> return await Handle((dynamic)envelope.Body, envelope.GetHeaders());
> }
>
> public async Task<HttpStatusCode> Handle(OpenNewAccount command,
> Action<IDictionary<string, object>> updateHeaders)
> {
> return await GenericHandle(IsConnected,
> new Func<Action, IRepository, Account>((action, repo) =>
> {
> var x = ConstructAggregate<Account>();
> x.SetSaveCallBack(action);
> x.OpenAccount(command.AccountId, command.OwnerId);
> return x;
> }), updateHeaders);
> }
>
>
> and in the CommandHandlerBase class:
>
> protected async Task<HttpStatusCode> GenericHandle<T>(bool isConnected,
> Func<Action, IRepository, T> operation, Action<IDictionary<string, object>>
> updateHeaders) where T : Aggregate
> {
> if (!isConnected) return HttpStatusCode.ServiceUnavailable;
> try
> {
> var writeStatus = HttpStatusCode.InternalServerError;
> var writeEvent = new ManualResetEvent(false);
> Action callback = () =>
> {
> writeStatus = HttpStatusCode.Accepted;
> writeEvent.Set();
> };
> var aggregate = operation(callback, _repository);
> await _repository.Save(aggregate, Guid.NewGuid(), updateHeaders);
> writeEvent.WaitOne();
> return writeStatus;
> }
> catch (Exception) // some case switch to return proper code by exception
> {
> return HttpStatusCode.InternalServerError;
> }
> }
>
>
>
> --
> You received this message because you are subscribed to the Google Groups
> "Event Store" group.
> To unsubscribe from this group and stop receiving emails from it, send an
> email to event-store...@googlegroups.com.
> For more options, visit https://groups.google.com/d/optout.



pd

unread,
Apr 12, 2015, 7:52:48 AM4/12/15
to event...@googlegroups.com
Seems much simpler.. :)
OK, I'll try and se what I can do with that. Thanks. 

pd

unread,
Apr 12, 2015, 1:19:14 PM4/12/15
to event...@googlegroups.com
Is this a common solution?

Wouldn't I have to put a dependency on this context in the Aggregate to use those events?
The repository either gets the events from the context, or from the aggregate. As it is now, repository takes aggregate, extracts the events, saves them, and clears uncommitted events.

OK, I could pass context instead, and clear uncommitted from aggregate after repository.Save(..) returns. But the aggregate is heavily used in the CommonDomain repository.
It seems brittle to keep the same events both in the context and in the aggregate. Now we suddenly have a consistency issue.

I'm failing to see how the context should be used. Is it passed to specialized command handlers from the composite one? When would events be added to it? I'm sure there's a really nifty way to handle it, I'm just not seeing it.

***

I went with my own solution in the end. I really am open to new thoughts, but sometimes it takes too much time and effort to bend the mind 180º and then it's just not worth it, even if you know it's probably a better solution ^^

Greg Young

unread,
Apr 12, 2015, 1:26:52 PM4/12/15
to event...@googlegroups.com
Context is AKA as UnitOfWork ..

and yes its very common.

You should seriously consider not using that library.

pd

unread,
Apr 12, 2015, 3:27:02 PM4/12/15
to event...@googlegroups.com
well yeah, I know context is uow, and I wasn't asking if uow is common, but rather if that way you described it is common for CQRS + ES.

Anyway, it would be really good to know why I should consider not using that library. It's not that I think I should, I just haven't seen any comments about it being inferior before. Like many others I'm more of a "ah, I can understand that"-person than a "oh, he says so"-person. ;)

pd

unread,
Apr 12, 2015, 3:47:21 PM4/12/15
to event...@googlegroups.com
It's just the EventStore repository that I'm using btw. I've only seen positive things, like this https://groups.google.com/forum/#!searchin/event-store/CommonDomain/event-store/l4jwAiPxe64/idhoT0QSPFYJ mentioned about it before.

Greg Young

unread,
Apr 12, 2015, 4:59:58 PM4/12/15
to event...@googlegroups.com
On Sun, Apr 12, 2015 at 10:27 PM, pd <pyd...@gmail.com> wrote:
> well yeah, I know context is uow, and I wasn't asking if uow is common, but
> rather if that way you described it is common for CQRS + ES.
>


yes its common they are the same pattern.

> well yeah, I know context is uow, and I wasn't asking if uow is common, but
> rather if that way you described it is common for CQRS + ES.

Doing headers by context is hard when using aggregates the way they
work in your example. With a UOW its trivial. Just use the UOW and
your problem goes away.

> Anyway, it would be really good to know why I should consider not using that
> library. It's not that I think I should, I just haven't seen any comments
> about it being inferior before. Like many others I'm more of a "ah, I can
> understand that"-person than a "oh, he says so"-person. ;)
>

Try asking on the neventstore list there has even been talk about
removing it from neventstore.

pd

unread,
Apr 13, 2015, 5:21:29 AM4/13/15
to event...@googlegroups.com
OK, great, posted a question about it there. Thanks a lot for answering all these questions. *Thumbs up*

Doing headers by context is hard when using aggregates the way they
work in your example. With a UOW its trivial. Just use the UOW and
your problem goes away.

I think I have it solved, using the "hard" way.  I just couldn't understand how to implement the UOW easily. I know, must seem daft, but I got stuck on the questions above and couldn't bend my mind right - they're all wrought around this complex solution of mine now ^^
Reply all
Reply to author
Forward
0 new messages