elixir

How OTP Applications are structured

Jeff Kreeftmeijer

Jeff Kreeftmeijer on

How OTP Applications are structured

OTP, the framework that provides standards to help build Erlang applications, uses applications to package code into units or components. This convention helps structuring your code into logical groups of modules and a way to start and stop the application's supervisors. Your Phoenix project is an application, but the Phoenix framework and Ecto are applications too.

An application is a component that can be compiled by itself. It can depend on other applications, and it can take its own configuration. Unlike in other languages, this convention provides a standardized way to specify how the VM should interact with it.

Aside from the compiled .beam files containing virtual machine code, compiled applications consist of an application specification .app file that tells the VM how to handle the application, and an optional application callback module that is used to start, run and stop the application.

Application Callback Modules

For applications with supervisors, the application callback module defines functions that start and stop the application. Applications without supervisors (usually libraries with functions you can call without no internal state) omit the callback module since the application doesn't need to be started.

In Elixir, an application callback module uses the Application behavior. The behavior implements the start/2 and stop/1 callbacks. The former starts the application's main supervisor, and the latter is an optional callback that's used to clean up after stopping the supervisor.

Elixir's mix new task automatically creates an application callback module when a new project is generated with the --sup flag.

1$ mix new --sup elixir_app

The generated project contains an application callback module in lib/elixir_app/application.ex.

1defmodule ElixirApp.Application do
2  # See https://hexdocs.pm/elixir/Application.html
3  # for more information on OTP Applications
4  @moduledoc false
5
6  use Application
7
8  def start(_type, _args) do
9    # List all child processes to be supervised
10    children = [
11      # Starts a worker by calling: ElixirApp.Worker.start_link(arg)
12      # {ElixirApp.Worker, arg},
13    ]
14
15    # See https://hexdocs.pm/elixir/Supervisor.html
16    # for other strategies and supported options
17    opts = [strategy: :one_for_one, name: ElixirApp.Supervisor]
18    Supervisor.start_link(children, opts)
19  end
20end

When used, the Application module adds a stub of the stop/1 function, which returns an ok-tuple. The generated start/2 function starts the app's main supervisor.

Application Specifications

Running mix compile.app places the application's specification in the .app file in the ebin directory, and is one of the steps taken when compiling the app with mix compile.

The specification contains Erlang terms which define the application. It lists the modules defined in the app, the version number and a list of other apps that the app depends on. It also specifies the module to be used as the callback that starts the app.

The specification file is based on the settings in the application’s mix.exs file. To be able to generate the specification, each app must have a name and version number defined in its project function.

1defmodule ElixirApp.MixProject do
2  use Mix.Project
3
4  def project do
5    [
6      app: :elixir_app,
7      version: "0.1.0"
8    ]
9  end
10end

Below is a minimal sample mix.exs file, which only lists the project name and version number, yields a specification that lists Elixir’s default applications and specifies the modules in the app.

1{application,elixir_app,
2             [{applications,[kernel,stdlib,elixir]},
3              {description,"elixir_app"},
4              {modules,['Elixir.ElixirApp','Elixir.ElixirApp.Application']},
5              {registered,[]},
6              {vsn,"0.1.0"}]}.

Specifying and Configuring the Application Callback Module

Aside from the app's name and version number, a mix.exs file generated with the --sup option has an application function with a mod key.

1defmodule ElixirApp.MixProject do
2  # ...
3
4  def application do
5    [
6      mod: {ElixirApp.Application, []}
7    ]
8  end
9end

When generating the specification, it includes the callback module. This key points to the application's callback module, so the VM knows which module to use to start the application.

1{application,elixir_app,
2             [{applications,[kernel,stdlib,elixir,logger]},
3              {description,"elixir_app"},
4              {modules,['Elixir.ElixirApp','Elixir.ElixirApp.Application']},
5              {registered,[]},
6              {vsn,"0.1.0"},
7              {mod,{'Elixir.ElixirApp.Application',[]}}]}.

Tip: The list in the :mod-tuple is used to pass configuration options to the application. Anything passed in ends up as the arguments to the callback module's start/2 function.

:applications and :extra_applications

The applications key in the specification lists all the applications that your app depends on. By default, mix compile.app includes kernel stdlib and elixir.

1defmodule ElixirApp.MixProject do
2  use Mix.Project
3
4  def project do
5    [
6      app: :elixir_app,
7      version: "0.1.0",
8      deps: [{:appsignal, "~> 1.0.0"}]
9    ]
10  end
11end

Dependencies are automatically added to the applications list, and they’re automatically started before the application boots if they have an Application module.

1{application,elixir_app,
2             [{applications,[kernel,stdlib,elixir,appsignal]},
3              {description,"elixir_app"},
4              {modules,['Elixir.ElixirApp','Elixir.ElixirApp.Application']},
5              {registered,[]},
6              {vsn,"0.1.0"}]}.

To add more applications like Elixir’s logger, you add them to the :extra_applications key and they will subsequently be added to the existing list.

1defmodule ElixirApp.MixProject do
2
3  # ...
4
5  def application do
6    [
7      extra_applications: [:logger],
8      mod: {ElixirApp.Application, []}
9    ]
10  end
11end

The :applications key is used to explicitly specify the applications that are to be included in the specification. When used, only the listed applications and the defaults will be added. Any other dependencies are not automatically included.

Applications all the way down

Each seperate library, as well as your application itself, is an application. Some applications have a supervision tree, requiring them to have a callback module that takes care of starting and stopping the whole application.

Your application can depend on other applications, and each of these can have their own supervision trees and callback modules. Whatever's in there, it's always specified in the application specification file. Think of it as the formula for your Elixir. 😉

This concludes our overview of OTP applications in Elixir. We've tried convey some of the beauty of one of OTP's conventions. It is one of the many things we love about Elixir. If you do too, let us know what you'd like to see us write about, or subscribe to the Elixir Alchemy newsletter.

Share this article

RSS

AppSignal monitors your apps

AppSignal provides insights for Ruby, Rails, Elixir, Phoenix, Node.js, Express and many other frameworks and libraries. We are located in beautiful Amsterdam. We love stroopwafels. If you do too, let us know. We might send you some!

Discover AppSignal
AppSignal monitors your apps