Elixir Note - Concurrency

For example, we have a simple module to fetch the price of a given ticker symbol (or stock symbol) from API and parse the results.

defmodule Worker do
  def price_of(ticker) do
    call_api(location) |> parse_data
  end
  
  defp call_api(ticker) do
    # function to call api
  end

  defp parse_data do
  end
end

Now we can start making our code concurrent, with two approaches:

1. Use Elixir primitive process:

defmodule Worker do
  def loop do
    receive do
      {sender_pid, ticker} ->
        send(sender_pid,  {:ok, price_of(ticker)})
      _ ->
        IO.puts "don't know how to process this message"
    end
  end
  
  def price_of(ticker) do
  end
  ## Helper Functions
end

We use built-in spawn function to create a process, send/2 to send the process we created. Finally, to get back responses from the shell session - use flush/0 function:

> pid = spawn(Worker, :loop, [])
> send(pid, {self, "AAPL"})
# {PID<0.343.0>, "AAPL"}
> flash
# {:ok, "AAPL: 140,2"}

2. Use OTP GenServer

defmodule Worker do
  use GenServer
  ## Client API
  def start_link(opts \\ []) do
    GenServer.start_link(__MODULE__, :ok, opts)
  end
 
  def get_price(pid, ticker) do
    GenServer.call(pid, {:ticker, ticker})
  end
 
  ## Server Callbacks
  def init(:ok) do
    {:ok, %{}}
  end
 
  def handle_call({:location, ticker}, _from, stats) do
    case price_of(ticker) do
      {:ok, result} -> {:reply, result, stats}
       _ -> {:reply, :error, stats}
    end
  end
 
  ## Helper Functions
end

Using GenServer behavior is more simple and straightforward. We don't need to walk through some steps: spawn process -> send and receive message -> flush results.

> {:ok, pid} = Worker.start_link
> Worker.get_price(pid, "AAPL")
# "140,2"

Note about GenServer :

GenServer module calls Callback module
GenServer.start_link Worker.init/1
GenServer.call/3 Worker.handle_call/3
GenServer.cast/2 Worker.handle_cast/2
2