Simplify your system debugging by introducing event store linking

… and check why 5600+ Rails engineers read also this

Simplify your system debugging by introducing event store linking

Last week I was dealing with an interesting bug. In our system, there was a user that didn’t belong to any tenant. And a tenant that didn’t have any users. It’s a situation that shouldn’t happen in our application.

Debugging that issue was quite simple because of the linking feature of RailsEventStore that we use in our application to correlate all events related to a user in a single stream. By linking to such a user stream you get the possibility to see all events that are related to that certain user account.

What is interesting here is that there’s a UserLeftTenant event, which in our case, should lead to deleting the user’s account if that was the only tenant that the user belonged to. But that didn’t happen. Additionally, as you can see events that happened after, there was still a possibility to log in as that user. Which resulted in a very ugly error.

Well, at least we can see that the account still exists and it’s still possible to log in, right? Eventually, it turned out that there was another way for a user to leave the tenant. It didn’t follow the process of checking if that was the only tenant that the user belongs to. It was also quite easy to find in the code as I was able to grep by the UserLeftTenant event and find that place in our codebase. Another benefit of using events ;)

  class LinkByUserId
    def initialize(event_store: Rails.configuration.event_store, prefix: "$by_user_id_")
      @event_store = event_store
      @prefix = prefix
    end

    def call(event)
      user_id = event.metadata[:user_id]
      @event_store.link([event.event_id], stream_name: "#{@prefix}#{user_id}") if user_id
    end
  end

You have to subscribe to all events

    event_store.subscribe_to_all_events(LinkByUserId.new)

Now we have to add the user_id into the metadata. In the usual Rails application, you can set it up in your ApplicationController as around_action callback.

class ApplicationController < ActionController::Base
    
    around_action :enrich_events_with_current_user_metadata, if: :current_user
    
    def enrich_events_with_current_user_metadata
     extra_metadata = { user_id: current_user.id, locale: I18n.locale.to_s }
     event_store.with_metadata(extra_metadata) { yield }
    end
end

Of course, you don’t have to limit yourself to linking events to user streams. Anything interesting for you, your team, or the business stakeholders will work well. Don’t be scared to find new insights about your application.

If you get in trouble setting up your RES, feel free to join our community

You might also like