Location transparency in Elixir by example
Elixir is a functional, concurrent, high-level general-purpose programming language that runs on the BEAM virtual machine.
BEAM is the virtual machine at the core of the Erlang Open Telecom Platform (OTP).
In Erlang send operation is location transparent — the recipient can be on any machine, and all the information needed to guide the message to the right location is encoded in the process identifier. Erlang guarantees that process identifiers are unique on the network, even across machines.
In this post, we will implement location transparency In Elixir.
First, let's create a new mix project
mhewedy@localhost:~/temp/elixir/tmp$ mix new simple_queue --sup
It will create a new Elixir (Mix) project with the following structure:
mhewedy@localhost:~/temp/elixir/tmp$ cd simple_queue
mhewedy@localhost:~/temp/elixir/tmp/simple_queue$ tree
.
├── lib
│ ├── simple_queue
│ │ └── application.ex
│ └── simple_queue.ex
├── mix.exs
├── README.md
└── test
├── simple_queue_test.exs
└── test_helper.exs
3 directories, 6 files
Now, let’s implement the module SimpleQueue (at lib/simple_queue.ex
) as a GenServer as follows:
defmodule SimpleQueue do
use GenServer
# Client API
def start_link(state \\ []) do
GenServer.start(__MODULE__, state, name: {:global, __MODULE__})
end
def queue() do
GenServer.call({:global, __MODULE__}, :queue)
end
def enqueue(value) do
GenServer.cast({:global, __MODULE__}, {:enqueue, value})
end
def dequeue() do
GenServer.call({:global, __MODULE__}, :dequeue)
end
# Server API
def init(state) do
{:ok, state}
end
def handle_call(:queue, _from, state) do
{:reply, state, state}
end
def handle_call(:dequeue, _from, [value | state]) do
{:reply, value, state}
end
def handle_call(:dequeue, _from, []) do
{:reply, nil, []}
end
def handle_cast({:enqueue, value}, state) do
{:noreply, state ++ [value]}
end
end
As you can see in the previous code, the tuple
{:global, __MODULE__}
gives a name to the Process as SimpleQueue and the:global
here used to register the process as a global process using the Erlang:global
module.
And let’s modify the file lib/simple_queue/application.ex
to add the SimpleQueue
GenServer to the Supervision tree of our application, Under the children list, add Our SimpleQueue as follows:
children = [
# Starts a worker by calling: SimpleQueue.Worker.start_link(arg)
# {SimpleQueue.Worker, arg}4
{SimpleQueue, []}
]
Now, let’s start two iex processes (to represent two nodes/machines)
As seen above, we name the first node as alex@localhost
and the second node as bob@localhost
Now, let’s establish a connection between the two nodes, we will use Node.connect
function as follows:
Now, let’s start manipulating the SimpleQueue process (that was started by the supervisor) on the alex@localhost
node:
Now, let’s check the queue on the bob
node:
Viola! We are able to query the SimpleQueue
process created on alex
node from bob
node!
As we have seen, regardless of the location the process was created at (alex
node in our example), we are able to interact with the process as if it is local to the bob
node.
Resources