![]() |
By Benjamin Tan Wei Hao
In this article, excerpted from my book The Little Elixir & OTP Guidebook, I introduce you to OTP behaviours. |
Think of OTP behaviours as design patterns for processes. These behaviours emerged from battle-tested production code, and have been refined continuously ever since. Using OTP behaviours in your code helps you by providing you the generic pieces of your code for free, leaving you to implement the specific pieces of business logic.
Take GenServer for example. GenServer provides you with client/server functionality out of the box. In particular, it provides functionality that in common to all servers. What are these common features? They are:
- Spawning the server process
- Maintaining state within the server
- Handling requests and sending responses back
- Stopping the server process
GenServer has got the generic side covered. You, on the other hand, have to provide the business logic. The specific logic that you need to provide include:
- The state that you want to initialize the server with
- The kinds of messages the server handles
- When to reply to the client
- What message to reply to the client
- What resources to clean up after termination
There are also other benefits. When you are building your server application for example, how would you know that you have covered all the necessary edge cases and concurrency issues that might crop up? Furthermore, it would not be fun to have to understand different implementations of server logic.
In my programs that don’t use the GenServer behaviour, I usually name the main loop, well loop. However, there is no stopping anyone from naming it await, recur or even something ridiculous like while_1_true
. Using the GenServer behaviour releases me (and more likely the naming-challenged developer) from the burden of having to think about these trivialities.
The Different OTP Behaviours
The following table lists the common OTP behaviours that are provided out of the box. OTP doesn’t limit you to these four. In fact, you can implement your own behaviours. However, it is imperative to understand how to use the default ones well because they cover most of the use cases you will ever encounter.
Table 1. OTP Behaviours and the functionality they provide
Behaviour | Description |
GenServer | A behaviour module for implementing the server of a client-server relation. |
GenEvent | A behaviour module for implementing event handling functionality |
Supervisor | A behaviour module for implementing supervision functionality |
Application | A module for working with applications and defining application callbacks. |
To make things more concrete, we can see for ourselves how these behaviours fit together. For this, we need the Observer tool, provided by OTP for free. Fire up iex, and start Observer:
Listing 1 Launching the Observer tool
% iex
iex(1)> :observer.start
:ok00
When the window pops up, click on the “Applications” tab. You should see something like this:
Figure 1 The Observer tool displaying the supervisor tree of the Kernel application
In the left column is a list of OTP applications that were started when iex was started. Each option in the left column reveals the supervisor hierarchy. For example, the above diagram shows the supervisor hierarchy for the kernel
application, which is the very first application started, even before the elixir application starts.
If you look closely, you will notice that the supervisors have a sup appended. kernel_sup
, for example, supervises ten other processes. These processes contain both Supervisors and GenServers.
Behaviours like the GenServer and GenEvent are the workers. They contain most of the business logic, and do most of the heavy lifting. Hopefully, this has given you a high level overview of how the OTP behaviours fit together.