Let's talk about `application/0`
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 examplelogger
orinets
.: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
.