Communication and Managing State in Elixir processes (using an example of BankAccount)

Processes are the basic building blocks of Elixir programming.

In Elixir, all code runs inside processes. Processes are isolated from each other, run concurrent to one another and communicate via message passing. Processes are not only the basis for concurrency in Elixir, but they also provide the means for building distributed and fault-tolerant programs.
Elixir Docs

However, I couldn't grasp the potential of processes until I solved this problem from the Exercism website.

Simulate a bank account supporting opening/closing, withdrawals, and deposits of money. Watch out for concurrent transactions!

Also the problem description literally said create an account that can be accessed from multiple threads/processes (terminology depends on your programming language). πŸ˜…

Primal Instincts

Now I am a web developer. And whenever I hear that something needs to be persisted, my first cry is to use a database.

But hey, this was a simple problem. I am sure they did not want me to open a DB connection.

I did not have too much idea of processes. So I dived into "Elixir in Action" book and noted the below points.

In Elixir, it’s common to create long-running processes that can respond to various messages. Such processes can keep their internal state, which other processes can query or even manipulate.

In this sense, stateful server processes resemble objects. They maintain state and can interact with other processes via messages. But a process is concurrent, so multiple server processes may run in parallel.

Server and Clients

Aha, so the challenge here was to implement the "Server and Clients" pattern using threads and processes. And not the MVC pattern. πŸ˜…

These were my notes from Evernote when I was finally able to crack the puzzle πŸ‘‡β˜ΊοΈ

Account is a process with some state(balance)

And then there are other processes which interact with this process.

The rest was easy.

Passing messages

Elixir uses the mailbox pattern to communicate between two processes.

Just like you send and receive an email in your inbox, every process can send a message to an inbox(different process or even it's own) using the send function.

Once the message is in the inbox, it waits for its owner to choose to receive it using the receive function.

As you can see below, the client sends a message to account server process. And the account server process receives a message and handles it.

# Client
def find_balance(account) do
    send account, { :balance }
end

# Server
def account(balance) do
    receive do
      { :balance } -> 
        IO.puts("Balance - #{balance}")
        account(balance)
    end
end

Hold state

Here endless tail recursion is used to persist the value of balance for the lifetime of a process.

As you can see when the server receives a message add, it changes the state accordingly.

# Server
defmodule Procs do

  def account(balance) do
    receive do
      { :balance, caller } ->
        send(caller, {:balance, balance})
        account(balance)
      { :add, amount } ->
        account(balance+amount)
      msg ->
        IO.puts "Message not recognized - #{msg}"
        account(balance)
    end
  end

end
Show Comments