Correlation id and causation id in evented systems
… and check why 5600+ Rails engineers read also this
Correlation id and causation id in evented systems
Debugging can be one of the challenges when building asynchronous, evented systems. Why did this happen, what caused all of that?. But there are patterns which might make your life easier. We just need to keep track of what is happening as a result of what.
For that, you can use 2 metadata attributes associated with events you are going to publish.
Let’s hear what Greg Young says about correlation_id
and causation_id
:
Let’s say every message has 3 ids. 1 is its id. Another is correlation the last it causation. 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).
Now, the message that you are responding to can be either a command or an event which triggered some event handlers and probably caused even more events.
In Rails/Ruby Event Store this is also possible. Recently we’ve released version 0.29.0 which adds #with_metadata method that makes it even easier. BTW, that was our 52nd release.
class MyEventHandler
def call(event)
event_store.with_metadata(
correlation_id: event.metadata[:correlation_id] ||
event.event_id,
causation_id: event.event_id
) do
# do something which triggers another event(s)
event_store.publish_event(MyEvent.new(data: {foo: 'bar'}))
end
end
private
def event_store
Rails.configuration.event_store
end
end
of course, if you don’t publish many events, it might be easier to apply it manually, once.
class MyEventHandler
def call(event)
# do something which triggers another event
event_store.publish_event(MyEvent.new(
data: {foo: 'bar'},
metadata: {
correlation_id: event.metadata[:correlation_id] ||
event.event_id,
causation_id: event.event_id
}
))
end
end
Now, keeping that correlation and causation IDs in events’ metadata is one thing. That’s beneficial and if you want to check why event X
happened you can just easily do it, but it’s not where the story ends.
Imagine that you have a global handler that registered which reacts to every event occurring in your system and building two projections by linking the events to certain streams:
class BuildCorrelationCausationStreams
def call(event)
if causation_id = event.metadata[:causation_id]
event_store.link_event(
event.event_id,
stream_name: "causation-#{causation_id}"
)
end
if correlation_id = event.metadata[:correlation_id]
event_store.link_event(
event.event_id,
stream_name: "correlation-#{correlation_id}"
)
end
end
end
What would that give you?
Given an event with id 54b2
you can now check:
- in stream
causation-54b2
all the events which were triggered directly as a result of it - and in stream
correlation-54b2
all the events which were triggered directly or indirectly as a result of it (if that message started the whole conversation)
That makes it possible to verify what happened because of X?
I hope that in the future we can automate all of it when some of the upcoming Ruby Event Store features are finished:
Would you like to continue learning more?
If you enjoyed that story, subscribe to our newsletter. We share our every day struggles and solutions for building maintainable Rails apps which don’t surprise you.
You might enjoy reading:
- Ruby Event Store - use without Rails - did you know you can use RailsEventStore without Rails by going with RubyEventStore :)
- When DDD clicked for me - It took me quite a time to grasp the concepts from DDD community and apply them in our Rails projects. This is a story of one of such “aha” moments.
- Why Event Sourcing basically requires CQRS and Read Models - Event sourcing is a nice technique with certain benefits. But it has a big limitation. As there is no concept of easily available current state, you can’t easily get an answer to a query such as give me all products with available quantity lower than 10. What can be done about it?
- Relative Testing vs Absolute Testing - 2 modes of testing that you can switch between to make writing tests easier.
- Using ruby parser and the AST tree to find deprecated syntax - when grep is not enough for your refactorings.