Turning Our Sequence Program into an OTP Application

Learn how to change our previous sequence project into an OTP application.

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’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:
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.