From ActiveRecord to EventSourcing
Upcoming SlideShare
Loading in...5

From ActiveRecord to EventSourcing



An introduction to a possible implementation of CQRS/ES architecture for a Ruby on Rails app. It starts from Domain Model to arrive to a sample app that implements the Event Sourcing pattern. This ...

An introduction to a possible implementation of CQRS/ES architecture for a Ruby on Rails app. It starts from Domain Model to arrive to a sample app that implements the Event Sourcing pattern. This presentation was part of Wroclove_rb 2014 conference in Wraclow (PL)



Total Views
Views on SlideShare
Embed Views



4 Embeds 30 24 4 1 1



Upload Details

Uploaded via as Adobe PDF

Usage Rights

CC Attribution-NonCommercial LicenseCC Attribution-NonCommercial License

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
Post Comment
Edit your comment

From ActiveRecord to EventSourcing From ActiveRecord to EventSourcing Presentation Transcript

  • From ActiveRecord to Events Emanuele DelBono @emadb
  • Customer Address Invoice Items Contacts Role Contract Price City
  • @emadb I’m a software developer based in Italy. I develop my apps in C#, Javascript and some Ruby. I’m a wannabe Ruby dev.
  • Lasagna architecture
  • View Controller Model (AR) Database
  • O/RM
  • Active record “An object that wraps a row in a database table or view, encapsulates the database access, and adds domain logic on that data.” M. Fowler
  • Active record 1 table => 1 class Too coupled with the database structure No SRP Database first class User < ActiveRecord::Base attr_accessible :email, :password belongs_to :group end
  • Get vs Post def index @products = Product.all end ! def create @product = if redirect_to @product else render action: 'new' end end
  • Active Record A single model cannot be appropriate for reporting, searching and transactional behaviour. Greg Young
  • Read vs Write Reads and writes are two different concerns Reads are simpler Writes need logic
  • Command Query Responsibility Segregation
  • CQRS Presentation Layer Handler BL Repository Write DB Read DB Query Service Denormalizer
  • CQRS Fully normalized Reads are easy Writes became easier SELECT fields FROM table (WHERE …)
  • Object state id basket_id article_id quantity 1 4 8 1 2 3 8 3 3 3 6 1 4 4 5 1
  • Thinking in events Every change is an event. add_item 1 add_item 2 remove_item 1 add_item 3 time
  • Event Sourcing Capture all changes to an application state as a sequence of events. M.Fowler If the changes are stored in a database, we can rebuild the state re-applying the events.
  • Event Sourcing Presentation Layer Bus Handler DMRepository Event store Denormalizer Query service Read DB Command Events
  • Pros • Encapsulation • Separation of concern • Simple storage • Performance/Scaling • Simple testing • More information granularity • Easy integration with other services
  • Cons • Complex for simple scenarios • Cost of infrastructure • Long-living objects needs time to be reconstructed • Tools needed (i.e. rebuild the state)
  • Ingredients • Rails app (no ActiveRecord) • Redis (pub-sub) • Sequel for querying data • MongoDb (event store) • Sqlite (Read db) • Wisper (domain events)
  • Domain Model Basket BasketItem Article * 1 • Fully encapsulated (no accessors) • Fully OOP • Events for communication • PORO
  • show_me_the_code.rb
  • include CommandExecutor ! def add_to_basket send_command {"basket_id" => 42, "article_id" => params[:id].to_i}) redirect_to products_url end Controller Bus Command POST /Products add_to_basket
  • module CommandExecutor ! def send_command (command) class_name = channel = class_name.sub(/Command/, '') @redis.publish channel, command.to_json end ! end send_command
  • def consume(data) basket = repository.get_basket(data["basket_id"]) article = repository.get_article(data["article_id"]) basket.add_item article ! basket.commit end Bus Handler Command handler
  • class Basket include AggregateRootHelper ! def add_item (item) raise_event :item_added, { basket_id: id, item_code: item.code, item_price: item.price } end # ... ! end add_item
  • def raise_event(event, args) @uncommited_events << {name: event, args: args} send "on_#{event}", args end raise_event DM (Basket) Events
  • def get_item (item_code){|i| i.item_code == item_code}.try :first end ! def on_item_added (item) get_item(item[:item_code]).try(:increase_quantity) || @items << end on_item_added DM (Basket) Events
  • def commit while event = uncommited_events.shift, event) send_event event end end commit DM (Basket) Event store
  • Event Store
  • def item_added(data) db = Sequel.sqlite(AppSettings.sql_connection) article = db[:products_view].where(code: data[:item_code]).first basket = db[:basket_view].where('basket_id = ? AND article_id = ?', data[:basket_id], article[:id].to_i).first if basket.nil? #insert else db[:basket_view].where(id: basket[:id]).update(quantity: (basket[:quantity] + 1)) end end denormalizer Denormalizer
  • Read-Db
  • def index @products=db[:basket_view] end Controller Read DB Query GET /Products index
  • Conclusion • Stop thinking in CRUD • Read and Write are different • Domain model should be based on PORO • CQRS/ES is useful in complex scenario • Ruby power helps a lot (less infrastructure code)