# A potion of experience

You may notice that this blog has just been enriched, especially if you're following my Mastodon account. See that thing all the way past the article? It's a comments section. Cause I want to have a conversation with my readers, rather than keep shouting into the void.

You can surely see it's pretty basic.

I made it myself. See, I had my eye for a while on this elixir that would give me some network development experience, and I decided to use it. I had to supplement it with some extra stuff to get anything more than *just* experience, but I'll talk about that later.

## Glug glug

It's not a mystery: it's just Elixir. It's a language running on the BEAM virtual machine, and taking advantage of the Erlang/OTP runtime. OTP has fascinated me for a long time, with its "crash-only" approach, a concurrent take on the Actor model, and aggressive immutability.

In short, it's a programming paradigm outside of the orthodoxy I've known. And what better source of new insights is there than an unusual point of view?

Disclaimer: I'm still rather new to Elixir, so if I mess up some terminology, let me know… in the comments ;)

## Level up

The feature of Elixir that I liked the most comes from Erlang/OTP: it's the actor model. Your program gets split into… uh, "applications", which operate as independent threads, and can exchange messages. For example, the web module sends a message to the email module to let me know I should approve a new comment, and doesn't even wait for an answer.

If the email sender crashes, the web service keeps churning on, and only the email component gets restarted. And I don't even need to care about the restart logic!

That's already cool, but what if I want extra logic in the email sender? I don't want to be spammed by a lot of emails when my blog reaches the front page of Hacker News, but rather have the email module wait for 5 minutes between notifications.

Immutability then changes the rules of the game: you can't just save the time of the last email in a local variable, and handle messages in a loop. There's a kind of a local database for each application, where data can be explicitly stored. One thing comes to another, and I implemented a state machine to deal with the notifications.

And that's where I levelled up: I stated using state machines, which exchange messages to cause transitions. It's an excellent organization to debug, because you can serialize the state of your module, and see how exactly external events cause changes in the state. If you add a little more effort to turn side effects into more messages, you can perform "in vitro" state transformations as part of your test suite.

## Bitter taste

Elixir is not all good though. The one especially bittersweet part is how much metaprogramming it allows. The basic syntax ends up being simple (or so I'm told), but once you actually start using Elixir, expect surprising syntax constructs. Here are a couple of examples that keep confusing me.

Most statements are contained between `do` and `end`, like this:

if true do
  x = x + 1
  y = y + 1

But not stuff inside branches of a `case` expression:

case foo do
    true ->
      x = x + 1
      y = y + 1
    false ->
      x = x

I feel uneasy each time I write this. What's the delimiter marking the end of the `true` branch? Note that indentation doesn't matter here.

Another annoying property of some Elixir libraries is spooky action at a distance, where the reasons for doing something are implicit and hidden away behind layers of abstraction. Here's an excerpt from the Plug library tutorial:

defmodule Example.Router do
  use Plug.Router

  alias Example.Plug.VerifyRequest

  plug Plug.Parsers, parsers: [:urlencoded, :multipart]
  plug VerifyRequest, fields: ["content", "mimetype"], paths: ["/upload"]
  plug :match
  plug :dispatch

  get "/" do
    send_resp(conn, 200, "Welcome")

  get "/upload" do
    send_resp(conn, 201, "Uploaded")

  match _ do
    send_resp(conn, 404, "Oops!")

Take a look at the `plug` lines. They will take care of encoding and verification. It's nifty because you don't have to worry, just slap those in. It's confusing because if you're a newbie and want to stay near the basics, those constitute a barrier. It's opaque, because there doesn't seem to be a syntactical opening to declare some calls to be affected by those filters.

In the end, I avoided the problem by never using such clever tricks, at the cost of having less learning material (not that the material with tricks taught me anything).

In the end, it comes out as a mostly positive coding experience, and I'll choose Elixir over Django in the future.

But coding the web app is not all.

## Hangover

After coding, I had to deploy it on my server somehow, or else experience is all I get. This grew to be a full half of the experience, and a half I'd rather do without.

Let me start with Ansible.

I don't like it.

It disappoints me in one crucial area: it does not compose. I can't easily create Ansible instructions to deploy Beng on a pristine system for developers, and then reuse the same instructions to deploy it on my infra alongside other pages. Isn't that what programming languages are good at? Executing batches of instructions differing by parameters? Perhaps I'm too much of an Ansible noob, but I haven't found the necessary flexibility there.

So I wrote a couple idempotent shell scripts to replace Ansible.


Back to Elixir - it turns out that the binaries need a certain version of the Erlang runtime to be present on the destination system. CentOS 7 didn't have the same version as my development machine. I couldn't build them in a CentOS container either.

Long story short, I gave up on CentOS and went with Nix to build the comments app.

The upside is that if I want, NixOS can take over a lot of what I needed Ansible for: building the software, configuring it, installing dependencies. The downside is, when I came back to the app 6 months later, my Nix package utterly and completely doesn't build. I'm not so sure about employing Nix for server config duties now.


My shell scripts were still needed to move the data and configs from the development machine to the server. Step by step, they grew into something bigger. Something monstrous. Something like… Ansible? But with some neat features that Ansible doesn't have:

Sadly, it doesn't compose much better. I still have some hardcoded things I don't want to share with the world, so it will remain unpublished for now.

## Aftermath

The Elixir app and the deployment sweat were a good lesson in humility. Originally, I estimated the whole thing to take a week. It took two weeks of intense work across several months. Last week I estimated the deployment portion to take an evening. That alone took a week. But now I have something to show for it, and the lessons I learned from Elixir levelled me up as a programmer, so… I guess it was worth it.

Written on .


dcz's projects

Thoughts on software and society.

Atom feed