DEV Community

Cover image for How OTP Applications are structured
Jeff Kreeftmeijer for AppSignal

Posted on • Originally published at blog.appsignal.com

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.

$ mix new --sup elixir_app
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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"}]}.
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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',[]}}]}.
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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"}]}.
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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)

Collapse
 
jdsteinhauser profile image
Jason Steinhauser

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!