27 Apr 2023 · Software Engineering

    5 Reasons to Use Elixir in Production

    9 min read
    Contents

    Elixir is a dynamic, functional language for building scalable and maintainable applications. The creation of Elixir was initiated by maintainers of the Ruby language and the Rails framework projects running on top of Erlang Virtual Machine.

    The Concurrency Revolution appeared as a talk in the C/C++ Users Journal (23rd February, 2005). In this article, Herb Sutter wrote that: “the free lunch is over.” The trend of new CPU releases was changing from more speed to more cores.

    Desktop applications didn’t change much but increasing adoption of SaaS (Software as a Service) systems saw different frameworks and tools debuting on the development stage. One of them was Rails, created by 37signals, creators of Basecamp.

    Back in 2011, a young and very active developer was working with Ruby and Rails. But the problem with the technologies of the day wasn’t just correct use of multi-core CPUs: this young developer was worried about thread-safety.

    Thread-safety

    The first handicap for these old systems was the thread-safety. This issue can be illustrated with the following example:

    class PageController < ApplicationController
      def index
        @@my_info = params[:info]
    
        sleep 0.1
    
        if @@my_info == params[:info]
          render json: "ok #{info}"
        else
          render plain: "it should never happen!", status: 500
        end
      end
    end

    The @@my_info variable is global. In a multi-threaded context, this code shouldn’t have any problems because each thread has its own copy, right? The answer is “NO” because concurrency in Ruby shares the global state.

    This problem isn’t unique to Ruby. It is present in other languages like C, C++, Java, and Python where global state is shared between threads.

    When our young developer grabbed a book called Seven Languages in Seven Weeks and discovered Erlang it was a trigger for him. Other people had tried to do what José Valim wanted to achieve. Languages like Reia started as an idea to provide Ruby on top of Erlang, but José, a very active developer of Ruby with significant contributions to Rails, decided to create something different and called it Elixir.

    Immutability

    The Erlang platform was created as a telecommunications platform. Such a platform needed to have the following features:

    • Fault tolerance: doesn’t mean try/catch in every line of code — that’s defensive programming. Fault tolerance is the ability to contain failures to a small part of the system and the ability to restart that small part without affecting the other parts of the system. Contrary to panic in Go or an unhandled exception in Java, Erlang’s design tries to prevent general application outages.
    • High Concurrency Level. BEAM is share-nothing. The heap memory for each process isn’t accessible to any other processes. This makes a system thread-safe by default.
    • Soft real-time. Because we don’t share memory and the variables are immutable, the garbage collector doesn’t “stop the world” to free memory. This makes it possible to write programs with soft, real-time requirements. It means the time to run specific code is fixed.
    • Hot-code upgrade. Thanks to the previous features it’s also possible to perform hot-code upgrades. By loading a new version of the module, subsequent calls to those functions invoke the new code. In turn, the old version remains in use for the duration of the previous invocations. Once all previous executions are finished, the old version of the module is unloaded.

    What could Elixir distill from its Ruby and Clojure influence? José had in mind some cool features from Ruby and, taking advantage of the design of the new language, he decided to copy from other languages he knew.

    José Valim always says the pillars of Elixir are productivity, extensibility and compatibility with Erlang.

    Productivity

    A good basis for productivity is avoiding repetitive writing and repetitive tasks. Avoiding the boilerplate. The syntactic sugar brought from Ruby helps with this. Languages like Java, C++, or even Erlang sometimes need to use templates for specific implementations, meaning code is repeated again and again. Ruby fought against this and it can be seen in Elixir as well. For example, the if construct is defined in Elixir as a macro:

    if state >= 50 do
      do_this()
    else
      do_this_instead()
    end
    
    # it could be also:
    if state >= 50, do: do_this(), else: do_this_instead()
    
    # which is also this:
    if(state >= 50, do: do_this(), else: do_this_instead())

    Another facility is the pipe operator. Borrowed from Clojure, the pipe operator helps us build a flow of data. For example, when we develop using Erlang and we need to process data with different functions we have two different solutions:

    update_user(User, #{email := Email, address := Address}) ->
        User1 = change_user_email(User, Email),
        User2 = change_user_address(User1, Address),
        save_user(User2).

    Using Elixir we can perform the same code without intermediary variables:

    def update_user(user, %{email: email, address: address}) do
      user
      |> change_user_email(email)
      |> change_user_address(address)
      |> save_user()
    end

    Productivity is more than source code shortcuts: it’s based on having first-class documentation and tooling like the mix and iex commands, the ExUnit framework for tests, or even the Hex repository for dependencies.

    Extensibility

    One of the first conferences where I met José Valim was the Erlang Factory in 2015 in Stockholm. He presented a concept about Erlang and its JSON libraries. Sometimes we found libraries we could use but didn’t match our use case completely. Using the Inversion of Control pattern might have helped but not as easily since the language didn’t provide mechanisms to facilitate it.

    Just to illustrate this idea, imagine we create a JSON library that provides a method for serializing data: numbers, strings, lists, tuples, and maps. But what if we need a defined structure that should be serialized in a specific way?

    The initial idea of most Erlang developers in this situation (even the creators) was to copy the JSON library and perform a modification to include their customizations. Not very extendable. To address this, José borrowed an idea from Clojure: protocols.

    defprotocol JSON do
      @spec encode(t) :: String.t()
      def encode(value)
    end
    
    defimpl JSON, for: BitString do
      def encode(string), do: ~s|"#{string}"|
    end
    
    defimpl JSON, for: Any do
      def encode(any), do: to_string(any)
    end

    This way we can add this source code as a dependency and provide a new defimpl which uses our structure and indicates how to perform data encoding.

    Compatibility with Erlang

    Last but not least is compatibility which includes: concurrency, distribution, and embracing BEAM. As I mentioned at the beginning, the paradigm of development has changed and developers are forced to play more and more with high load, concurrency, and distribution. Elixir takes advantage of the virtual machine around which it was created (BEAM) and handles concurrency as Erlang does because the solutions provided by this language are the correct ones.

    I think that the following quote about Elixir from an Evan Miller article puts things well:

    “I almost always leave with the impression that the designers did the “right thing”. I suppose this is in contrast to Java, which does the pedantic thing, Perl, which does the kludgy thing, Ruby, which has two independent implementations of the wrong thing, and C, which doesn’t do anything.”

    All of the decisions made regarding concurrency and handling of resources from the virtual machine perspective were correct because big companies like Whatsapp created something similar to other products with much less effort. Indeed, Facebook was using C++ and Hack (a typed version of the PHP language) and they needed lots of very talented engineers to make it work. I always remember a quote from Robert Virding about Virding’s First Rule of Programming:

    “Any sufficiently complicated concurrent program in another language contains an ad hoc informally-specified bug-ridden slow implementation of half of Erlang.”

    Robert Virding

    Why implement something new if that’s what BEAM (the Erlang virtual machine) does? Therefore, Elixir uses BEAM instead of trying to reinvent the wheel.

    Companies, developers, and tools

    Elixir is gaining traction and there are more and more companies using it daily. The list of companies using it includes PepsiCo, Spotify, Discord, Pinterest, Toyota Connected, Bleacher Report, Financial Times, Adobe, BBC, BlockFi, PagerDuty, Slack, and so on.

    One of the more frequent excuses against new technology is the problem of hiring. Hiring Elixir developers isn’t easy, but keeping in mind the number of open positions  available developers, it’s normal and not something specific to Elixir. It’s difficult hiring a developer no matter what technology, but you can always train your developers in-house. Hiring juniors or people willing to learn the technology and providing them the tools: books, courses, and training sessions, can be a good option.

    We usually configure our build pipeline with the tools we use in order to ensure that the internal quality is good enough. These tools include:

    • Version control system. The clear winner in this category these days is git. If you are a developer, I don’t need to say more.
    • Continuous integration system. Here we have different alternatives. There is a lot on the market and depending on your needs you can choose one or another. Take, for instance, Semaphore: performant builds supporting your stack with detailed reporting and important security features.
    • Continuous deployment system. In addition to continuous integration, it’s a great idea to set up auto-deployment of every change the moment it’s developed and passes the checks.

    Elixir helps us with these because it:

    • Has a configuration for each different stage: dev, test, and prod.
    • Provides a way to perform releases using its default tooling (mix).
    • Has plenty of tools for ensuring good code quality: tests (ExUnit), coverage, dialyzer, credo, doctor, and sobelow.
    • Has a great log system (logger).
    • Has observability tools available in its ecosystem (telemetry).

    As already stated, one of the pillars of Elixir is productivity and good tooling spawns productivity and team performance.

    Conclusion

    Elixir is mature and battle-tested in creating great software and providing a coherent, performant, and reliable development system to the delight of developers around the world. Many companies rely on Elixir and its features to provide robustness to their users. What’s stopping you from trying it out?

    Leave a Reply

    Your email address will not be published. Required fields are marked *

    Avatar
    Writen by:
    Manuel has been a developer since he was 12 years old he started with Basic, like others, but later also with Modula-2, Pascal, C, Assembler, and these before he was 20 years old. In his professional career, he has used Perl, PHP, Python, Ruby, Java, and JavaScript, and since 2009 he was more on Erlang and since 2016 he started with Elixir and Go. He can be considered a polyglot programmer and he loves to teach and mentor others.
    Avatar
    Reviewed by:
    I picked up most of my soft/hardware troubleshooting skills in the US Army. A decade of Java development drove me to operations, scaling infrastructure to cope with the thundering herd. Engineering coach and CTO of Teleclinic.