Subscribing for events in rails_event_store

Sample CQRS / ES application gone wrong

In my post Building an Event Sourced application I’ve included sample code to setup denormalizers (event handlers) that will build a read model:

def event_store @event_store ||= RailsEventStore :: Client . new . tap do | es | es . subscribe ( Denormalizers :: Router . new ) end end

One router to rule them all

Because that is only a sample application showing how easy is to build an Event Sourced application using Ruby/Rails and Rails Event Store there were some shortcuts. Shortcuts that should have never been there. Shortcuts that have made some doubts for others who try to build their own solution.

The router was defined as:

module Denormalizers class Router def handle_event ( event ) case event . event_type when Events :: OrderCreated . name then Denormalizers :: Order . new . order_created ( event ) when Events :: OrderExpired . name then Denormalizers :: Order . new . order_created ( event ) when Events :: ItemAddedToBasket then Denormalizers :: OrderLine . new . item_added_to_basket ( event ) when Events :: ItemRemovedFromBasket then Denormalizers :: OrderLine . new . item_removed_from_basket ( event ) end end end end

And denormalisers were implemented as:

module Denormalizers class Order def order_created ( event ) # ... end def order_expired ( event ) # ... end end end

But we could remove it completely and we do not need that case at all!

All this code could be rewritten using rails_event_store subscriptions as follows:

#command handler (or anywhere you want to initialise rails_event_store def event_store @event_store ||= RailsEventStore :: Client . new . tap do | es | es . subscribe ( Denormalizers :: OrderCreated . new , [ 'Events::OrderCreated' ]) es . subscribe ( Denormalizers :: OrderExpired . new , [ 'Events::OrderExpired' ]) es . subscribe ( Denormalizers :: ItemAddedToBasket . new , [ 'Events::ItemAddedToBasket' ]) es . subscribe ( Denormalizers :: ItemRemovedFromBasket . new , [ 'Events::ItemRemovedFromBasket' ]) end end #sample event handler (denormaliser) module Denormalizers class OrderCreated def handle_event ( event ) # ... denormalisation code here end end end

You see? No Router at all! It’s event store who “knows” where to send messages (events) based on subscriptions defined.

Implicit assumptions a.k.a conventions

Sometimes when you have a simple application like this it is tempting to define “convention” and avoid the tedious need to setup all subscriptions. It seems to be easy to implement and (at least at the beginning of the project) it seems to be elegant and simple solution that would do “the magic” for us.

# WARNING: not recommended code ahead ;) def event_store @event_store ||= RailsEventStore :: Client . new . tap do | es | get_all_events_defined . each | event_class | handlers_for ( event_class ). each | handler | es . subscribe ( handler , [ event_class . to_s ]) end end end end def get_all_events_defined [ Events :: OrderCreate , Events :: OrderExpired , Events :: ItemAddedToBasket , Events :: ItemRemovedFromBasket ] # or implement some more sophisticated way of getting all event's classes ;) end def handlers_for ( event_class ) handler_class = "Denormalizers:: #{ event_class . name . demodulize } " . constantize handler_class . new end

I wonder what would happen if we called it “Implicit Assumptions” instead of “Convention over Configuration”. — Andrzej Krzywda (@andrzejkrzywda) June 7, 2015

Naming is important! If we do not use convention but instead implicit assumption we will realise that it is not that simple and elegant at it looks like. Even worse, project tent to grow. When you will start using domain events you will want more and more of them. You could even want to have several handles for a single event ;) And maybe your handlers will need some dependencies? … Here is the moment when your simple convention breaks!

Make implicit explicit!

By coding the subscriptions one by one, maybe grouping them in some functional areas (bounded context) and clearly defining dependencies you could have more clear code, less “magic” and it should be easier to reason how things work.