mf

An Introduction to Elixir Applications

In my previous articles we have discussed various Elixir terms and written a hefty amount of code. What we have not discussed, however, is how to structure and organize your code so that it is easy to maintain and release. 

Applications are very common for Erlang and Elixir and are used to build reusable components that behave as stand-alone units. One application may have its own supervision tree and configuration, and it can rely on other applications that are available either locally or on some remote server. All in all, working with applications is not that complex, and people who have come, say, from the world of Ruby will find many familiar concepts.

In this article you will learn what applications are, how they can be created, how to specify and install dependencies, and how to provide environment values. At the end of the article we will do some practice and create a web-based calculator. 

I will be using Elixir 1.5 in this article (it was released a couple of months ago), but all the explained concepts should apply to version 1.4 as well.

Applications?

Some might argue that the term “application” is not very appropriate because in Erlang and Elixir it actually means a component, or some code that has a bunch of dependencies. The application itself can be used as a dependency as well—in Ruby world we would call it a “gem”.

All in all, applications are very common in Elixir and allow you to craft reusable components while also providing easy dependency management. They consist of one or multiple modules with zero or more dependencies and are described by the application resource file. This file contains information about the application’s name, version, its modules, dependencies, and some other stuff. You may create the resource file manually, but it is much easier to do so with the mix tool that will also prepare a correct folder structure for you. 

So let’s see how we can create a new Elixir application!

New Application

To create a new application, all you need to do is run the following command:

We can also provide the --sup flag to create an empty supervisor for us. Let’s create a new application called Sample this way:

This command will create a sample directory for you with a handful of files and folders inside. Let me quickly guide you through them:

  • config folder contains a sole file config.exs that, as you can guess, provides configuration for the application. Initially it has some useful comments, but no configuration. Note, by the way, that the configuration provided in this file is only restricted to the application itself. If you are loading the application as a dependency, its config.exs will be effectively ignored.
  • lib is the primary folder of the application that contains a sample.ex file and a sample folder with an application.ex file. application.ex defines a callback module with a start/2 function that creates an empty supervisor.
  • test is the folder containing automated tests for the application. We won’t discuss automated tests in this article.
  • mix.exs is the file that contains all the necessary information about the application. There are multiple functions here. Inside the project function, you provide the app’s name (as an atom), version, and environment. The application function contains information about the application module callback and runtime dependencies. In our case, Sample.Application is set as the application module callback (that can be treated as the main entry point), and it has to define a start/2 function. As already mentioned above, this function was already created for us by the mix tool. Lastly, the deps function lists build-time dependencies.

Dependencies

It is quite important to distinguish between runtime and build-time dependencies. Build-time dependencies are loaded by the mix tool during the compilation and are basically compiled into your application. 

They can be fetched from a service like GitHub, for example, or from the hex.pm website, an external package manager that stores thousands of components for Elixir and Erlang. Runtime dependencies are started before the application starts. They are already compiled and available for us.

There are a couple of ways to specify build-time dependencies in a mix.exs file. If you’d like to use an application from the hex.pm website, simply say:

The first argument is always an atom representing the application’s name. The second one is the requirement, a version that you desire to use—it is parsed by the Version module. In this example, ~> means that we wish to download at least version 0.0.1 or higher but less than 0.1.0. If we say ~> 1.0, it means we’d like to use version greater than or equal to 1.0 but less than 2.0. There are also operators like ==, >, <, >=, and <= available.

It is also possible to directly specify a :git or a :path option:

There is also a :github shortcut that allows us to provide only the owner's and a repo's name:

To download and compile all dependencies, run:

This will install a Hex client if you don't have one and then check if any of the dependencies needs to be updated. For instance, you can specify Poison—a solution to parse JSON—as a dependency like this:

Then run:

You will see a similar output:

Poison is now compiled and available on your PC. What's more, a mix.lock file will be created automatically. This file provides the exact versions of the dependencies to use when the application is booted. 

To learn more about dependencies, run the following command:

Behaviour Again

Applications are behaviours, just like GenServer and supervisors, which we talked about in the previous articles. As I already mentioned above, we provide a callback module inside the mix.exs file in the following way:

Sample.Application is the module's name, whereas [] may contain a list of arguments to pass to the start/2 function. The start/2 function must be implemented in order for the application to boot properly.

The application.ex contains the callback module that looks like this:

The start/2 function must either return {:ok, pid} (with an optional state as the third item) or {:error, reason}.

Another thing worth mentioning is that applications do not really require the callback module at all. It means that the application function inside the mix.exs file may become really minimalistic:

Such applications are called library applications. They do not have any supervision tree but can still be used as dependencies by other applications. One example of a library application would be Poison, which we specified as a dependency in the previous section.

Starting an Application

The easiest way to start your application is to run the following command:

You will see an output similar to this one:

A _build directory will be created inside the sample folder. It will contain .beam files as well as some other files and folders.

If you don't want to start an Elixir shell, another option is to run:

The problem, though, is that the application will stop as soon as the start function finishes its job. Therefore, you may provide the --no-halt key to keep the application running for as long as needed:

The same can be achieved using the elixir command:

Note, however, that the application will stop as soon as you close the terminal where this command was executed. This can be avoided by starting your application in a detached mode: 

Application Environment

Sometimes you may want the user of an application to set some parameter before the app is actually booted. This is useful when, for example, the user should be able to control which port a web server should listen to. Such parameters can be specified in the application environment that is a simple in-memory key-value storage. 

In order to read some parameter, use the fetch_env/2 function that accepts an app and a key:

If the key cannot be found, an :error atom is returned. There are also a fetch_env!/2 function that raises an error instead and get_env/3 that may provide a default value.

To store a parameter, use put_env/4:

The fourth value contains options and is not required to be set.

Lastly, to delete a key, employ the delete_env/3 function:

How do we provide a value for the environment when starting an app? Well, such parameters are set using the --erl key in the following way:

You can then easily fetch the value:

What if a user forgets to specify a parameter when starting the application? Well, most likely we need to provide a default value for such cases. There are two possible places where you can do this: inside the config.exs or inside the mix.exs file.

The first option is the preferred one because config.exs is the file that is actually meant to store various configuration options. If your application has lots of environment parameters, you should definitely stick with config.exs:

For a smaller application, however, it is quite okay to provide environment values right inside mix.exs by tweaking the application function:

Example: Creating a Web-Based CalcServer

Okay, in order to see applications in action, let's modify the example that was already discussed in my GenServer and Supervisors articles. This is a simple calculator that allows users to perform various mathematical operations and fetch the result quite easily. 

What I want to do is make this calculator web-based, so that we can send POST requests to perform calculations and a GET request to grab the result.

Create a new lib/calc_server.ex file with the following contents:

We will only add support for the add operation. All other mathematical operations can be introduced in the same way, so I won't list them here to make the code more compact.

The CalcServer utilizes GenServer, so we get child_spec automatically and can start it from the callback function like this:

0 here is the initial result. It must be a number, otherwise CalcServer will immediately terminate.

Now the question is how do we add web support? To do that, we'll need two third-party dependencies: Plug, which will act as an abstraction library, and Cowboy, which will act as an actual web server. Of course, we need to specify these dependencies inside the mix.exs file:

Now we can start the Plug application under our own supervision tree. Tweak the start function like this:

Here we are providing child_spec and setting Sample.Router to respond to requests. This module will be created in a moment. What I don't like about this setup, however, is that the port number is hard-coded, which is not really convenient. I might want to tweak it when starting the application, so let's instead store it in the environment:

Now provide the default port value inside the config.exs file:

Great! 

What about the router? Create a new lib/router.ex file with the following contents:

Now we need to define a couple of routes to perform addition and fetch the result:

We are using get and post macros to define the /result and /add routes. Those macros will set the conn object for us. 

ok and fetch_number are private functions defined in the following way:

fetch_query_params/2 returns an object with all the query parameters. We are only interested in the number that the user sends to us. All parameters initially are strings, so we need to convert it to integer.

send_resp/3 sends a response to the client with the provided status code and a body. We won't perform any error-checking here, so the code will always be 200, meaning everything is okay.

And, this is it! Now you may start the application in any of the ways listed above (for example, by typing iex -S mix) and use the curl tool to perform the requests:

Conclusion

In this article we have discussed Elixir applications and their purpose. You have learned how to create applications, provide various types of information, and list dependencies inside the mix.exs file. You've also seen how to store the configuration inside the app's environment and learned a couple of ways to start your application. Lastly, we have seen applications in action and created a simple web-based calculator.

Don't forget that the hex.pm website lists many hundreds of third-party applications ready for use in your projects, so be sure to browse the catalog and pick the solution that suits you! 

Hopefully, you found this article useful and interesting. I thank you for staying with me and until the next time.

Powered by WPeMatico

Leave a Comment

Scroll to Top