My personal blog hauleth.dev
blog
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Merge pull request #2 from hauleth/elixir-application

Article about Mix's `application/0` function

authored by

Łukasz Jan Niemier and committed by
GitHub
2f1d0bab 18fb80e3

+192 -2
+4 -1
Makefile
··· 1 - .PHONY: assets build 1 + .PHONY: assets build local 2 2 3 3 build: assets 4 4 hugo 5 + 6 + local: assets 7 + hugo server -wD 5 8 6 9 assets: 7 10 yarn install
+1 -1
config.toml
··· 1 - baseURL = "https://hauleth.dev/" 1 + baseURL = "/" 2 2 languageCode = "en-gb" 3 3 title = "Hauleth" 4 4 theme = "terminal"
+187
content/post/elixir-application.md
··· 1 + --- 2 + title: "Let's talk about `application/0`" 3 + date: 2019-07-26T11:36:01+02:00 4 + description: | 5 + Have you ever thought about that one simple function in your `mix.exs`? It 6 + comes out as quite powerful and useful place for storing configuration and 7 + post-launch scripts. 8 + tags: 9 + - elixir 10 + - erlang 11 + - beam 12 + - programming 13 + --- 14 + 15 + When you start your new Elixir project via `mix new my_awesome_project` you will 16 + end with something like this in `mix.exs`: 17 + 18 + ```elixir 19 + defmodule MyAwesomeProject.Mixfile do 20 + use Mix.Project 21 + 22 + def config do 23 + [ 24 + name: :my_awesome_project, 25 + # … 26 + deps: deps() 27 + ] 28 + end 29 + 30 + def application do 31 + [ 32 + extra_applications: [:logger] 33 + ] 34 + end 35 + 36 + defp deps do 37 + [ 38 + # … 39 + ] 40 + end 41 + end 42 + ``` 43 + 44 + And in most cases you will focus on `deps/0`, sometimes on `config/0`, but the 45 + `application/0` you will almost never touch, and if you do, you probably will only 46 + need it to add new entry in `:extra_applications` or sometimes 47 + `:included_applications`. Except for, that this function interns are terra 48 + incognita, a place where you never look unless you are forced to, like "Read 49 + it later" list in Safari. This is sad, as it is quite powerful and useful 50 + piece of code. 51 + 52 + However first things first. 53 + 54 + ## What `application/0` is for? 55 + 56 + In Erlang the running system is built from applications. Those applications are 57 + like processes in your system managed by your system supervisor (SysV, systemd, 58 + OpenRC, launchd, etc.). They are launched either during VM startup or on 59 + direct user request via `Application.start/1-2` and its family. Starting 60 + this application is described in `my_awesome_app.app` file which is commonly 61 + generated by the build system from the template. In Rebar3 projects this 62 + template is `src/appname.app.src` and in Elixir it's the return value of the 63 + named `application/0` function. This generated file is known as [Application 64 + Resource File][app file]. It is just tuple in form of `{:application, 65 + :my_awesome_app, opts}` where `opts` is a keyword list of additional properties 66 + (to be exact it's output of the `application/0` function). 67 + 68 + Two of those optional fields are quite known in the Elixir community: 69 + 70 + - `:mod` which is a tuple `{module(), term()}` containing the starting point 71 + of our application; in most cases this is the module that will return main 72 + supervisor of the application. 73 + - `:applications` contains all applications required by our application; younger 74 + Elixir developers possibly never seen that as since version 1.5 this field 75 + is automatically filled by parsing `:deps`, though we still can add entries 76 + there via `:extra_applications` 77 + 78 + There are also few Elixir specific fields, sometimes used in larger projects: 79 + 80 + - `:extra_applications` - those should be included in the release 81 + and automatically started before running current application; but aren't in the 82 + dependencies list because, for example, are in default distribution, for 83 + example `logger` or `inets`. 84 + - `:included_applications` - applications which should be included in the 85 + release, but not automatically started on boot. 86 + 87 + Wait, there is more! 88 + 89 + Unfortunately not all of the highly useful keys are used/known in the community. 90 + 91 + ## Application environment 92 + 93 + For some reason everyone calls it configuration, so if you are familiar with 94 + `config/config.exs` and for some reason you decided to go [against the *Library 95 + Guidelines*](guidelines) and you have decided to use application environment for 96 + configuring your code (there are reason to do so, even when you publish your 97 + code as a "library", see `lager` or `opencensus`) then you will soon find that 98 + putting configuration in your's library `config/config.exs` do not matter much 99 + in it's dependants. You have 2 possibilities how to solve that: 100 + 101 + - Use `Application.get_env/3` and define your "default" as a 3rd argument. 102 + - Use `:env` to set data that should be loaded to application environment by 103 + default. 104 + 105 + Be wary that the second option works only for current application, so you 106 + cannot configure other applications (for example `logger`) there. *But what is 107 + the point?* You may ask, and I found one. If you want to use default 108 + `sys.config` file for configuring your application then sometimes few pointless 109 + configuration option can land there, like `:ecto_repos` variable which truly 110 + doesn't matter much in production as it's only used by Ecto's Mix tasks. What 111 + I do is to add new entry in `application/0` with: 112 + 113 + ```elixir 114 + env: [ 115 + ecto_repos: [MyAwesomeApp.Repo] 116 + ] 117 + ``` 118 + 119 + And call it a day. Now I can focus on keeping **real** configuration options in 120 + `config/config.exs` and remove unneeded fields from there. But remember that you 121 + still can override these values by setting them in `sys.config` if needed, so 122 + this is pure win for me. 123 + 124 + ## Start phases 125 + 126 + Application configuration also allows you to define additional pieces of code to 127 + be run after your application started. For example imagine situation when you 128 + want to send Slack notification that given node started and is ready to work. 129 + You can do it via temporary task in `Supervisor.init/2` by defining child list 130 + like: 131 + 132 + ```elixir 133 + [ 134 + MyApp.Repo, 135 + MyApp.Worker, 136 + {Task, &send_slack_notification/0} 137 + ] 138 + ``` 139 + 140 + Alternatively you can use `:start_phases` in `application/0`: 141 + 142 + ```elixir 143 + start_phases: [ 144 + notify: [] 145 + ] 146 + ``` 147 + 148 + And then define in your `Application` module function `start_phase/3`: 149 + 150 + ```elixir 151 + def start_phase(:notify, :normal, opts) do 152 + :ok = send_slack_notification() 153 + end 154 + ``` 155 + 156 + Where 1st argument will be the name of the phase, 2nd will be start type the 157 + same as in [`Application.start/2` callback](https://hexdocs.pm/elixir/Application.html#c:start/2), 158 + and 3rd is the value passed in `:start_phases`. 159 + 160 + The awesome part there is that `start_phase/3` is called not only for current 161 + application, but all of it's dependencies as well. 162 + 163 + ## Registered names 164 + 165 + This is one of the things that had more sense in Erlang world than in Elixir, 166 + but by being good citizen we should use it as well. This is a nice solution for 167 + lack of namespacing in Erlang - it allowed release tools to detect collisions in 168 + named processes. This is simple list of atoms that contain all names that are 169 + globally registered by this application. Example form [Elixir's 170 + Logger](https://github.com/elixir-lang/elixir/blob/ee9f38635e9a6c816adb575fc9431ded49be8032/lib/logger/mix.exs#L14): 171 + 172 + ```elixir 173 + registered: [Logger, Logger.BackendSupervisor, Logger.Supervisor, Logger.Watcher] 174 + ``` 175 + 176 + Unfortunately Phoenix do not use this field itself and do not suggest using one 177 + in it's default project generator. But in general it's good practise. 178 + 179 + ## Summary 180 + 181 + There is much more in application configuration to what most Elixir code is 182 + using, it is worth sometimes to read how your application is defined and ran. 183 + For more information about generating application description file you can check 184 + out [`mix help compile.app`](https://hexdocs.pm/mix/Mix.Tasks.Compile.App.html). 185 + 186 + [app file]: http://www.erlang.org/doc/design_principles/applications.html#application-resource-file 187 + [guidelines]: https://hexdocs.pm/elixir/library-guidelines.html