Turning Our Sequence Program into an OTP Application
Learn how to change our previous sequence project into an OTP application.
We'll cover the following
Introduction
So, here’s the good news. The application in the Supervisors and Workers lesson is already a full-blown OTP application. When mix
created the initial project tree, it added a supervisor (which we then modified) and enough information to our mix.exs
file to get the application started. In particular, it filled in the application
function:
def application do
[
mod: {
Sequence.Application, []
},
extra_applications: [:logger],
]
end
This says that the top-level module of our application is called Sequence
. OTP assumes this module will implement a start
function, and it’ll pass that function an empty list as a parameter.
In our previous version of the start
function, we ignored the arguments and instead hardwired the call to start_link
to pass 123
to our application. Let’s change that to take the value from mix.exs
instead. First, change mix.exs
to pass an initial value (we’ll use 456
):
def application do
[
mod: {
Sequence.Application, 456
},
extra_applications: [:logger],
]
end
Then, wechange the application.ex
code to use this passed-in value:
defmodule Sequence.Application do
@moduledoc false
use Application
def start(_type, initial_number) do
children = [
{ Sequence.Stash, initial_number},
{ Sequence.Server, nil},
]
opts = [strategy: :rest_for_one, name: Sequence.Supervisor]
Supervisor.start_link(children, opts)
end
end
We can check that this works:
iex> Sequence.Server.next_number
456
Let’s look at the application
function again.
-
The
mod:
option tells OTP what module is the main entry point for our application. If our application is a conventional runnable application, then it’ll need to start somewhere, so we’d write our kickoff function here. But even pure library applications may need to be initialized. (For example, a logging library may start a background logger process or connect to a central logging server.) -
For the sequence application, we tell OTP that the
Sequence
module is the main entry point. OTP calls this module’sstart
function when it starts the application. The second element of the tuple is the parameter to pass to this function. In our case, it’s the initial number for the sequence. -
There’s a second option we’ll want to add to this.
- The
registered:
option lists the names that our application will register. We can use this to ensure each name is unique across all loaded applications in a node or cluster. In our case, the sequence server registers itself under the nameSequence.Server
, so we’ll update the configuration to read as follows:
- The
def application do
[
mod: {
Sequence.Application, 456
},
registered: [
Sequence.Server,
],
extra_applications: [:logger],
]
end
Now that we’ve done the configuring in mix
, we run mix compile
, which both compiles the application and updates the sequence.app
application specification file with information from mix.exs
. The same thing happens if we run mix
using iex -S mix
.
The mix
command tells us it has created a sequence.app
file, but where is it? We’ll find it tucked away in _build/dev/lib/sequence/ebin
. Although a little obscure, the directory structure under _build
is compatible with Erlang’s OTP way of doing things. This makes life easier when we release our code. Notice that the path has dev
in it. This keeps things we’re doing in development separate from other build products.
Let’s look at the sequence.app
that was generated.
{application,sequence,
[{applications,[kernel,stdlib,elixir,logger]},
{description,"sequence"},
{modules,['Elixir.Sequence',
'Elixir.Sequence.Application','Elixir.Sequence.Server','Elixir.Sequence.Stash']},
{vsn,"0.1.0"},
{mod,{'Elixir.Sequence.Application',456}},
{registered,['Elixir.Sequence.Server']},
{extra_applications,[logger]}]}.
This file contains an Erlang tuple that defines the application. Some of the information comes from the project
and application
section of mix.exs
. Also, mix
automatically added a list of the names of all the compiled modules in our application (the .beam
files) and a list of the applications that our application depends on (kernel
, stdlib
, and elixir
). That’s pretty smart.
More on application parameters
In the previous example, we passed the integer 456
to the application as an initial parameter. Although that’s valid, we really should have passed in a keyword list instead. That’s because Elixir provides a function, Application.get_env
, to retrieve these values from anywhere in our code. So, we probably should have set up mix.exs
with the following:
def application do
[
mod: { Sequence, [] },
env: [initial_number: 456],
registered: [ Sequence.Server ]
]
end
Then, we could’ve accessed the value using get_env
. We call this with the application name and the name of the environment parameter to fetch:
defmodule Sequence do
use Application
def start(_type, _args) do
Sequence.Supervisor.start_link
(Application.get_env(:sequence, :initial_number))
end
end
Run Sequence.Server.next_number
command to execute the code below:
Get hands-on with 1400+ tech skills courses.