{:ok, result} with Elixir

Frank McGeough
5 min readMar 19, 2018

--

I’ve had an enormous amount of fun using the Elixir language to build apps over the last couple years. One of the reasons is how productive the language is. In particular how useful pattern matching is — in all sorts of situations — in allowing the expression of what I think is clear and easily maintained code. One of the patterns that you see over in over in Elixir (or Erlang) code is a function returning {:ok, result} or :error. This makes the code to handle error conditions look cleaner and I think it’s easier to absorb than what I’m used to seeing in other languages. I’ve extracted a small example from one of our apps of this type of pattern.

Expected vs Unexpected

One of the key points and real joys of Elixir and the Erlang/OTP system on which it is based is its ability to self-heal. The general rule in coding in these languages is to concentrate on the “happy path”. If the unexpected happens then the Erlang process (extremely light-weight, easy and fast to create/destroy) exits and can be restarted by a Supervisor of that process. This technique recovers from problems that occur only in production and are extremely rare and transient. There is a great write-up on this called “The Zen of Erlang”.

One of the important points about this approach is that it is meant to handle unexpected/transient problems. When you are building your software it’s important to consider what constitutes an expected problem. Expected problems should be handled in your code. A good example is calling a third-party API. Unless you believe that the third-party API is 100% reliable you should expect that you’ll get occasional errors when calling the API. It is probably unreasonable to expect that if an error is returned from the API that it will go away when your process restarts. What if the third-party is doing some routine maintenance that requires a 10 minute outage? What if they’ve had a data center issue and are down hard?

Background

We’ve developed a web app in Elixir at Vonage that aims to fill in gaps with AWS Console. It collects data related to instances, volumes, security groups, load balancers, and more by calling AWS APIs. In developing our web app we’ve made use of a very useful Elixir library called ex_aws that provides a service framework for implementing AWS service APIs. Since Elixir is a relatively new language, there were a number of services that that hadn’t yet been implemented. When we found any that we needed that were missing we went ahead and implemented those ourselves. One of the recent ones we implemented was CodeDeploy.

CodeDeploy is an AWS service for software deployments. There were some groups within our company using this. Because folks were using it we wanted our web app to have the latest information on what instances have been modified by what applications for particular environments via CodeDeploy. This is useful for a couple of purposes. For dev or testing environments we wanted folks to easily see what instances have been modified and when. For the production environment we wanted to provide some tracking reports on modified instances that various groups needed.

Some of the CodeDeploy API’s are paged. This means that when you call the AWS API it returns a subset of the total results and gives you back a nextToken that you can use to retrieve another page worth of results. You can continue to call the API with the new nextToken until the final page comes back and there is no nextToken.

Any AWS API call may fail for a variety of reasons. One of the common issues is that you’ve exceeded your threshold for API calls (ex_aws actually has an exponential back-off so you can avoid these type of problems getting all the way back to your code but it can still happen). Other issues might be disastrous Amazon issues or, more benignly, network problems. In any case it's not a great idea to have your process exit if an expected problem occurs.

Code

The code below is an example of how we handle paging in calling the AWS APIs. We started with defining what results we wanted back from our list_instances function call. Since errors could occur we decided that we’d return a tuple (an Elixir composite data type with a fixed number of elements) with two elements. The first would be the result of the call (:ok or :error) and the second would be the list of the actual data we wanted (instance ids in this case). In the case of :error we’d return an empty list.

The code works by calling the AWS API to get the initial set of data. Regardless of whether that returns :error or :ok we call handle_paging. We get the nextToken (this will evaluate to nil if there is no nextToken), and the set of instance ids. We pass in the result from the first call and then pattern match on the parameters.

Pattern matching in Elixir works from top-down in the module. The first pattern that matches “wins”. In this code we have 3 handle_paging functions. The first match occurs if the result of the operation was :ok and there are no more pages of data (next_token parameter is nil). In this case we return {:ok, acc}. acc is the accumulated list of instance ids. The second match takes care of the error situations. In the case of :error we just want to return {:error, []}. This lets the caller of the function know a problem occurred. The third match is for the situation where we have paging. In this case we call the AWS API again but with next_token set and then recursively call handle_paging while concatenating the instance ids into our accumulator.

That’s it

These concepts — recursion and pattern matching, and :ok/:error — are common patterns that you’ll see in Elixir code. It’s one of the reasons the code can be delightful to work on. To me it is easy to digest.

One of the important things to think about when coding your own modules is to handle errors that are expected. You have to determine what is unexpected/transient for you. If you handle errors you should provide an easily consumable pattern for code that is upstream from what you are writing. Returns tuples in Elixir and the :ok/:error pattern make it easy to do this.

--

--

Frank McGeough

Dad, software guy, amateur guitarist, cook. ABR (always be reading)