Learning Elixir with writing a small CLI
A short summary on my adventure making a small Elixir project as a Java developer.
You can find the full source code on my GitHub: https://github.com/veresdavid/news_cli
Motivation
I discovered the Elixir programming language around 4 years ago and I instantly felt, that I have to learn it. Coming from the object-oriented world by mostly working with Java, I found it’s syntax and philosophy very exotic and different from what I get used to.
So I started to read tutorials, watch videos about it and solve some coding katas. But it didn’t last too long. However, I didn’t completely forget about it, and every year I had 1–2 weeks, when I returned back to learning (mostly starting from the beginning due to the long break). I was kind of slowly progressing over the years.
This time, I said to myself: “Enough of playing around, let’s finally build something with Elixir!”.
Make a small project
This time I chose to use a different learning strategy. Instead of reading and watching tutorials, then trying to build something, I went with the reverse order: figure out a project idea, start building it and learn through making this project. So I was basically forcing myself to face barriers and try to resolve those by looking up specific tutorials or documentation. Of course, I already had some Elixir knowledge, but it was really rusty, so I only had a tiny bit of advantage.
Ultimately, I knew that I would like to use Elixir to build something web related, but for the sake of learning, I just didn’t want to use a complex framework like Phoenix.
So I decided to make a CLI. This is typically not the best use case for Elixir, as a CLI doesn’t need to be scalable, distributed or fault-tolerant. But to get used to the language and build something minimal, it was perfect.
News CLI
When it comes to learning and to make a pet project for the sake of learning, I usually struggle to figure out something. Something that is not too boring, but also not too mainstream (like a TODO app, even though there is nothing wrong with it).
What I knew for sure, that it would be nice to communicate with a 3rd party API from my CLI.
So with these 2 keywords in my mind (CLI and 3rd party API), I went to ChatGPT and asked for some learning project ideas and specification for them. It provided me a few options and the winner for me was: News CLI 📰
The purpose of the CLI is to call a 3rd party HTTP REST API that serves news and also to have the ability select specific news. From a user’s point of view, the usage looks something like this:
So the “user story” was provided, it was time to make things happen.
Mock API
To be honest, I didn’t want to register to an already existing News API service, just to get some API key and rate limits, to get access to some data, which is almost whatever from my point of view. I just wanted a simple HTTP REST API, that can be called, serves a minimal set of data and that’s all.
So I chose to go with the good old WireMock. Not even with a Dockerized version, just the plain JAR file, to keep things pretty simple (and because Docker was not installed on my PC at that time 😅).
Due to my choice, I also had to figure out this minimal API, so I can define my mapping files and mock responses, but it was not a big deal. An example API call looks something like this:
With my mock 3rd party API in place, next step was to actually write some Elixir code.
Custom command framework
It’s always a good practice to plan a bit ahead, so I was thinking about how should I make my implementation. As we saw it on the example CLI usage, we have subcommands (category, country, search), that serve diferent purposes. It would be nice to be able to add custom commands easily, but also don’t make it too flexible, to keep things simple. Overall, it was not my intention to build a fully fledged CLI, just a simple one that is handy for learning Elixir.
It would also be nice to make some kind of a contract, that defines how a command can work (thinking with Java in my head, something like an interface). Well Elixir definitely has something like this, called a behaviour:
This is how a command looks like in my implementation: it provides a simple function, that takes arguments (a list of strings), does the processing and then returns a result (a success or an error related one). So I can have modules, that use (implement) this behaviour and they can be composed, to build my command structure.
What was strill strange for me at that time, is that Elixir is a dynamic programming language and from some aspects, not as strict as Java. When I implement the behaviour above, I’m forced to implement the callbacks of it. But I can’t really force it to build a command structure, that only contains Command implementations. Other tools like typespecs and a Dialyzer can help to detect some type issues with static code analysis, but other than that, it is up to us developers, to avoid mistakes (by writing intuitive code, proper documentation and testing).
Request pipeline abstraction
After I figured out the command framework and started to implement the different subcommands, I realized that they are really similar and perform almost the same operations: make an API call, check reponse, extract response body, parse response body, do processing.
I wanted to keep the last step high on the call chain, but the rest of them seemed like good candidates to introduce some abstraction around. The order of the steps is given, and even though they almost identical, ultimately differ, so it would be nice to be able to somehow customize them per usecase. So for my pipeline, I came up with this higher order function:
Each input of this function is another function as well. http_api_caller performs a custom API call and returns the response object. response_body_extractor extracts the response body. It might sound too simple to have it’s own component, but the idea behind it is that it can check the response object is valid or not (status code check, header check, etc.). Finally, body_parser can convert an Elixir map (parsed from a JSON string) to a custom type. Actually, this function is further passed down, because JSON deserialization is handled by another function. With this, deserialization can be kept in a common place, why the body_parser lambda should only care about converting the parsed map to a custom type.
I don’t say that this solution is perfect, but it fits my usecase pretty good and I was kinda proud of myself after creating this reusable logic 😎Passing functions to a function just feels so nice, this is a really cool feature of functional languages.
Error handling
Elixir has this philosophy called “let it crash”. In short, this is about letting a process crash if we face an unexpected scenario, then let the system recover. This is a very interesting topic, but currently, doesn’t really fit my CLI, as I don’t work with processes.
Also, if something goes wrong (3rd party API is down, wrong response is returned, failed to parse JSON response, etc.), I don’t want to let the user there with some weird Elixir error trace. Rather, I want’t to give them some info about what went wrong.
When it comes to designing functions with the idea of error handling, Elixir choses to do either: raise an error or return a tuple indicating the outcome ({:ok, …} or {:error, …}) ⚠️
There is a try construct in Elixir as well, but I didn’t want to overuse it, while we have the opportunity to have the error tuples as well. Most of the functions that can end unexpectidly provides 2 function forms: one that can raise an error (noted with a ! at the end of function name) and another (without !) that can return error tuple. In case of an error tuple, it also gives us the error reason as the second element of the tuple. Combining it with the pattern matching feature of Elixir, it was a really good choice.
So wherever I call built-in or 3rd party functions, I always choose the one that returns an error tuple in case of error, and I also formed my functions to follow this pattern.
Throughout my code, I handle these error tuple scenarios, mostly by converting them to my inner error definitions. Each meaningful error gets represented by an atom (something like a constant), which will be handled at the top of the flow, presenting some meaningful error message to the user.
Testing
Of course I couldn’t let my code without some proper testing. Given the default testing framework ExUnit, it was pretty easy to set up my test scripts.
For me as a regular practice in Java, I was about to instantly jump in and write tests and use mocks wherever a function or module was used, that is outside of the scope of my test. So I started to look around how mocking is done in Elixir and also raised a question in the official Elixir Discord server. While mocking is definitely possible and used in Elixir as well (for example with the help of Mox), I was made aware of a different testing philosophy, to try to reduce the overuse of mocks (London vs Chicago testing). Also, José Valim, the creator of Elixir has this nice blog post about testing and using mocks: Mocks and explicit contracts 🤔
Limiting the usage of mocks is not something that I was used to, but I thought it would be a good practice, so why not? After implementing my tests, I was only using mocks around my CLI’s boundaries (in this case, the 3rd party API call) and I was left with a mixture of unit tests and integration tests. I had to admit that it was a bit strange, but I definitely see the reasoning behind it, and also ended up with a good test coverage, so it was a win in my book!
Documentation
Mostly at the beginning of my implementation, I felt weird that I don’t have to explicitly define types for data and functions, and that in some cases it is not even enforced by the compiler. Overall, I got use to it and it can also make things easier in certain situations, but I definitely felt the need to make my code more explicit.
So one way of impriving it was adding typespecs and Dialyxir. Typespecs serves documentation purposes by providing some further type information (like for functions, the type of arguments and the return type), also they will present in the API documentation. While Dialyxir can run static code analysis and detect potential bugs, based on typespecs. Cool!
I also wanted to provide a bit detailed description for my functions and generate an API documentation, so I decorated my source files with a bunch of @moduledoc attributes. The result was a nice API documentation page, which I already saw so many times (either for the base lib or for 3rd parties), but now I provided my own 🤩
Imperfect solution
In the end, the News CLI was done and I could play around with my 3 implemented subcommands!
Above, I only highlighted the key points and discoveries of my development. The rest of it was just some regular coding 🙂
I’m pretty sure, that my solution is far from perfect. I’m kind of also sure, that the current code is somewhat a weird mix of OO and functional design 😅News CLI could be a better CLI and a better functional code. But let it stand there, as a snapshot of a developers work, who is just about to get more in depth with Elixir and functional programming.
Check the source code on my GitHub: https://github.com/veresdavid/news_cli
General takeaways
Summarizing the key takeways of this short project:
- Learning things by building things is challenging but effective
- AI tools can be handy while learning new things
- Planning can help before start, but also focus on getting things done
- Don’t be afraid to try out new things and new philosophies while coding
- As a first-timer, don’t necessarily aim for perfection
- Elixir is a really nice and fun language! 🔥
Thank you!
If you get this far, thank you for reading my article! I’m planning on publishing more in the future, so stay tuned!
See ya! 👋