Turning Our Sequence Program into an OTP Application

So, here’s the good news. The application here 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 will pass that function an empty list as a parameter.

In our previous version of the start function, we ignored the arguments and instead hard-wired 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 change the application.ex code to use this passed-in value:

otp-app/sequence/lib/sequence/application.ex
 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​​ ​​-S​​ ​​mix
 Compiling 5 files (.ex)
 Generated sequence app
 iex>​ Sequence.Server.next_number
 456

Let’s look at the application function again.

The mod: option tells OTP the module that is the main entry point for our app. If our app is a conventional runnable application, then it will 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 app, we tell OTP that the Sequence module is the main entry point. OTP will call this module’s start 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 name Sequence.Server, so we’ll update the configuration to read as follows:

otp-app/sequence/mix.exs
 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 app 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.)

 $ ​​mix​​ ​​compile
 Compiling 5 files (.ex)
»Generated sequence app

Mix tells us it has created a sequence.app file, but where is it? You’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 you release your code. You’ll notice that the path has dev in it—this keeps things you’re doing in development separate from other build products.

Let’s look at the sequence.app that was generated.

otp-app/sequence/_build/dev/lib/sequence/ebin/sequence.app
 {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 app. Some of the information comes from the project and application section of mix.exs. Mix also automatically added a list of the names of all the compiled modules in our app (the .beam files) and a list of the apps our app 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(ish), 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

 def​ application ​do
  [
 mod:​ { Sequence, [] },
 env:​ [​initial_number:​ 456],
 registered:​ [ Sequence.Server ]
  ]
 end

and then 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

Your call.