Multi-application Umbrella Projects

It’s unfortunate that Erlang chose to call self-contained bundles of code applications. In many ways, they’re closer to being shared libraries. And as our projects grow, we may find ourselves wanting to split our code into multiple libraries, or applications.

Fortunately, mix makes this painless. To illustrate the process, we’ll create a simple Elixir evaluator. Given a set of input lines, it’ll return the result of evaluating each. This’ll be one application. To test it, we’ll need to pass in lists of lines. We’ve already written a trivial ~l sigil that creates lists of lines for us, so we’ll make that sigil code into a separate application.

Elixir calls these multi-application (multi-app) projects umbrella projects.

Create an umbrella project

We use mix new to create an umbrella project, passing it the --umbrella option.

$ mix new --umbrella eval 
* creating README.md
* creating mix.exs
* creating apps

Compared to a normal mix project, the umbrella is pretty lightweight with just a mix file and an apps directory.

Create the sub-projects

Subprojects are stored in the apps directory. There’s nothing special about them—they’re simply regular projects created using mix new. Let’s create our two projects now:

$ cd eval/apps
$ mix new line_sigil 
* creating README.md
... and so on
$ mix new evaluator 
* creating README.md
... and so on
* creating test/evaluator_test.exs

At this point, we can try out our umbrella project. Let’s go back to the overall project directory and try mix compile.

$ cd ..
$ mix compile
==> evaluator
Compiled lib/evaluator.ex
Generated evaluator app
==> line_sigil
Compiled lib/line_sigil.ex
Generated line_sigil app

Now, we have an umbrella project containing two regular projects. Because there’s nothing special about the subprojects, we can use all the regular mix commands in them. At the top level, though, we can build all the sub-projects as a unit.

Making the sub-project decision

The fact that subprojects are just regular mix projects means we don’t have to worry about whether to start a new project using an umbrella. We start it as a simple project. If we later discover the need for an umbrella project, we create it and move our existing simple project into the apps directory.

Note: We can also create a new umbrella project by following the above commands step by step.

The LineSigil project

This project is trivial. We just copy the LineSigil module from the previous section into apps/line_sigil/lib/line_sigil.ex. We verify it builds by running mix compile in either the top-level directory or the line_sigil directory.

The evaluator project

The evaluator takes a list of strings containing Elixir expressions and evaluates them. It returns a list containing the expressions intermixed with the value of each. For example, let’s look at the following code:

a=3 
b=4 
a+b

Given the above, our code will return the following:

code>  a = 3
value> 3
code>  b = 4
value> 4
code>  a + b
value> 7

We’ll use Code.eval_string to execute the Elixir expressions. To have the values of variables pass from one expression to the next, we’ll also need to explicitly maintain the current binding. Here’s the code:

defmodule Evaluator do

  def eval(list_of_expressions) do
    { result, _final_binding } =
        Enum.reduce(list_of_expressions,
                    {_result = [], _binding = binding()}, 
                    &evaluate_with_binding/2)
    Enum.reverse result
  end

  defp evaluate_with_binding(expression, { result, binding }) do
    { next_result, new_binding } = Code.eval_string(expression, binding)
    { [ "value> #{next_result}", "code>  #{expression}" | result ], new_binding }
  end
end

Linking the subprojects

Now, we need to test our evaluator. It makes sense to use our ~l sigil to create lists of expressions, so let’s write our tests that way.

Get hands-on with 1200+ tech skills courses.