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.
$ mix new --sup elixir_app
The generated project contains an application callback module in lib/elixir_app/application.ex
.
defmodule ElixirApp.Application do
# See https://hexdocs.pm/elixir/Application.html
# for more information on OTP Applications
@moduledoc false
use Application
def start(_type, _args) do
# List all child processes to be supervised
children = [
# Starts a worker by calling: ElixirApp.Worker.start_link(arg)
# {ElixirApp.Worker, arg},
]
# See https://hexdocs.pm/elixir/Supervisor.html
# for other strategies and supported options
opts = [strategy: :one_for_one, name: ElixirApp.Supervisor]
Supervisor.start_link(children, opts)
end
end
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.
defmodule ElixirApp.MixProject do
use Mix.Project
def project do
[
app: :elixir_app,
version: "0.1.0"
]
end
end
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.
{application,elixir_app,
[{applications,[kernel,stdlib,elixir]},
{description,"elixir_app"},
{modules,['Elixir.ElixirApp','Elixir.ElixirApp.Application']},
{registered,[]},
{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.
defmodule ElixirApp.MixProject do
# ...
def application do
[
mod: {ElixirApp.Application, []}
]
end
end
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.
{application,elixir_app,
[{applications,[kernel,stdlib,elixir,logger]},
{description,"elixir_app"},
{modules,['Elixir.ElixirApp','Elixir.ElixirApp.Application']},
{registered,[]},
{vsn,"0.1.0"},
{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
.
defmodule ElixirApp.MixProject do
use Mix.Project
def project do
[
app: :elixir_app,
version: "0.1.0",
deps: [{:appsignal, "~> 1.0.0"}]
]
end
end
Dependencies are automatically added to the applications
list, and they’re automatically started before the application boots if they have an Application module.
{application,elixir_app,
[{applications,[kernel,stdlib,elixir,appsignal]},
{description,"elixir_app"},
{modules,['Elixir.ElixirApp','Elixir.ElixirApp.Application']},
{registered,[]},
{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.
defmodule ElixirApp.MixProject do
# ...
def application do
[
extra_applications: [:logger],
mod: {ElixirApp.Application, []}
]
end
end
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 in a comment, or subscribe to the Elixir Alchemy newsletter.
Top comments (1)
Excellent! I had been blindly including applications when told to in a repo's README but I had no idea what was actually happening. Thanks for the explanation!