to top

So why Elixir + Phoenix?

There are times when a project’s requirements can’t be handled by a language you are accustomed to. In our case, we needed an efficient way to handle real-time messaging with huge amounts of traffic. Obviously, Ruby is not really suited for this, and we started to look in other directions.

One of the most promising technologies on the horizon at that time was the Elixir programming language and Phoenix web framework. It was a risk in the sense that the ecosystem was not very mature in 2015. Nevertheless, we thought that the benefits would outweigh the risks.

So why Elixir + Phoenix?

The Elixir programming language and Phoenix web framework – the most promising technologies on the horizon.

So why Elixir and Phoenix?

Elixir is based on Erlang VM, but I won’t go into the nuts and bolts of its history or why it is awesome for async communication. Instead, I’d rather focus on Elixir, and most of what I will describe here holds true for Erlang as well.

Language differences between Ruby and Elixir

Concurrency – Elixir is built for concurrency. While it uses a concurrency model called an “actor model,” the Erlang VM actually implemented this even before the term was defined.

The idea is that you can create isolated processes that can communicate with one another using messages but can’t manipulate each other’s state.

The example module looks like this:

defmodule Example do

 def start_link do

   spawn(fn() -> loop() end)

 end

 def loop do

   receive do

     {:hello, pid} -> IO.inspect("Got hello from #{inspect pid}")

   end

   loop()

 end

end

And this is how we send a process message in IEx:

iex(1)> pid = Example.start_link()

#PID<0.120.0>

iex(2)> send pid, {:hello, self()}

"Got hello from "#PID<0.118.0>"

"#PID<0.118.0>"

{:hello, #PID<0.118.0>}

Doesn’t that sound like OOP? There is even a joke in the Erlang/Elixir community that these languages are closer to the definition than any other mainstream OOP language. So, is Elixir an object-oriented language? No, it is not. I’ll say more about that in a bit.

The benefits of message communication are that you know where everything is coming from and you have control over how the processes interact with each other. It is also much easier to visualize and debug than regular concurrency techniques such as locks and mutexes in a threading model.
On the other hand, Ruby is lagging behind on concurrency. While some Ruby implementations have thread-based concurrency, the MRI (the original Ruby written in C) has a GIL enabled, which prevents concurrent CPU tasks from happening. I’d have to say that Ruby does allow concurrent IO operations.

Immutability – Ruby is, by its nature, a mutable language, while Elixir is immutable. Why does this matter?

Here is an example of immutability in Elixir:

list =  %{"a" => "1", "b" => "2"} 

%{ "a" => a, "b" => b, } = list

Map.put(list, "a",  4)

a != list["a"]

This topic is somewhat related to concurrency, but I thought that it deserved its own section. Since all processes are isolated and messages are being sent by creating a completely new copy of the data, we don’t have to worry about corrupting or mutating the data on the other side of the system. Inside the process, the operations are synchronous, so the process execution order is much saner than, say, in NodeJS. This also has the additional benefit of an efficient garbage collector. Once the process has done its thing, it can release memory to the OS.

In Ruby (JRuby, Rubinius), we would need to deal with threads manipulating other threads’ data, and that is usually really hard to debug and resolve. Race conditions creep up on you really fast.

Functional language – Elixir is a functional language, as opposed to Ruby, which is an OO language. The code in Elixir is organized into modules, and each module includes a set of functions that are usually aimed at resolving a specific set of problems. The biggest relief while programming with Elixir is that you see what you get. You need to specify each argument that you want to receive, so there are no added complications like instance variables. This also helps avoid the problems mentioned above, where some other part of the system might manipulate the data in another random place.

Rails and Phoenix

Feels like home – The structure of a web app is quite similar on both platforms. This shouldn’t come as a surprise, since one of the biggest Phoenix contributors is a former Rails core team member, and most of the people involved in its development come from a Ruby background. You might find slight differences here and there, like views are called templates in Phoenix and Phoenix’s views act more like decorators, but it shouldn’t be such a difficult switch to make conceptually.

Real-time communication – While Rails has recently added Action Cable to its stack, it still has a long way to go to match Phoenix’s performance or elegance. Phoenix has the concept of channels, and each connection to a channel actually spawns a new process, which in turn won’t harm other connections in case of an error. Also, Phoenix provides a front-end library that gives you a nice abstraction over raw websocket connections.

Performance – There are many posts on the interwebs that describe various performance benchmarks, some of them comprehensive and some of them less so. From our experience, there was not a tool in our QA department arsenal that could overload the server. It was the machine that tested that gave up first, no matter the power.

Conclusion

After almost two years of development, we are very happy with the way Phoenix has progressed and with our experience with it so far. The community has a contagious sense of excitement, which is a huge boost in morale for us.

I still think Rails is a good choice for most web projects, but Phoenix in 2017 is definitely worth considering if your situation permits or requires real-time communication. Of course, these decisions depend on your own preferences.