At Carbon Five, we’ve been getting excited about Elixir and Phoenix – with its promise of the productivity of Ruby and Rails without the performance penalty. We’ve used it for a few of our internal projects with great success, but we’re always wary of any new technology’s hype cycle. We wanted a bit more experience working with Elixir on a real project.
We recently had the opportunity to do so. The project was an iOS application that made heavy use of the device’s location services; users can share where they are and what they’re doing. We needed a backend for an API, and to keep all connected clients up-to-date. From the start, Phoenix and its channels seemed like a great fit. Today, we’d like to share some of what we learned.
We were immediately productive
Coming from Ruby, we were immediately comfortable with Elixir’s syntax. The Phoenix’s MVC framework gave us a familiar structure for our code. Elixir’s tools, like
mix, were invaluable. And we could even leverage some of our usual services, like CircleCI and Heroku. These similarities helped us be productive relatively quickly – even within our first week.
There’s a caveat to this, however. Elixir’s similarity to Ruby is deceptive. Beneath the facade it’s truly a very different beast. As time progressed, we noticed this more and more. We had brought some of our Ruby baggage along with us, and we weren’t using the language the way it wanted to be used. We still hadn’t gotten the hang of some of Elixir’s unique features, like the pipe operator and pattern matching, and we used them excessively or not enough. Thankfully, a solid test suite (see below) let us refactor easily.
The libraries are mostly there
Ruby’s vast ecosystem is one of the keys to Rails’ productivity. We knew that Elixir and Erlang wouldn’t have everything that we could have reached for in Ruby’s open-source community. But we were pleasantly surprised with what we did find.
In addition to Phoenix and Ecto, we used a variety of other libraries contributed by the community: guardian for authentication, arc for file uploads, geo for GIS, and ex_aws for Amazon services. We found that, for the most part, these libraries stayed out of our way and let us focus on building our product. There was one small exception – we used Amazon’s SNS to send push notifications to devices. It’s somewhat arcane API authentication didn’t quite work for us out of the box – so we ended up having to roll our own.
Testing support is great
Like the majority of the Ruby community, we’re big believers in writing tests. ExUnit made us feel right at home. Functional programming made our code very easily testable – we found that we reached for mocks much less frequently than we would have with Ruby. When we did need a mock (for example, for code that relied on an external service), being explicit made our code clearer.
One of the biggest differences was in the speed of our test suite. Even as our unit tests started to number in the hundreds, the entire suite only took a few seconds to run. We took full advantage ExUnit’s asynchronous runner, parallelizing the majority of our suite, including our channel tests. As anyone who’s run a slow test suite would attest, the benefit of this over the course of a project is huge.
On the downside, our tests were a bit more repetitive than we would have liked. Inability to group related tests into contexts really hurt us here. Luckily, Elixir 1.3 added
describe, which addresses this problem.
Embracing processes and OTP
Elixir asks us to think about solving problems in a different way. Our system had a lot of ephemeral state. We started out by putting it into the database without thinking too much about it. As we got more comfortable, we started to lean more on processes.
We got the concrete benefit of less latency – we were making less database roundtrips – but the change also significantly decoupled our code. Individual processes could register for messages via a pubsub pattern. A process sending a push notification could be entirely separated from the one that triggered it. Errors became more isolated, and we could now introduce supervisors to recover from them.
We know we’ve got a lot longer to go with OTP. From a Ruby background, there isn’t anything similar to draw experiences from. We’re still discovering best practices for building OTP applications, and while there’s great documentation, learning the “hows” and “whys” is still something we’re figuring out on our own.
Elixir brings something compelling to the table – an expressive, modern language built on the rock-solid Erlang VM. For the majority of us that are new to both, there’s a lot to learn – from functional programming, to concurrency, to the concepts of OTP. Some resources that have helped us along the way: Programming Elixir, Elixir in Action, Programming Phoenix, and Learn You Some Erlang. There is also a slack group, IRC channel (#elixir-lang on Freenode), forum, and mailing list where people are happy to help.