Skip to content
  • Blog

Productivity, Maintainability and Performance with Elixir & Functional Programming

May 28 — 2020

Rémi Prévost
Partner, Director ⏤ Software Development

At Mirego, we’re constantly reviewing our practices, and looking for ways to improve our tools as well as how we work. We are never completely convinced that we have the ultimate solution in hand and that’s why we always question our technological choices.

In terms of Web development, we’re always looking for the perfect balance between productivity, maintainability and performance. We believe we have found this balance with functional programming and the Elixir language.



History

Elixir is a functional programming language launched in 2012 by long-time members of the Ruby on Rails community. They still liked the Ruby language, but felt that, by the nature of its foundations, couldn’t push it further in terms of performance and maintainability. Therefore, they started Elixir by building it on Erlang language’s foundations, while retaining the major Ruby’s advantages over other languages.

We had a similar reasoning back in 2014, when we started to think about our use of Ruby. The performance and maintainability issues of our Web products had become very important:

  • Performance—We’re building growing Web applications and platforms for public use. These services high availability and speed are crucial to provide a first-level user experience;
  • Maintainability— We build products with an increasing life expectancy for our customers. Therefore, we often have to maintain these products. It’s essential that the complexity of a project’s code remains stable over the years.

The arrival of Elixir, a functional programming language emphasizing these two specific challenges, convinced us to gradually make the leap to this ecosystem.

In addition, because we have been using it in production since 2014, we believe we have sufficient learning background to assert that using Elixir and functional programming allows us to build reliable, maintainable, efficient and fun to develop digital Web products.



Functional Programming with Elixir


Benefit # 1: Productivity

Several factors determine the language’s productivity in a certain technology, including its learning curve, the libraries of its ecosystem and the community that surrounds it. Elixir is fortunate to be a leader in all three aspects.

Like Ruby, Elixir was designed with the goal of maximizing what Ruby’s author calls “developer happiness.” The language features were built to be as predictable as possible for the average developer. The same goes for its “all-inclusive” tools which include a framework to develop automated tests, a compiler, an interactive console and standardized implementations to use its “actor” model as effectively as possible.

Elixir also has a large amount of stable and reliable libraries maintained by a very active community (to which we contribute a lot!). Since this community is united, major libraries’ crystallization was formed very quickly. There is no fragmentation in the ecosystem, where a large number of separate libraries want to fulfill the same need.

Also, since Elixir is a modern language, the implementation of modern technologies in the ecosystem has always been a priority for its community. In addition to the basic tools covering dependency management, automated tests, documentation and system tasks, there are libraries allowing us to quickly build Web applications integrating databases, asynchronous tasks, GraphQL APIs, communications via WebSocket and telemetry.

We don’t have to reinvent the wheel for each project, thanks to the libraries’ quantity and especially quality put forward by this community.



Benefit # 2: Maintainability

Although productivity is important during a project development, it should never impact the maintainability of the code. For example, being able to quickly develop a functionality in a single day loses its lustre if each time you have to modify this functionality, several hours of understanding, reasoning and trial and error are necessary.

This is where functional programming concepts come in.

Functional programming requires explicit interactions between the various code’s components. There is no way to embed code that triggers “side effects”, i.e., code that transparently changes the state of another component.


Object-oriented Programming

Interactions are implicit between the different methods and references. It becomes easy to lose our bearings.

class AuthenticationService
  def initialize(user)
    @user = user
  end

  def login(password)
    # The `authenticate!` method implicitely changes the state of `@user`.
    authenticate!(password)
    @user.authenticated?
  end

  def authenticate!(password) do
    # The `@user` reference is implicitely defined.
    if @user.password == hash(password)
      @user.authenticated? = true
    else
      @user.authenticated? = false
    end
  end
end

service = AuthenticationService.new(user)
service.login(password)
# => true

Functional programming

The interactions are explicit between the different methods and the data structures.

defmodule Authentication do
  # The `user` variable is explicitely received as an argument.
  def login(user, password) do
    # The output of `authenticate!` is retained and reused
    # because the state of the passed `user` cannot be modified elsewhere.
    user = authenticate!(user, password)
    user.authenticated?
  end

  # The `user` variable is always explicitely received as an argument
  def authenticate(user, password) do  
    if user.password == hash(password) do
      # The state of `user` is not modified — a completely new structure is
      # returned with its newly defined `authenticated?` property.
      %{user | authenticated?: true}
    else
      %{user | authenticated?: false}
    end
  end
end

Authentication.login(user, password)
# => true

Functional programming makes the code:

  • easier to reason, since there are no implicit behaviours to decode;
  • easier to test in the context of automated tests, since it suffices to provide incoming data to functions, and validate outgoing data.

Concerning Elixir language, its ecosystem’s stability is reflected in the way we work. We have been working with this language for several years and the vast majority of our Elixir projects are configured, structured and deployed in the same way.

Ultimately, all these advantages of functional programming and Elixir give us maximum confidence in our code and facilitate its evolution.



Benefit # 3: Performance

Productivity and maintainability are principles that apply to the code as such, but not necessarily at the end product level. One of the aspects on which the code has a direct impact on a product is its performance, and no compromises should be made on this.

As mentioned earlier, Elixir builds on Erlang and its history of production use for over 30 years in the largest and most reliable telecommunications systems in the world. Elixir therefore takes full advantage of the maturity of Erlang’s ecosystem, whose top priorities include performance.

Elixir and Erlang are recognized in the industry. They are used in production as much in products where the high availability of real-time communication systems is critical, such as WhatsApp and Discord, or in products like Pinterest and Bleacher Report, where performance is capital.

While in other technological ecosystems, several optimizations must be planned from the outset to achieve satisfactory results, performance is rarely an issue with Elixir.

For example, with Ruby and PHP, it isn’t uncommon to have to quickly resort to an excessive use of caching systems to minimize code execution or an increase in the number of servers to process a larger number of requests. The minimal impact of Elixir’s functional programming and architecture eliminates the need to worry about code execution or the fallback to the “boot more machines” approach to performance issues.

Therefore, Elixir helps us to focus our energy on creating excellent products rather than spending it on continuously bypassing and solving performance issues.

Elixir in Production: Montreal Canadiens

When we started thinking about the backend architecture for the Montreal Canadiens mobile app redesign, Elixir was quickly identified as the ideal ecosystem.

On one hand, with the development of functionalities such as a thread of a match’s important events which take place in real time as well as an interactive quiz soliciting the simultaneous participation of all the team’s fans, the backend performance had to be of impeccable quality.

Result: During peak periods, the backend serves thousands of HTTP requests per minute without any impact on its availability and performance, with a minimal number of servers.

On the other hand, with the integration of several external suppliers linked to several fields (eg ticketing, hockey statistics, loyalty program, notifications) and the synchronization of external data with several of them, the good separation of the various areas within the same project was paramount.

Result: Despite its complexity, the project was developed quickly within a few months, with a team made up of several developers, without any productivity or maintainability issues.

We have been using functional programming and Elixir since 2014 to help us be as productive as possible when developing web products. And as we don’t want to compromise on maintainability or performance, Elixir remains the best choice, thanks to its functional nature and mature foundations.

00:00
00:00

En français SVP !