Upgrade to Pro — share decks privately, control downloads, hide ads and more …

The CQRS diet

The CQRS diet

Presented at Conferencia Rails 2010.

You probably follow the motto “skinny controller, fat model”, which is a good thing. Skinny controllers are definitely the way to go but, as your domain logic grows, it’s very easy to end up with a model overweight problem, which is not so good. The issue is that your ActiveRecord objects have to deal with too many different responsibilities: validation, business rules, awareness of its own persistence, filtering…

Command-Query Responsibility Segregation (CQRS) is an architectural pattern that promises to improve the maintainability, performance and scalability of your applications by helping to separate concerns in your system. This pattern, along with Event Sourcing and other Domain-Driven Design ideas, is gaining increased popularity, particularly among developers building solutions for rich / complex domains.

This talk will introduce these concepts and show different ways we can (re)architect our Rails application in order to get their benefits. From a simple implementation using just the “tools in the room” to a implementation “in all its full glory” using NoSQL data stores and messaging queues.

All those who have a Rails projects complex enough to abuse of model callbacks, de-normalize the database or use presenter objects, may get benefits in terms of simplicity and maintainability from implementing the ideas I’ll present in this talk.

Luismi Cavallé

November 07, 2010
Tweet

More Decks by Luismi Cavallé

Other Decks in Programming

Transcript

  1. ★ the CQRS diet ★ Are you happy with your

    Rails app? ★ Models clean and straightforward ★ No need to deal with complex domain logic ★ No scalability or performance issues ★ No maintainability problems
  2. ★ the CQRS diet ★ Some smells… ★ Denormalized database

    ★ Complex caching ★ Overweighted models ★ Presenter pattern ★ Increasingly complex infrastructure ★ Considering NoSQL
  3. ★ the CQRS diet ★ THE SINGLE RESPONSIBILITY PRINCIPLE should

    never be more one reason class change There than for a to “ ”
  4. ★ the CQRS diet ★ ★ Validation ★ Domain Logic

    methods & callbacks ★ Structure associations ★ Filtering scopes ★ Presentation helper methods ★ Persistence Model Responsibilities
  5. ★ the CQRS diet ★ DOMAIN DATA MODEL READ DATA

    MODEL ★ Validation ★ Business Logic ★ Filtering ★ Data presentation CONCERNS ATRIBUTTES NORMALIZED PERSISTENCE IGNORANT TRANSACTIONAL CONSISTENT OBJECT ACID DATA ORIENTED DENORMALIZED EVENTUALLY CONSISTENT INDEXABLE BASE
  6. ★ the CQRS diet ★ Event Handler COMMANDS QUERIES EVENTS

    UPDATES Project List Project Details DOMAIN MODEL READ MODEL Task Project
  7. ★ the CQRS diet ★ class Project < ActiveRecord::Base #

    t.string :name # t.text :notes # t.datetime :deadline # t.boolean :active has_many :tasks scope :active, where(:active => true) validates_presence_of :name def status active? ? "Active" : "Inactive" end end INITIAL MODEL
  8. ★ the CQRS diet ★ CONTROLLER class ProjectsController < ApplicationController

    def index @projects = end def create project = Project.create!(params[:project]) redirect_to(project, :notice => 'Project created.') end def destroy project = Project.find(params[:id]) project.destroy redirect_to(projects_url) end end Project.active QUERY COMMAND COMMAND
  9. ★ the CQRS diet ★ CONTROLLER class ProjectsController < ApplicationController

    def index @projects = end def create project = Project.create!(params[:project]) redirect_to(project, :notice => 'Project created.') end def destroy project = Project.find(params[:id]) project.destroy redirect_to(projects_url) end end ProjectReport.active QUERY COMMAND COMMAND
  10. ★ the CQRS diet ★ class ProjectReport < ActiveRecord::Base #

    t.string :name # t.string :status # => “Active” or “Inactive” # t.integer :project_id scope :active, where(:status => "Active") end READ MODEL
  11. ★ the CQRS diet ★ class Project < ActiveRecord::Base DOMAIN

    MODEL scope :active, where(:active => true) validates_presence_of :name def status active? ? "Active" : "Inactive" end has_many :tasks end # t.string :name # t.datetime :deadline # t.boolean :active # t.text :notes
  12. ★ the CQRS diet ★ class Project < ActiveRecord::Base DOMAIN

    MODEL scope :active, where(:active => true) validates_presence_of :name def status active? ? "Active" : "Inactive" end has_many :tasks end # t.string :name # t.datetime :deadline # t.boolean :active # t.text :notes
  13. ★ the CQRS diet ★ class Project < ActiveRecord::Base validates_presence_of

    :name def status active? ? "Active" : "Inactive" end has_many :tasks end DOMAIN MODEL # t.string :name # t.datetime :deadline # t.boolean :active # t.text :notes
  14. ★ the CQRS diet ★ class Project < ActiveRecord::Base validates_presence_of

    :name def status active? ? "Active" : "Inactive" end has_many :tasks end DOMAIN MODEL # t.string :name # t.datetime :deadline # t.boolean :active # t.text :notes
  15. ★ the CQRS diet ★ class Project < ActiveRecord::Base validates_presence_of

    :name # t.string :name has_many :tasks end # t.datetime :deadline # t.boolean :active DOMAIN MODEL # t.text :notes
  16. ★ the CQRS diet ★ class ProjectReport < ActiveRecord::Base #

    t.string :name # t.string :status # t.integer :project_id scope :active, where(:status => "active") end DOMAIN MODEL READ MODEL class Project < ActiveRecord::Base # t.string :name # t.string :status # t.datetime :deadline # t.boolean :active has_many :tasks validates_presence_of :name end
  17. ★ the CQRS diet ★ OBSERVER class ProjectObserver < ActiveRecord::Observer

    def after_save(project) report = ProjectReport.find_or_create_by_project_id(project.id) report.update_attributes! :name => project.name, :status => project.active ? "Active" : "Inactive" end def after_destroy(project) ProjectReport.destroy_all(:project_id => project.id) end end
  18. ★ the CQRS diet ★ class ProjectReport < ActiveRecord::Base #

    t.string :name # t.string :status # t.integer :project_id scope :active, where(:status => "active") end DOMAIN MODEL READ MODEL class Project < ActiveRecord::Base # t.string :name # t.string :status # t.datetime :deadline # t.boolean :active has_many :tasks validates_presence_of :name end class ProjectObserver < ActiveRecord::Observer def after_save(project) report = ProjectReport.find_or_create_by_project_id(project.id) report.update_attributes! :name => project.name, :status => project.active ? "Active" : "Inactive" end def after_destroy(project) ProjectReport.destroy_all(:project_id => project.id) end end OBSERVER
  19. ★ the CQRS diet ★ Similar, real-world example* ★ MongoDB

    for the Read Model (Presenters / Silos) ★ Asynchronous Observers with RabbitMQ (Silovators) Denormalizing Your Rails Application by Daniel Lucraft & Matt Wynne Scottish Ruby Conference 2010 *
  20. ★ the CQRS diet ★ Why events are key? ★

    Events keep models consistent ★ Events keep models loosely coupled ★ Events keep track of all changes
  21. ★ the CQRS diet ★ FEATURE feature "Deposit cash", %q{

    In order to have money to lend to other clients As a banker I want clients to deposit cash into their accounts }
  22. ★ the CQRS diet ★ class DepositsController < ApplicationController #

    POST /accounts/23/deposits def create account = Account.find(params[:account_id]) account.deposit(params[:deposit][:amount]) redirect_to account end end CONTROLLER
  23. ★ the CQRS diet ★ DOMAIN MODEL class Account include

    AggregateRoot def initialize @balance = 0 end def deposit(amount) # Do some validation new_balance = @balance + amount publish :deposit_made, :amount => amount, :new_balance => new_balance, :account_uid => uid end def on_deposit_made(event) @balance = event.data[:new_balance] end
  24. ★ the CQRS diet ★ THE MAGIC def publish(name, attributes)

    event = Event.new(:name => name, :data => attributes) do_apply event event.aggregate_uid = uid published_events << event end def do_apply(event) method_name = "on_#{event.name}" method(method_name).call(event) end
  25. ★ the CQRS diet ★ THE MAGIC def build_from(events) object

    = self.new events.each do |event| object.send :do_apply, event end object end def find(klass, uid) events = Event.find(:aggregate_uid => uid) klass.build_from(events) end
  26. ★ the CQRS diet ★ DOMAIN MODEL class Account include

    AggregateRoot def initialize @balance = 0 end def deposit(amount) # Do some validation new_balance = @balance + amount publish :deposit_made, :amount => amount, :new_balance => new_balance, :account_uid => uid end def on_deposit_made(event) @balance = event.data[:new_balance] end
  27. ★ the CQRS diet ★ DOMAIN MODEL class Account include

    AggregateRoot def initialize @balance = 0 end def deposit(amount) # Do some validation new_balance = @balance + amount publish :deposit_made, :amount => amount, :new_balance => new_balance, :account_uid => uid end def on_deposit_made(event) @balance = event.data[:new_balance] end
  28. ★ the CQRS diet ★ DOMAIN MODEL class Account include

    AggregateRoot def initialize @balance = 0 end def deposit(amount) # Do some validation new_balance = @balance + amount publish :deposit_made, :amount => amount, :new_balance => new_balance, :account_uid => uid end def on_deposit_made(event) @balance = event.data[:new_balance] end
  29. ★ the CQRS diet ★ DOMAIN MODEL class Account include

    AggregateRoot def initialize @balance = 0 end def deposit(amount) # Do some validation new_balance = @balance + amount publish :deposit_made, :amount => amount, :new_balance => new_balance, :account_uid => uid end def on_deposit_made(event) @balance = event.data[:new_balance] end
  30. ★ the CQRS diet ★ ★Validation ★Domain Logic methods &

    callbacks ★Structure associations ★Filtering scopes ★Presentation helper methods
  31. ★ the CQRS diet ★ There will be (some) dragons

    ★ More complex architecture than Rails’ MVC ★ More “moving parts” in the infrastructure ★ Distributed and asynchronous world is chaotic ★ Problems that used to be trivial no longer are
  32. ★ the CQRS diet ★ You need a good reason

    ★ Complex domain logic ★ Complex existent infrastructure ★ Complex scalability needs
  33. ★ the CQRS diet ★ Separating reads & writes is

    not new ★ Databases ★ HTTP ★ Infrastructure level ★ Performance or Scalability
  34. ★ the CQRS diet ★ Separating by function is not

    new ★ Big systems (e.g. Amazon, Ebay) partition its functionality at application level ★ But again the motivation is scalability
  35. ★ the CQRS diet ★ Performance and scalability as a

    side effect VERY NICE Benefits in terms of maintainbility