Your SlideShare is downloading. ×
From ActiveRecord to EventSourcing
Upcoming SlideShare
Loading in...5
×

Thanks for flagging this SlideShare!

Oops! An error has occurred.

×
Saving this for later? Get the SlideShare app to save on your phone or tablet. Read anywhere, anytime – even offline.
Text the download link to your phone
Standard text messaging rates apply

From ActiveRecord to EventSourcing

4,831

Published on

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)

Published in: Technology
0 Comments
18 Likes
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total Views
4,831
On Slideshare
0
From Embeds
0
Number of Embeds
9
Actions
Shares
0
Downloads
37
Comments
0
Likes
18
Embeds 0
No embeds

Report content
Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
No notes for slide

Transcript

  • 1. From ActiveRecord to Events Emanuele DelBono @emadb
  • 2. Customer Address Invoice Items Contacts Role Contract Price City
  • 3. @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.
  • 4. Lasagna architecture
  • 5. View Controller Model (AR) Database
  • 6. O/RM
  • 7. 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
  • 8. 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
  • 9. Get vs Post def index @products = Product.all end ! def create @product = Product.new(product_params) if @product.save redirect_to @product else render action: 'new' end end
  • 10. Active Record A single model cannot be appropriate for reporting, searching and transactional behaviour. Greg Young
  • 11. Read vs Write Reads and writes are two different concerns Reads are simpler Writes need logic
  • 12. KEEP CALM AND STOP THINKING IN CRUD
  • 13. Command Query Responsibility Segregation
  • 14. CQRS Presentation Layer Handler BL Repository Write DB Read DB Query Service Denormalizer
  • 15. CQRS Fully normalized Reads are easy Writes became easier SELECT fields FROM table (WHERE …)
  • 16. Object state id basket_id article_id quantity 1 4 8 1 2 3 8 3 3 3 6 1 4 4 5 1
  • 17. Thinking in events Every change is an event. add_item 1 add_item 2 remove_item 1 add_item 3 time
  • 18. 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.
  • 19. Event Sourcing Presentation Layer Bus Handler DMRepository Event store Denormalizer Query service Read DB Command Events
  • 20. Pros • Encapsulation • Separation of concern • Simple storage • Performance/Scaling • Simple testing • More information granularity • Easy integration with other services
  • 21. Cons • Complex for simple scenarios • Cost of infrastructure • Long-living objects needs time to be reconstructed • Tools needed (i.e. rebuild the state)
  • 22. http://antwonlee.com/
  • 23. Ingredients • Rails app (no ActiveRecord) • Redis (pub-sub) • Sequel for querying data • MongoDb (event store) • Sqlite (Read db) • Wisper (domain events)
  • 24. Domain Model Basket BasketItem Article * 1 • Fully encapsulated (no accessors) • Fully OOP • Events for communication • PORO
  • 25. show_me_the_code.rb
  • 26. include CommandExecutor ! def add_to_basket send_command AddToBasketCommand.new( {"basket_id" => 42, "article_id" => params[:id].to_i}) redirect_to products_url end Controller Bus Command POST /Products add_to_basket
  • 27. module CommandExecutor ! def send_command (command) class_name = command.class.name channel = class_name.sub(/Command/, '') @redis.publish channel, command.to_json end ! end send_command
  • 28. 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
  • 29. 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
  • 30. def raise_event(event, args) @uncommited_events << {name: event, args: args} send "on_#{event}", args end raise_event DM (Basket) Events
  • 31. def get_item (item_code) @items.select{|i| i.item_code == item_code}.try :first end ! def on_item_added (item) get_item(item[:item_code]).try(:increase_quantity) || @items << BasketItem.new(item) end on_item_added DM (Basket) Events
  • 32. def commit while event = uncommited_events.shift events_repository.store(id, event) send_event event end end commit DM (Basket) Event store
  • 33. Event Store
  • 34. 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
  • 35. Read-Db
  • 36. def index @products=db[:basket_view] end Controller Read DB Query GET /Products index
  • 37. 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)
  • 38. https://github.com/emadb/revents

×