In this article, excerpted from my book The Little Elixir & OTP Guidebook, I introduce you to OTP behaviors.
Think of OTP behaviors as design patterns for processes. These behaviours emerged from battle-tested production code, and have been refined continuously ever since. Using OTP behaviors 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 behavior, 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 behavior releases me (and more likely the naming-challenged developer) from the burden of having to think about these trivialities.
The Different OTP Behaviors
The following table lists the common OTP behaviors that are provided out of the box. OTP doesn’t limit you to these four. In fact, you can implement your own behaviors. 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 Behaviors and the functionality they provide
A behavior module for implementing the server of a client-server relation.
A behavior module for implementing event handling functionality
A behavior module for implementing supervision functionality
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 :ok
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.
This article is excerpted from The Little Elixir and OTP Guidebook. Save 39% with code tanweihao39 at manning.com.