So far, the exercism.io problems were fun to solve, but were contained within single process domain. The Bank Account problem opens up a new dimension 🙂

The task is rather simple: create a simplistic bank account manager that can open or close a bank account, query it’s balance and deposit to / redraw from it. The catch is, it needs to support access from multiple processes. Finally some distributed action!

The main issues here are:

how to communicate with other processes

how to keep state in sync

There are a number of techniques at our disposal, like ETS (Erlang Term Storage), GenServer or we can wire our own as described in docs about state in processes. The main thing to understand is that we actually only need message passing between processes to complete this task, with a little help from the Agent (sorry Neo :-)).

What we want to do is:

spawn a new Agent

remember it’s pid

perform all account management operations using that pid

stop Agent when done

The Agent’s pid serves as our account number for this simple exercise.

The simplest solution could look like this:

defmodule BankAccount do @opaque account :: pid @spec open_bank() :: account def open_bank() do Agent.start_link(fn -> 0 end) |> elem(1) end @spec close_bank(account) :: none def close_bank(account) do Agent.stop(account) end @spec balance(account) :: integer def balance(account) do Agent.get(account, &(&1)) end @spec update(account, integer) :: any def update(account, amount) do Agent.update(account, &(&1 + amount)) end end

What goes on is, when we open the bank account, Agent is started and linked to current process and we just need to store it’s pid, that acts as our account number, for future account operations.

Spawning an Agent requires only a single function, one that initializes the state. Agent then exposes get/3 and update/3 which can be used to change the state accordingly. All those state manipulating functions expect earlier stored pid, so they can access the related Agent, plus a function that operates on the state.

Very very simple and straight forward. It hides the underlying stuff like processes, message passing and message receiving in a nice manner and really allows us to concentrate on the business side of things. Much appreciated, and kind of justifies the hipe 🙂

And testing this isn’t much of a problem either!

setup do account = BankAccount.open_bank() { :ok, [ account: account ] } end test "incrementing balance from another process then checking it from test process", context do assert BankAccount.balance(context[:account]) == 0 this = self() spawn(fn -> BankAccount.update(context[:account], 20) send(this, :continue) end) receive do :continue -> :ok after 1000 -> flunk("Timeout") end assert BankAccount.balance(context[:account]) == 20 end

Basically, it just spawns a new process that makes a deposit to our bank account and then notifies of job completion (line 11 in the above code). The main test process is set to receive a message of type :continue (line 14) after which it can verify that the deposit has been made indeed.

This message passing between main test process and spawned one is the key to all distributed computing in Elixir / Erlang. Even without Agent’s help, it looks and works quite nicely.

The entire code can be found at Github.