IvanR

IvanR

Commanded + Domo to validate type/format of Commands and Events

It was an elegant “Eventsourcing and CQRS in Elixir” talk given by @vasspilka at ElixirConf EU 2021. It shows how to use the Commanded library by @slashdotdash to model a Bank domain with the Domain-Driven Design approach and make an event-driven system out of it.

Having the well-defined model with Command and Event structs, it still can be an issue to validate data coming from the User Interface and validate the consistency of casting commands and events in each other.

For example, the value of the bank account identifier should be both of binary type and have a specific format. That is different from other kinds of binary identifiers across the system.

One of the possible ways to solve the validation problems defined above is by using Domo library in Command and Event structs like that:

# defining the format precondition for value's type 
defmodule Bank do
  import Domo

  @type account_number() :: binary()
  precond account_number: &validate_account_number/1

  def validate_account_number(string) do
    if string =~ ~r/^[[:digit:]]{3}-[[:digit:]]{3}$/, do: :ok, else: {:error, "Account number should be of xxx-xxx, x = 0..9 format."}
  end
  ...
end

# using Domo in the event struct
defmodule Bank.Core.Commands.DepositMoney do
  use Domo, ensure_struct_defaults: true
  ...
  # automatically adds new_ok/1 and new!/1 constructors
end

# using constructor in the Accounts context
defmodule Bank.Core.Accounts do
  def deposit_money(acc_id, amount) do
    [account_id: acc_id, amount: amount]
    |> DepositMoney.new_ok()
    |> maybe_dispatch(returning: :execution_result)
  end

  defp maybe_dispatch({:ok, command}, opts), do: Bank.Core.Application.dispatch(command, opts)
  defp maybe_dispatch({:error, _message} = error, _opts), do: error
  ...
end

Then the following errors can be handled automatically:

iex(1)> Accounts.deposit_money(nil, 10)        
{:error,
 [
   account_id: "Invalid value nil for field :account_id of %Bank.Core.Commands.DepositMoney{}. Expected the value matching the <<_::_*8>> type."
 ]}

iex(2)> Accounts.deposit_money("100102", 500)        
{:error, 
  [
    account_id: "Account number should be of xxx-xxx, x = 0..9 format."
  ]}

iex(3)> Accounts.deposit_money("100-102", -12)
{:error,
 [
   amount: "Invalid value -12 for field :amount of %Bank.Core.Commands.DepositMoney{}. Expected the value matching the non_neg_integer() type."
 ]}

iex(4)> Accounts.deposit_money("100-102", 120)
{:ok, %Commanded.Commands.ExecutionResult{}} 

Domo makes it possible to associate a precondition (format validation) function with the given @type definition and adds new_ok/1 validating constructor and several others to a struct.

The consistency of commands and events is ensured with the call to the validating constructor.

See the complete Bank example from the @vasspilka’s talk seasoned with Domo on GitHub - IvanRublev/bank-commanded-domo · GitHub.

More details about Domo Domo v1.5.19 — Documentation

Most Liked

factoryd

factoryd

Nice talk!

I’ve been using Commanded for a few years now and have developed a small library to help with these (and other) issues as well.

It is based on Ecto embedded schemas as Ecto is pretty much ubiquitous in Elixir-land.

The library is called cqrs_tools and used in production on a few of my apps.

I saw this post and challenged myself to implement the project with cqrs_tools. So I forked the project mentioned in the OP and did it here.

The main strength of this library IMO is treating commands and queries as the smallest unit of execution while providing the tooling to take care of a common “lifecycle” for commands and queries; including input validation.

Commands need not be event-sourced but can be easily. They make no assumptions on how you execute them. They require a handle_dispatch callback to be implemented by the developer and that’s it.

This library also gives you a tool to build a “traditional” so-called Phoenix contexts via the BoundContext macro. Example here and here

I hope this is useful to someone and they can dig it. :slight_smile:

EDIT: There’s a couple livebooks in the repo to play around with as well.

vasspilka

vasspilka

Thank you so much!! I feel like I could have done a better job at presenting, hopefully I’ll improve my speaking next for next time :wink:

Domo looks awesome, I was actually looking for something like it for a while, I wonder how well it can work with typed_struct I use it a lot in micro_words.

Thank’s again for the kind words, I’ll play around and explore Domo in the weekend and give you some feedback. Cheers :slight_smile:

IvanR

IvanR

Thanks to Barbara Trojecka for the link to the video:

Where Next?

Popular in Discussions Top

Donovan
Hello everyone, I’m so glad to have discovered this awesome community. Thanks for creating it! This is my second post, and apologies for...
New
blackode
Elixir Upgrading is so Simple in Ubuntu and It worked for me Ubuntu 16.04 git clone https://github.com/elixir-lang/elixir.git cd elixir...
New
Crowdhailer
I’ve been hearing much about the new formatter and it’s something I have been keen to try. I find examples buy far the most illuminating...
248 19365 150
New
WildYorkies
It seems that the more I read, the more I find Elixir users speaking about all the ways that Elixir is not good for x, y, and z use cases...
New
Markusxmr
Since Drab has been developed for a while in the open, introducing the Liveview functionality in a way it happend appears to undermine th...
New
Nvim
Anybody knows a comprehensive comparison of Django and Phoenix, thanks for the help. Where are they similar? Where do they differ the m...
New
axelson
Decided against including more info in the title, but the gist is that Plataformatec sponsored projects will continue with the assets bei...
New
nunobernardes99
Hi there Elixir friends :vulcan_salute: In a recent task I was on, I needed to check in two dates which of them is the maximum and which...
New
mmmrrr
Just saw that dhh announced https://hotwire.dev/ Is it just me or is this essentially live view? :smiley: Although I like the “iFrame-e...
New
AstonJ
It’s been a while since we’ve had a thread like this, so what better way to kick off the year with :003: What does being an Elixir user ...
New

Other popular topics Top

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
jononomo
I am trying to figure out how Mix knows whether the environment is test, dev, or prod – where is this set? Thanks.
New
vonH
In asking this question I am more interested about the expressiveness of the language itself and less concerned about the availability of...
New
danschultzer
None of the current solutions worked well for me, so I went ahead and built a user management system from scratch. This project took far...
548 29703 241
New
dogweather
I wrote this comment on r/haskell, and it’s not popular there. :wink: But I think I’m on to something… Haskell reminds me of Java, and e...
New
jay1
Why is it that the mnesia database isn’t the most preferred database for use in Elixir/Phoenix?
New
Nvim
Anybody knows a comprehensive comparison of Django and Phoenix, thanks for the help. Where are they similar? Where do they differ the m...
New
albydarned
Hello all! I am typing this post from my new MacBook Pro with the M1 chip. I’m loving it so far, and will probably use it as my daily dr...
New
marius95
Hello everyone, I try to use an Javascript Event Handler in my root.html.leex file. Therefore I created a function in the app.js file: ...
New
vrod
I am using the Starship cross-shell prompt – it seems pretty nice, but I get some errors: [WARN] - (starship::utils): Executing command ...
New

We're in Beta

About us Mission Statement