Hi, I am working with the commanded
library and will love to hear some of your views.
-
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.
-
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?
- 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.