from activerecord to eventsourcing

44
From ActiveRecord to Events Emanuele DelBono @emadb

Upload: emanuele-delbono

Post on 14-May-2015

4.900 views

Category:

Technology


0 download

DESCRIPTION

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)

TRANSCRIPT

Page 1: From ActiveRecord to EventSourcing

From ActiveRecord to Events

Emanuele DelBono @emadb

Page 2: From ActiveRecord to EventSourcing
Page 3: From ActiveRecord to EventSourcing

Customer

Address

Invoice

Items

Contacts

Role

Contract

Price

City

Page 4: From ActiveRecord to EventSourcing
Page 5: From ActiveRecord to EventSourcing
Page 6: From ActiveRecord to EventSourcing

@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.

Page 7: From ActiveRecord to EventSourcing

Lasagna architecture

Page 8: From ActiveRecord to EventSourcing

View

Controller

Model (AR)

Database

Page 9: From ActiveRecord to EventSourcing

O/RM

Page 10: From ActiveRecord to EventSourcing

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

Page 11: From ActiveRecord to EventSourcing

Active record1 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

Page 12: From ActiveRecord to EventSourcing

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

Page 13: From ActiveRecord to EventSourcing

Active Record

A single model cannot be appropriate for reporting, searching and transactional

behaviour.

Greg Young

Page 14: From ActiveRecord to EventSourcing

Read vs Write

Reads and writes are two different concerns

Reads are simpler

Writes need logic

Page 15: From ActiveRecord to EventSourcing
Page 16: From ActiveRecord to EventSourcing

KEEP CALM AND

STOP THINKING IN CRUD

Page 17: From ActiveRecord to EventSourcing

Command

Query

Responsibility

Segregation

Page 18: From ActiveRecord to EventSourcing

CQRS

Presentation Layer

Handler

BL Repository

Write DB Read DB

Query Service

Denormalizer

Page 19: From ActiveRecord to EventSourcing

CQRS

Fully normalized

Reads are easy

Writes became easier

SELECT fields FROM table (WHERE …)

Page 20: From ActiveRecord to EventSourcing

Object state

id basket_id article_id quantity

1 4 8 1

2 3 8 3

3 3 6 1

4 4 5 1

Page 21: From ActiveRecord to EventSourcing
Page 22: From ActiveRecord to EventSourcing

Thinking in events

Every change is an event.

add_item 1

add_item 2

remove_item 1

add_item 3

time

Page 23: From ActiveRecord to EventSourcing

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.

Page 24: From ActiveRecord to EventSourcing
Page 25: From ActiveRecord to EventSourcing

Event SourcingPresentation Layer

Bus

Handler

DMRepository

Event store Denormalizer

Query service

Read DB

Command

Events

Page 26: From ActiveRecord to EventSourcing

Pros• Encapsulation

• Separation of concern

• Simple storage

• Performance/Scaling

• Simple testing

• More information granularity

• Easy integration with other services

Page 27: From ActiveRecord to EventSourcing

Cons

• Complex for simple scenarios

• Cost of infrastructure

• Long-living objects needs time to be reconstructed

• Tools needed (i.e. rebuild the state)

Page 28: From ActiveRecord to EventSourcing

http://antwonlee.com/

Page 29: From ActiveRecord to EventSourcing

Ingredients

• Rails app (no ActiveRecord)

• Redis (pub-sub)

• Sequel for querying data

• MongoDb (event store)

• Sqlite (Read db)

• Wisper (domain events)

Page 30: From ActiveRecord to EventSourcing

Domain ModelBasket

BasketItem

Article

*

1

• Fully encapsulated (no accessors)

• Fully OOP

• Events for communication

• PORO

Page 31: From ActiveRecord to EventSourcing

show_me_the_code.rb

Page 32: From ActiveRecord to EventSourcing

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

BusCommand

POST /Products

add_to_basket

Page 33: From ActiveRecord to EventSourcing

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

Page 34: From ActiveRecord to EventSourcing

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

HandlerCommand

handler

Page 35: From ActiveRecord to EventSourcing

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

Page 36: From ActiveRecord to EventSourcing

def raise_event(event, args) @uncommited_events << {name: event, args: args} send "on_#{event}", args end

raise_event

DM (Basket)

Events

Page 37: From ActiveRecord to EventSourcing

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

Page 38: From ActiveRecord to EventSourcing

def commit while event = uncommited_events.shift events_repository.store(id, event) send_event event end end

commitDM (Basket)

Event store

Page 39: From ActiveRecord to EventSourcing

Event Store

Page 40: From ActiveRecord to EventSourcing

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

Page 41: From ActiveRecord to EventSourcing

Read-Db

Page 42: From ActiveRecord to EventSourcing

def index @products=db[:basket_view] end

Controller

Read DBQuery

GET /Products

index

Page 43: From ActiveRecord to EventSourcing

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)

Page 44: From ActiveRecord to EventSourcing

https://github.com/emadb/revents