chemist

chemist

CQRS/ES commanded questions

Hi, I am working with the commanded library and will love to hear some of your views.

  1. What is the difference between a saga and a process manager? Because I sense the process manager in commanded should be named a saga as it does route messages across multiple discrete contexts/aggregates.

  2. How should I write compensating actions using commanded proc mgr?

For example, let’s say I create a wallet where monies are to be drawn out of a customer’s account. This create_wallet command is encapsulated in a proc mgr which will then trigger a corresponding withdraw_account command. What if for some reason the account table is offline? Or for some reason, the transaction falls through like if a new withdrawal policy is enacted system-wide midway?

  1. Just wondering whats the advantage of the proc mgr in commanded apart from compensating actions and workflows as aggregates can emit multiple events from a single command? If i understood correctly, I dont see much difference between the two below:

A saga that listens for a WalletCreated event to trigger a WithdrawAccount command which emits a AccountWithdrawn event:

defmodule MyProj.Sagas.CreateWallet  

  def interested?(%WalletCreated{transfer_uuid: transfer_uuid}) 
  when not is_nil(transfer_uuid) do
    {:start!, transfer_uuid}
  end

  def interested?(%AccountWithdrawn{transfer_uuid: transfer_uuid}) when not is_nil(transfer_uuid) do
    {:continue!, transfer_uuid}
  end

  def handle(%CreateWallet{}, %WalletCreated{transfer_uuid: 
  transfer_uuid, wallet_id: wallet_id, funds_balance: funds_balance, 
  currency: currency}) do
    %WithdrawAccount{
      account_id: account_id,
      transfer_uuid: transfer_uuid,
      withdrawal: Money.to_decimal(funds_balance),
      currency: Cldr.validate_currency(currency) |> elem(1)
    }
  end

  def apply(%CreateWallet{} = saga, %WalletCreated{} = event) do
    %CreateWallet{
      saga
      | transfer_uuid: event.transfer_uuid,
        account_id: event.account_id,
        destination_wallet_id: event.wallet_id,
        funds: event.funds_balance,
        currency: event.currency,
        status: :withdraw_from_account
    }
  end

  def apply(%CreateWallet{} = saga, %WalletWithdrawn{}) do
    %CreateWallet
    {
      saga
      | status: :deposit_into_wallet
    }
  end

vs a single aggregate emitting two events WalletCreated and AccountWithdrawn which will also be projected and produce the same results as above

  def execute(%Wallet{wallet_id: nil}, %CreateWallet{wallet_id: 
  wallet_id, account_id: account_id, funds_balance: funds_balance, 
  currency: currency, transfer_uuid: transfer_uuid} = _create) do
    with validated_funds_balance <- {Money.new(currency, funds_balance)}
    do
      IO.inspect(transfer_uuid)
      %WalletCreated
      {
        wallet_id: wallet_id,
        account_id: account_id,
        funds_balance: validated_funds_balance,
        currency: currency,
        transfer_uuid: transfer_uuid
      }
      %AccountWithdrawn
      {
        account_id: account_id,
        transfer_uuid: transfer_uuid,
        withdrawal: Money.to_decimal(funds_balance),
        currency: Cldr.validate_currency(currency) |> elem(1)
      }
    else
      _ -> {:error, "The wallet cannot be created at this time.}
    end
  end

Many thanks.

Marked As Solved

slashdotdash

slashdotdash

What is the difference between a saga and a process manager?

A process manager is responsible for coordinating multiple aggregates. You can use a process manager to implement the Saga pattern. This is a way of implementing long-lived transactions (spanning minutes, hours, or days) that are written as a sequence of transactions that can be interleaved. All transactions in the sequence complete successfully, or compensating transactions are run to amend a partial execution (rollback on error). The error/3 callback function in a Commanded process manager is where you can handle errors and can execute any rollback commands as applicable.

Distributed Sagas: A Protocol for Coordinating Microservices by Caitie McCaffrey is a really good talk introducing the saga pattern.

In your example since both events are produced by a single aggregate there is no need for a process manager. Emitting two events from the aggregate would be the preferred approach.

Where Next?

Popular in Questions Top

ycv005
I have followed this StackOverflow post to install the specific version of Erlang. And When I am running mix ecto.setup then getting fol...
New
Werner
Hi, I’m using Ubuntu 18.04 and after updating to OTP-24.0 yesterday i have this warning when I run “mix local.hex”: 14:57:30.512 [warn] ...
New
fireproofsocks
I’m working on defining a simple Ecto schema for a table (in PostGres), but I don’t see where I can define a column as NOT NULL. Conside...
New
sacepums
Hey guys. I'm new to elixir and im really stocked about it. But I ran into a bit of problem - I need to convert a date sting, for examp...
New
minhajuddin
I have seen a lot of code which picks the first element from a list using Enum.at(0) instead of List.first. Is there a reason why people ...
New
vonH
When I run the Plug and I recompile I wind up having to use Ctrl C to quit iex and start again. Witht the help of rlwrap I can use the cu...
New
chrisalley
ExUnit now has describe blocks which is a welcome addition coming from RSpec. In the docs, it states that nested hierarchies of describe ...
New
WestKeys
Currently suffering from paralysis by [HTTP client] analysis. This is rather unusual in Elixirland as there tends to be consensus on the ...
New
Patoshizzle
After calling mix ecto.create I get this error: 17:00:32.162 [error] GenServer #PID&lt;0.412.0&gt; terminating ** (Postgrex.Error) FATAL...
New
Exadra37
Sometimes I want to check if the input into a function is not a blank string. My first approach: defmodule Example do def do_stuff(s...
New

Other popular topics Top

JakeBecker
TL;DR: I’ve just released an implementation of Microsoft’s IDE-independent Language Server Protocol for Elixir. It adds language support ...
1140 51847 244
New
lessless
I believe there are people here who are dealing with CSV files import on the daily basis, and since Excel is a really popular tool there ...
New
gshaw
What is the idiomatic way of matching for not nil in Elixir? E.g., First way: defp halt_if_not_signed_in(conn, signed_in_account) when...
New
malloryerik
Hi, this is for people who, like me, have had some friction using .html.heex templates in VSCode. The solution seems to be, in a hyphena...
New
KronicDeth
Elixir plugin for JetBrain’s IntelliJ Platform (including Rubymine) This is a plugin that adds support for Elixir to JetBrains IntelliJ...
289 35421 110
New
minhajuddin
I have seen a lot of code which picks the first element from a list using Enum.at(0) instead of List.first. Is there a reason why people ...
New
chensan
I have a User schema with a :from_id field set to type :string: defmodule TweetBot.Repo.Migrations.CreateUsers do use Ecto.Migration ...
New
fayddelight
I tried installing elixir 1.11.2 erlang 23.3.4 via asdf in my zsh shell. Enabled the versions locally and globally. When I list them ...
New
johnnyicon
Hi all, I've just started learning Elixir and Phoenix Framework, so please pardon my n00bness at this stage. I'm trying to use Postg...
New
ovidiubadita
Hey all, I discovered Elixir and I love it. I always wanted to learn a functional programming and I intended to go for Haskell, but afte...
New

We're in Beta

About us Mission Statement