this repo has no description

Merge pull request #2 from hauleth/elixir-application

Article about Mix's `application/0` function

Changed files
+192 -2
content
+4 -1
Makefile
···
-
.PHONY: assets build
+
.PHONY: assets build local
build: assets
hugo
+
+
local: assets
+
hugo server -wD
assets:
yarn install
+1 -1
config.toml
···
-
baseURL = "https://hauleth.dev/"
+
baseURL = "/"
languageCode = "en-gb"
title = "Hauleth"
theme = "terminal"
+187
content/post/elixir-application.md
···
+
---
+
title: "Let's talk about `application/0`"
+
date: 2019-07-26T11:36:01+02:00
+
description: |
+
Have you ever thought about that one simple function in your `mix.exs`? It
+
comes out as quite powerful and useful place for storing configuration and
+
post-launch scripts.
+
tags:
+
- elixir
+
- erlang
+
- beam
+
- programming
+
---
+
+
When you start your new Elixir project via `mix new my_awesome_project` you will
+
end with something like this in `mix.exs`:
+
+
```elixir
+
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][app 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*](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:
+
+
```elixir
+
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:
+
+
```elixir
+
[
+
MyApp.Repo,
+
MyApp.Worker,
+
{Task, &send_slack_notification/0}
+
]
+
```
+
+
Alternatively you can use `:start_phases` in `application/0`:
+
+
```elixir
+
start_phases: [
+
notify: []
+
]
+
```
+
+
And then define in your `Application` module function `start_phase/3`:
+
+
```elixir
+
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](https://hexdocs.pm/elixir/Application.html#c:start/2),
+
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](https://github.com/elixir-lang/elixir/blob/ee9f38635e9a6c816adb575fc9431ded49be8032/lib/logger/mix.exs#L14):
+
+
```elixir
+
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`](https://hexdocs.pm/mix/Mix.Tasks.Compile.App.html).
+
+
[app file]: http://www.erlang.org/doc/design_principles/applications.html#application-resource-file
+
[guidelines]: https://hexdocs.pm/elixir/library-guidelines.html