Implementing CQRS on top of OTP
(in Elixir)

Hubert Łępicki

Elixir Warsaw 2016-06-09

CQRS?

  • Command Query Responsibility Segregation 
  • design pattern
  • architectural pattern
  • Greg Young

Achtung!

You probably don't need to do CQRS

...at least not for whole application.

We have implemented CQRS in Elixir

and I'll show you how

Commands & Queries

  • Command - introduce change of state
  • Query - read state
  • Commands & Queries are API of your application

CQRS & DDD

  • Event Sourcing
  • Domain Model
  • Ubiquitous Language
  • Bounded Contexts
  • Aggregates

Building blocks

  • Commands & Queries
  • Streams of Events
  • Aggregates
  • Event Handlers
  • Read Model

Life cycle of a Command

  • Arrives from the user (from the UI/API etc.)
  • It's payload is validated
  • It's aggregate is initialized (unless already present)
  • Is being routed to aggregate
  • Business logic is validated
  • One or more Events are appended to aggregate's events stream

Life cycle of a Query

  • Arrives from the user (from the UI/API etc.)
  • It's payload is validated
  • Queries Read Model
  • Assembles response

Life cycle of an Aggregate

  • Is initialized with stream of past events
  • Restores it's current state
  • Waits for command
  • Processes the command (generates events)
  • Persists events
  • Handles the events (updating internal state, read model, notifying external systems)

Events are the source of truth

Events populate aggregate's state

Command's business logic runs within aggregate

New events are appended to the stream and update state

Aggregates are pretty important!

Aggregate in CQRS

  • defines bounded context
  • provides application-level transactions
  • contains current state of the system

An example!

User's wallets system

Our aggregates

  • Wallet for User #1
  • Wallet for User #2
  • Wallet for User #3

Wallet's state

  • label i.e. "Main wallet"
  • balance i.e. 100
  • blocked funds [{txId, amount}, ...]

Commands

  • %RenameWallet{name: "Savings"}
  • %TopUp{amount: 100}
  • %AuthorizeFunds{amount: 10, txId: 123}
  • %CaptureFunds{amount: 8, txId: 123}

Wallet Events

  • {WalletRenamed, %{name: "Savings"}}
  • {WalletToppedUp, %{from: 0, to: 100}}
  • {FundsAuthorized, %{amount: 10, txId: 10}}
  • {FundsCaptured, %{amount: 8, txId: 10}}

Aggregate the OTP way

GenServer

Supervisor for Aggregates

  • Dynamically adds/removes children
  • :simple_one_for_one
  • Registers under aggregate type (i.e. Wallet)
defmodule CqrsExample do                                                                                                                                      
  use Application                                                                                                                                             
                                                                                                                                                              
  def start(_type, _args) do                                                                                                                                  
    import Supervisor.Spec, warn: false                                                                                                                       
                                                                                                                                                              
    children = [                                                                                                                                              
      supervisor(AggregateSupervisor, [[name: Wallets]])                                                                                                       
      supervisor(AggregateSupervisor, [[name: Accounts]])                                                                                                      
      supervisor(AggregateSupervisor, [[name: Products]])                                                                                                      
      supervisor(AggregateSupervisor, [[name: Services]])                                                                                                      
    ]                                                                                                                                                         
                                                                                                                                                              
    opts = [strategy: :one_for_one, name: CqrsExample.Supervisor]                                                                                             
    Supervisor.start_link(children, opts)                                                                                                                     
  end                                                                                                                                                         
end  
defmodule AggregateSupervisor do                                                                                                                              
  def start_link([name: aggregate_name]) do                                                                                                                   
    import Supervisor.Spec                                                                                                                                    
                                                                                                                                                              
    children = [                                                                                                                                              
      worker(aggregate_for_name(aggregate_name), [])                                                                                                          
    ]                                                                                                                                                         
                                                                                                                                                              
    Supervisor.start_link(
      children,
      [strategy: :simple_one_for_one, name: aggregate_name]
    )                                                                    
  end                                                                                                                                                         
                                                                                                                                                              
  def get_aggregate(name, id) do                                                                                                                              
    ...                                                                                                                                                       
  end                                                                                                                                                         
                                                                                                                                                              
  defp aggregate_for_name(name) do                                                                                                                            
    ...                                                                                                                                                       
  end                                                                                                                                                         
end

CQRS does not require objects

CQRS is all about the proper handling of the state

Make your aggregates GenServers

Don't choose CQRS because you think it's cool

Moar resources

  • https://vimeo.com/97318824
  • https://github.com/bryanhunter/cqrs-with-erlang/tree/ndc-oslo
  • http://jfcloutier.github.io/jekyll/update/2015/11/04/cqrs_elixir_phoenix.html

Thank you!

Questions?

Made with Slides.com