When you start your new Elixir project via mix new my_awesome_project you will end with something like this in mix.exs:

defmodule MyAwesomeProject.Mixfile do
  use Mix.Project

  def config do
    [
      name: :my_awesome_project,
      #      deps: deps()
    ]
  end

  def application do
    [
      extra_applications: [:logger]
    ]
  end

  defp deps do
    [
      #    ]
  end
end

And in most cases you will focus on deps/0, sometimes on config/0, but the application/0 you will almost never touch, and if you do, you probably will only need it to add new entry in :extra_applications or sometimes :included_applications. Except for, that this function interns are terra incognita, a place where you never look unless you are forced to, like "Read it later" list in Safari. This is sad, as it is quite powerful and useful piece of code.

However first things first.

What application/0 is for?#

In Erlang the running system is built from applications. Those applications are like processes in your system managed by your system supervisor (SysV, systemd, OpenRC, launchd, etc.). They are launched either during VM startup or on direct user request via Application.start/1-2 and its family. Starting this application is described in my_awesome_app.app file which is commonly generated by the build system from the template. In Rebar3 projects this template is src/appname.app.src and in Elixir it's the return value of the named application/0 function. This generated file is known as Application Resource File. It is just tuple in form of {:application, :my_awesome_app, opts} where opts is a keyword list of additional properties (to be exact it's output of the application/0 function).

Two of those optional fields are quite known in the Elixir community:

  • :mod which is a tuple {module(), term()} containing the starting point of our application; in most cases this is the module that will return main supervisor of the application.
  • :applications contains all applications required by our application; younger Elixir developers possibly never seen that as since version 1.5 this field is automatically filled by parsing :deps, though we still can add entries there via :extra_applications

There are also few Elixir specific fields, sometimes used in larger projects:

  • :extra_applications - those should be included in the release and automatically started before running current application; but aren't in the dependencies list because, for example, are in default distribution, for example logger or inets.
  • :included_applications - applications which should be included in the release, but not automatically started on boot.

Wait, there is more!

Unfortunately not all of the highly useful keys are used/known in the community.

Application environment#

For some reason everyone calls it configuration, so if you are familiar with config/config.exs and for some reason you decided to go against the Library Guidelines and you have decided to use application environment for configuring your code (there are reason to do so, even when you publish your code as a "library", see lager or opencensus) then you will soon find that putting configuration in your's library config/config.exs do not matter much in it's dependants. You have 2 possibilities how to solve that:

  • Use Application.get_env/3 and define your "default" as a 3rd argument.
  • Use :env to set data that should be loaded to application environment by default.

Be wary that the second option works only for current application, so you cannot configure other applications (for example logger) there. But what is the point? You may ask, and I found one. If you want to use default sys.config file for configuring your application then sometimes few pointless configuration option can land there, like :ecto_repos variable which truly doesn't matter much in production as it's only used by Ecto's Mix tasks. What I do is to add new entry in application/0 with:

env: [
  ecto_repos: [MyAwesomeApp.Repo]
]

And call it a day. Now I can focus on keeping real configuration options in config/config.exs and remove unneeded fields from there. But remember that you still can override these values by setting them in sys.config if needed, so this is pure win for me.

Start phases#

Application configuration also allows you to define additional pieces of code to be run after your application started. For example imagine situation when you want to send Slack notification that given node started and is ready to work. You can do it via temporary task in Supervisor.init/2 by defining child list like:

[
  MyApp.Repo,
  MyApp.Worker,
  {Task, &send_slack_notification/0}
]

Alternatively you can use :start_phases in application/0:

start_phases: [
  notify: []
]

And then define in your Application module function start_phase/3:

def start_phase(:notify, :normal, opts) do
  :ok = send_slack_notification()
end

Where 1st argument will be the name of the phase, 2nd will be start type the same as in Application.start/2 callback, and 3rd is the value passed in :start_phases.

The awesome part there is that start_phase/3 is called not only for current application, but all of it's dependencies as well.

Registered names#

This is one of the things that had more sense in Erlang world than in Elixir, but by being good citizen we should use it as well. This is a nice solution for lack of namespacing in Erlang - it allowed release tools to detect collisions in named processes. This is simple list of atoms that contain all names that are globally registered by this application. Example form Elixir's Logger:

registered: [Logger, Logger.BackendSupervisor, Logger.Supervisor, Logger.Watcher]

Unfortunately Phoenix do not use this field itself and do not suggest using one in it's default project generator. But in general it's good practise.

Summary#

There is much more in application configuration to what most Elixir code is using, it is worth sometimes to read how your application is defined and ran. For more information about generating application description file you can check out mix help compile.app.