Elixir modules, files, directories, and naming conventions
Thank you my friend Anton for the awesome revision. ❤❤❤
Hello everyone! Here’s another blog post about Elixir. Today, let’s talk about naming and organising Elixir project modules and files. Then, I’ll try to summarise some conventions that I have seen out there and hopefully help you or your team with your own rules. I like this subject because good project structure and patterns are excellent for future maintainable code. Enough of introduction; let’s see what I have picked up.
The new command
Before starting an Elixir project, you need to think about how users will interact with your app. By knowing that, you might need to choose a framework. And every framework has a project initialisation command that will help you with the initial directory structure. Some examples:
Interaction type | Framework |
---|---|
Web applications | Phoenix |
IoT | Nerves |
Rich interactive CLI | Ratatouille |
Desktop app | Scenic |
A library for other apps | Mix (default) |
If you know what you need for your web application, you might only need Plug, not the whole Phoenix. Mix, the built-in Elixir build tool, will be present no matter the framework you choose.
Get intimate with the initial files and structure generated by your framework of choice. Understand it well and try to stick to it. It helps new people when joining your project get familiar quickly.
For the rest of this blog post, I’ll try to be as framework agnostic as possible. So bear with me, and let’s take a look at some everyday things.
Lib, config, and test directories
Most Elixir projects have lib, config, and test directories. In your lib directory is where your application production code goes. The config directory hosts the build and runtime configurations. The test directory contains the scripts that test your code. Straight forward, isn’t it?
Why .exs for tests and config scripts?
If you didn’t notice, your lib Elixir files go with .ex extension, while your test and config scripts go with .exs. Why? The .ex are files that mix will compile before a run. After the compilation, the generated compiled files will have code optimisation ready to run how many times you want. The .exs are scripts that will be interpreted every time you run, so there is no code optimisation. Why don’t you want an optimised config or test code?
The first reason is that you don’t need it. The test and config scripts run once in a while, on your machine or CI. Your application users will not run your test or configuration scripts many times per second. So your test code doesn’t need to be optimised by the compiler. But would that optimisation be beneficial? Make your development experience faster? Potentially not.
Compiling your test and config might slow down your development workflow. The build phase happens before your code runs, it compiles all files with .ex extension before any code is executed. If all test scripts have the .ex extension, they all need to be compiled before running a single test script. However, test scripts are independent of the other, so you could run a single file or multiple files without worrying about compiling all test scripts first. Things can get worse. Multiple test scripts can depend on the same application module. So a single change in one application module, could have a ripple effect of having to recompile multiple test scripts. All that before running a single test. Adding burden to your development feedback cycle. So, Elixir scripts using the .exs extension are a better kind, since they don’t make part of the build phase.
config/config.exs and config/runtime.exs
In the current state of Elixir (v 1.13.2 at the time of this post), all your application configurations should live in the config directory. The config.exs
file is the build configuration and is required during compilation. Good for things that add or remove code depending on configuration value. For example, Logger can purge functional calls below a certain level. On the other hand, the runtime.exs is for configuration values loaded during the application initialisation. These runtime values can change how your app behaves or serve as parameters to access some third party system. The runtime.exs
is ideal for reading system variables to configure the database access or API service during app startup.
The priv directory
The priv directory is where you put the static files you want to access on your release. For example, images, translation files, compiled and minified frontend assets, database migrations, or seed data to load during app boot.
/lib/your_app_name/application.ex
When an Elixir project has an initial supervision tree, the application.ex
file usually is the place where it sits.
Module naming and file naming
Module names in Elixir follow the UpperCamelCase convention. And then, you usually write a module per file, and the file name has the same name as the module but uses snake_case as a convention. Why? I don’t know. My guess is when reading an Elixir code, the CamelCase makes the module jump on your eyes. While snake_case makes it more friendly for Operating Systems(OS). You don’t need to worry about case sensitiveness divergences that can happen when looking for a file on the diverse OS ecosystem. The Elixir core team might have an exact technical or philosophical reason. If you are reading this and you know the answer, could you let me know? Pretty please? :3
I make a few exceptions, such as acronyms, prefer to keep it all uppercase, so I like HTTPClient, not HttpClient. I know it makes it harder to see where HTTP ends and where the Client starts, but I prefer to be explicit that HTTP is an acronym. It’s up to you and your team to decide what’s best.
Files location
The most common convention is that your so file directory system should reflect the namespacing of your modules. So if you have a module named NextSocialNetwork.User
, that module should live in a directory like lib/next_social_network/user.ex
. Pretty easy to follow, right? Each test file usually has a code that tests a single module, and they should live under the test
directory following the same structure of your lib
.
This is the gold convention you should always follow, but I have a few exceptions, and I’ll explain why. Hold my hand.
Contexts directories and files
With Phoenix ~1.4 the context’s topics have become a big thing in the Elixir community. But if you think about it, it wasn’t something super new at all. The whole idea is to have modules that work as an entry point for your app’s business. Then, all sub-entities for that aspect live there, on inner files and directories. It’s like your lib has sub-libraries. Then your sub-library has a main interface that lives on an entry point module, but it’s also okay if your sub-library exports some other modules that work as types or data structures.
Here’s an example: you need to authenticate users on your app. But your app has many ways to do that, like using their password or their Boogle or FadeBook accounts. Each mechanism can be a bit complex and change for different reasons, so it might be a good idea to split each mechanism in its own module but have a standard interface. So you can end up directories like this:
And your modules would look like:
NextSocialNetwork.Authentication
NextSocialNetwork.Authentication.Boogle
NextSocialNetwork.Authentication.FadeBook
NextSocialNetwork.Authentication.Password
Here’s my first exception for the golden convention of file location. I usually prefer to organise these files this way:
My main reasoning is thinking about the deletion and moving. If I want to move a context or sub-app to another directory, I want all its files to move together. But with authentication.ex
living outside of the folder, I have to do an extra action to move the entry point together. Another issue is when you’re using an interface to explore your project files, like Visual Studio Code, and your project is big enough, the entry point module ends very far from its context directory. For example:
I know it will look a little bit strange using the terminal, authentication/authentication.ex
. But that strangeness already happens if you look at Elixir’s initial config structure, e.g.: config/config.exs
. I see the entry point modules purpose working like an index file in a web server directory, and usually, the index files live in the directory as children, not as siblings.
Elixir files that fit a category and aren’t a context
Some kinds of files fit in one or more categories and do not belong exclusively to context. For example, errors, your app might have several modules that represent errors. Some people like to create namespaces and put these files under that. Others prefer to make a module suffix. Let’s look at each approach and how you can organise it.
Namespacing (maybe you should avoid)
Let’s say you have multiple errors in your app: argument, arithmetic, not found, etc. If they are all errors, they should belong to an Error
namespace, right? So you would have:
MyApp.Error.Argument
MyApp.Error.Arithmetic
MyApp.Error.NotFound
MyApp.Error.TemporarilyUnavailable
What does Arithmetic
have in common with TemporarilyUnavailable
? Almost nothing. What ties them together is the only characteristic of being errors. I don’t think it’s the optimal way of naming your modules. It’s not just me; the Elixir Core team also doesn’t do that. Look at the Exceptions section on Modules, so maybe you shouldn’t do it too.
If namespacing isn’t an optimal approach, what is it? I don’t think we have a perfect solution, but let’s look at the alternative.
Suffix
Instead of creating a namespace, you can use a suffix. So using the previous example, your modules naming would look like this:
MyApp.ArgumentError
MyApp.ArithmeticError
MyApp.NotFoundError
MyApp.TemporarilyUnavailableError
So they aren’t tied anymore to the unhelpful namespacing and concept “Error”, now you’re free to move them to sub or parent applications as the time needs. This naming convention is widespread on phoenix apps, where you have controllers, views, channels, helpers. You see the modules named UserController
, AccountView
, or BreadHelper
. You don’t see Controllers.User
, Views.Account
, or Helpers.Bread
. The Controllers
is a weak category that doesn’t tie well two business concepts like User
and Bread
.
Suffix and directory?
As long as your project grows, these modules that aren’t together conceptually might pollute your OS directories and make things hard to navigate. So it’s okay to organise your OS files and directories in a way that works better. So, for example, in a Phoenix web directory, we can see something like:
And the module is named MyAppWeb.UserController
, and not MyAppWeb.Controllers.UserController
. So your modules naming doesn’t need to be a mirror of your OS directories. It’s good when it can, but we don’t need to ruin their naming for the sake of the OS system. So in my mind, it started as a strange thing, but today I accepted this, and it’s fine to make a few exceptions to the golden convention rule.
An ideal OS directory for software would be one in that I could tag each file with multiple labels or categories. So I could rearrange them as much as I wanted. If you know something that does that, let me know.
That’s all for today. I tried to summarise the most common project organisations that I have seen some reasoning behind, and I hope it helps you understand and organise your Elixir projects.