Rails Event Store in Ruby— Introduction
Domain Driven Design is starting to make big waves in software engineering. As programmers, we always try to make our code easy to read and maintain. The problem arises when our project expands and can easily get out of control. There have been many answers to this problem: Object Oriented Design, SOLID, new frameworks like Trailblazer, functional programming. One idea is also to start thinking of our application as one driven by events.
In this guide, I’ll look at a simple use of the Rails Event Store, a gem created by the Arkency agency, which can be your engine for developing DDD applications. But before we can run, we need to walk first…
Rails Event Store
Rails Event Store is a gem that is an implementation of the Event Store in Ruby for use in a Rails application — you can also use it outside the Rails framework.
To install, add the following line to the Gemfile in your Rails project:
gem "rails_event_store"
Then run the following commands in the terminal. This will generate an event_store table in your database. It will contain all the data needed to publish events, retrieve them and handle them using subscriptions.
rails generate rails_event_store_active_record:migration --data-type=jsonb
rails db:migrate
Blog Post example
For the purpose of our article, let’s focus on the infamous Blog application example. We will have one Post model that will respond to the creation of a new blog post. Let’s see how we can create our first event.
However, before we start creating events, we need a quick configuration in application.rb
module Blog
class Application < Rails::Application
config.to_prepare do
Rails.configuration.event_store = RailsEventStore::Client.new
end
end
end
The code above will help us initialize our event store client, which can be used throughout our application.
Creating code for an event can be as simple as inheriting from RailsEventStore::Event
class PostCreated < RailsEventStore::Event
end
Publish event
To create a new event we will need to name our stream and create the event data (title and content of the blog post).
stream_name = "post_1"
event_data = {title: "Hello World", content: "This is my first blog post..." }
event = PostCreated.new(data: event_data)
client.publish(event, stream_name: stream_name)
We can also use a version with optimistic locking if we have a distributed application and want to avoid race conditions. To do this, we add the expected version to the code fragment.
stream_name = "post_1"
event_data = {title: "Hello World", content: "This is my first blog post..." }
event = PostCreated.new(data: event_data)
client.publish(event, stream_name: stream_name, expected_version: 1)
Publishing an event will commit our event in the database.
Reading events
The Rails event store comes out with an API for retrieving events from the database and many useful helper methods. Let’s look at some of them in the context of our code.
In my opinion, the most useful one is reading events from streams by latest.
scope = client.read.stream('post_1').backward.limit(100).to_a
Searching for events by time can be done using the newer_than method and passing a date as a param.
client.read.stream('post_1').newer_than('2023-02-14').to_a
We can also query events by time range.
client.read.stream('post_1').between(10.days.ago..3.days.ago).to_a
Deleting events
You can delete all events through the stream. This should be avoided in most cases, as it can corrupt the application state.
stream_name = "post_1"
client.delete_stream(stream_name)
Subscription
Finally, I would like to write about subscriptions, as understanding the publish subscribe mechanism can be helpful in the land of Domain Driven Design. Subscriptions respond to publish events using handlers.
In the code example below, we will react with our subscribe handler to the creation of a new post. When this happens, we want to save the post using our ActiveRecord model and we want to notify our subscribers by email that a new post has just been created.
class PostReadModel
def call(event)
if event_type == 'PostCreated'
post = create_new_post(event)
notify_subscribers(post)
end
end
private
def create_new_post
Post.create(event.data)
end
def notify_subscribers(post)
puts "subscribers notified"
Subscriber.each do |subscriber|
BlogMailer.new_post_notification(subscriber.email, post).deliver_now
end
end
end
Now we need to link our handler to the event store, it is as simple as adding this line to our configuration.
client.subscribe(PostReadModel.new, to: [PostCreated])
What’s next?
That would be all in terms of an introduction to the Rails Event Store. We have gone through publishing, subscribing and reading events, but there is a lot more that the framework has to offer like Event Sourcing, Projections, Command Bus. There is also a book on Domain Driven Design using the Rails Event Store from Arkency.
You can find more information in the resources below.
Resources
- https://railseventstore.org/docs/v2/start/
- https://github.com/RailsEventStore/rails_event_store
- https://blog.arkency.com/
- https://products.arkency.com/domain-driven-rails/
Words by Przemysław Mroczek, Senior Engineer
Editing by Kinga Kuśnierz, Content Writer