eventsourced icon indicating copy to clipboard operation
eventsourced copied to clipboard

Define valid aggregate states and applicable commands using an finite state machine

Open slashdotdash opened this issue 9 years ago • 0 comments
trafficstars

An aggregate usually follows a lifecycle where it transitions between a finite set of states in response to received commands. Instead of manually adding a state field and verifying the aggregate is in a valid state when handling a command it could use an FSM.

defmodule BankAccount do
  use EventSourced.AggregateRoot, 
    fields: [account_number: nil, balance: nil],
    initial_state: :unopened,

  defmodule Events do
    defmodule BankAccountOpened do
      defstruct account_number: nil, initial_balance: nil
    end

    defmodule MoneyDeposited do
      defstruct amount: nil, balance: nil
    end

    defmodule MoneyWithdrawn do
      defstruct amount: nil, balance: nil
    end
  end

  alias Events.{BankAccountOpened,MoneyDeposited,MoneyWithdrawn}

  defstate unopened do
    defcommand open_account(account_number, initial_balance), account do
      account
      |> update(%BankAccountOpened{account_number: account_number, initial_balance: initial_balance})
    end
  end

  defstate open do
    defcommand deposit(amount), account when amount > 0 do
      balance = account.state.balance + amount

      account
      |> update(%MoneyDeposited{amount: amount, balance: balance})
    end
  end

  # ...  

  # event handling callbacks that mutate state

  def apply(%BankAccount.State{} = state, %BankAccountOpened{} = account_opened) do
    %BankAccount.State{state |
      account_number: account_opened.account_number,
      balance: account_opened.initial_balance,
      state: :open,
    }
  end

  def apply(%BankAccount.State{} = state, %MoneyDeposited{} = money_deposited) do
    %BankAccount.State{state |
      balance: money_deposited.balance
    }
  end

  def apply(%BankAccount.State{} = state, %MoneyWithdrawn{} = money_withdrawn) do
    %BankAccount.State{state |
      balance: money_withdrawn.balance
    }
  end
end

Saša Jurić's fsm library would be a good fit.

slashdotdash avatar Nov 14 '16 17:11 slashdotdash