Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.
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 l...
Active record
1 table => 1 class
Too coupled with the database structure
No SRP
Database first
class User < ActiveRecord::B...
Get vs Post
def index
@products = Product.all
end
!
def create
@product = Product.new(product_params)
if @product.save
red...
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
KEEP CALM
AND
STOP THINKING
IN CRUD
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 ...
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 gr...
Cons
• Complex for simple scenarios
• Cost of infrastructure
• Long-living objects needs time to be reconstructed
• Tools ...
http://antwonlee.com/
Ingredients
• Rails app (no ActiveRecord)
• Redis (pub-sub)
• Sequel for querying data
• MongoDb (event store)
• Sqlite (R...
Domain Model
Basket
BasketItem
Article
*
1
• Fully encapsulated (no accessors)
• Fully OOP
• Events for communication
• PO...
show_me_the_code.rb
include CommandExecutor
!
def add_to_basket
send_command AddToBasketCommand.new(
{"basket_id" => 42, "article_id" => param...
module CommandExecutor
!
def send_command (command)
class_name = command.class.name
channel = class_name.sub(/Command/, ''...
def consume(data)
basket = repository.get_basket(data["basket_id"])
article = repository.get_article(data["article_id"])
b...
class Basket
include AggregateRootHelper
!
def add_item (item)
raise_event :item_added, {
basket_id: id,
item_code: item.c...
def raise_event(event, args)
@uncommited_events << {name: event, args: args}
send "on_#{event}", args
end
raise_event
DM (...
def get_item (item_code)
@items.select{|i| i.item_code == item_code}.try :first
end
!
def on_item_added (item)
get_item(it...
def commit
while event = uncommited_events.shift
events_repository.store(id, event)
send_event event
end
end
commit
DM (Ba...
Event Store
def item_added(data)
db = Sequel.sqlite(AppSettings.sql_connection)
article = db[:products_view].where(code:
data[:item_co...
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 usef...
https://github.com/emadb/revents
From ActiveRecord to EventSourcing
From ActiveRecord to EventSourcing
From ActiveRecord to EventSourcing
From ActiveRecord to EventSourcing
From ActiveRecord to EventSourcing
From ActiveRecord to EventSourcing
Upcoming SlideShare
Loading in …5
×

From ActiveRecord to EventSourcing

8,963 views

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 presentation was part of Wroclove_rb 2014 conference in Wraclow (PL)

Published in: Technology
  • Be the first to comment

From ActiveRecord to EventSourcing

  1. 1. From ActiveRecord to Events Emanuele DelBono @emadb
  2. 2. Customer Address Invoice Items Contacts Role Contract Price City
  3. 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. 4. Lasagna architecture
  5. 5. View Controller Model (AR) Database
  6. 6. O/RM
  7. 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. 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. 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. 10. Active Record A single model cannot be appropriate for reporting, searching and transactional behaviour. Greg Young
  11. 11. Read vs Write Reads and writes are two different concerns Reads are simpler Writes need logic
  12. 12. KEEP CALM AND STOP THINKING IN CRUD
  13. 13. Command Query Responsibility Segregation
  14. 14. CQRS Presentation Layer Handler BL Repository Write DB Read DB Query Service Denormalizer
  15. 15. CQRS Fully normalized Reads are easy Writes became easier SELECT fields FROM table (WHERE …)
  16. 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. 17. Thinking in events Every change is an event. add_item 1 add_item 2 remove_item 1 add_item 3 time
  18. 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. 19. Event Sourcing Presentation Layer Bus Handler DMRepository Event store Denormalizer Query service Read DB Command Events
  20. 20. Pros • Encapsulation • Separation of concern • Simple storage • Performance/Scaling • Simple testing • More information granularity • Easy integration with other services
  21. 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. 22. http://antwonlee.com/
  23. 23. Ingredients • Rails app (no ActiveRecord) • Redis (pub-sub) • Sequel for querying data • MongoDb (event store) • Sqlite (Read db) • Wisper (domain events)
  24. 24. Domain Model Basket BasketItem Article * 1 • Fully encapsulated (no accessors) • Fully OOP • Events for communication • PORO
  25. 25. show_me_the_code.rb
  26. 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. 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. 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. 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. 30. def raise_event(event, args) @uncommited_events << {name: event, args: args} send "on_#{event}", args end raise_event DM (Basket) Events
  31. 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. 32. def commit while event = uncommited_events.shift events_repository.store(id, event) send_event event end end commit DM (Basket) Event store
  33. 33. Event Store
  34. 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. 35. Read-Db
  36. 36. def index @products=db[:basket_view] end Controller Read DB Query GET /Products index
  37. 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. 38. https://github.com/emadb/revents

×