2024 in review


This year I decided to do a quick professional retrospective. It’s easier to plan the road ahead when you also have an eye on the rear-view mirror!
I can group most of my highlights of 2024 in 3 categories:
Becoming a technologist
Unison
The small stuff
Becoming a technologist
I have always thought about myself as a “Software Engineer”. My main activity was to produce proper code forming reliable systems, and culminating in desirable products. 2024 marks a shift where I learned to become a technologist. What do I mean by this?
It turns out that many issues pertaining to software have largely been solved:
Provision machines to execute applications.
Persist vast amounts of data.
Run distributed systems reliably.
Identify users and control access.
Analyze data.
etc…
Many of these solutions are available as services, under the AWS umbrella for example:
When you build software products it would be foolish to not stand on the shoulder of giants and not reuse, or integrate with, those services. The consequence is that you spend a lot less time programming, and a lot more time configuring. I think that the right question to ask is “can we go back to programming?” and will try to give an answer after a few examples.
What’s going on with my users?
Getting proper feedback is essential to understanding if your product is going in the right direction. At Ockam, we enable users to securely connect any system to any other system, without having to open any internet ports. Our command-line executable, ockam
, supports many commands to establish what we call portals and manage them (by creating cryptographic identities, permissions, etc…).
We want this experience to be as seamless as possible and, if we can, be proactive in assisting users with any issue that they might face. We tried to leverage OpenTelemetry traces in order to gather as much actionnable data as possible:
Which command is invoked?
What are the response times in various parts of our pipeline?
What errors might be raised?
Are there frequent command-line errors?
Are there some stuck users who can’t create a portal for some reason?
My job has been to instrument our command-line tool to structure this data, collect it, and slice it to:
Show the “journey” of a user.
Being able to track down issues to relevant traces and log messages.
The “structure” / “collect” / “slice” parts were not entirely under my control: OpenTelemetry is a standard with a strict (and large!) specification, and there are many offerings for collecting and aggregating telemetry data. This is where the technologist part comes in. Most of the work consists in:
Understanding the data model and behavior of a system that someone else built.
Put the right information at the right place.
Respect constraints established by someone else (maximum number of spans for a trace for example).
At a reasonable cost! Yes, this is also a dimension to optimize for and we’ve all heard of horror stories with software bills gone wrong.
I could probably make quite a long blog post around this piece work but I am going summarize my learnings here:
What we want to do is not really supported by any vendor. Many services focus on aggregating data and being able to drill down on an error. What we want to see is the most relevant events for each user.
OpenTelemetry is a bit of a bloated specification in need of a radical change (tl;dr “wide structured events” instead of traces/logs/metrics).
I had UML vibes from the past, realizing that the same standard was not supported in the same way by different vendors.
Testing and non-regression is difficult. How do you check that events are still properly sent and aggregated across your service and someone else’s?
Visualization of data is non-trivial and the complexity of the OpenTelemetry data model does not help.
The existence of a generic “collector” where you can send all the traces centrally, and then dispatch to the backend of your choice is a huge benefit. A good example of interface/implementation separation at the system level!
That being said, and despite the existence of the OpenTelemetry specification, each new vendor comes with its own configuration, project model, users model, visualization tools, query language, etc… It’s almost like starting from scratch every time.
Costs are definitely a factor. Are we getting the most out of a given service? (answer: not quite).
We’re all snowflakes
The other big piece of technology that I worked on this year is Snowflake. In summary Snowflake is a platform allowing you to import huge amounts of data and process it without having to worry about dealing with any infrastructure. A giant database and application environment at your fingertips.
Ockam is a particularly good fit for Snowflake since we allow users to import data from their private systems without exposing them on the internet, and then export analyses back. In order to help customers get started with instant connectivity to Snowflake we built a set of connectors to import/export data from/to Kafka, Postgres, SFTP, WebDAV.
This is very much an exercise in assembling technologies:
Learn about the Snowflake model: Warehouses, database, tables, streams, users, organizations, etc…
Learn about the deployment model of Snowflake: versions, patches, deployment directives, cloud regions, marketplace, accounts…
Assemble the software as docker images with specific restrictions on networking. Juggle security restrictions to configure our own service.
Use Python to develop small clients.
Use Javascript to implement some stored procedures.
Use SQL (Snowflake’s version of SQL) to write queries.
Configure a myriad of YAML files.
Use Streamlit to develop small configuration applications.
Deploy Kafka locally via a docker image. Use docker compose to also deploy our own service.
Click on many buttons, many, many times.
We eventually got those connectors to work after a lot of head-scratching but there is a stark contrast with traditional software development:
You have to fit in the vendor’s model. That model contains a lot more information than you actually need for the specific job you have. It’s on you to sort it out.
Many attempts can be necessary to understand exactly how that model behaves. Many things are left unspecified and you discover them only by trying.
The user experience can be limited, and fixing it is out of your reach (we can’t get users table metadata to display source/target mappings in our connectors for example).
Automatic testing in general is out of question, that would be a massive project in itself.
There is almost no way to compile or typecheck anything:
Configuration happens by accident. Not the right name is the right place? Wait 5 minutes to see your service crash after deployment.
Streamlit uses Python. Change some shared UI code somewhere and see silly and avoidable bugs somewhere else at runtime (if you happen to manually re-test all your applications…).
Who are you? A solved problem?
The last piece concerns my use of Auth0. Signing-up / Signing-in a service is such a ubiquitous and critical requirement that it makes sense to found a company around it (and it’s not a simple as it sounds).
At Ockam, we want to give the best onboarding experience to our users and, instead of re-inventing the proverbial wheel, we use Auth0. Once more my job was step away from the programming mode, to enter the configuring mode. At first glance, it’s not that bad:
We can customize the sign-up / login screens.
We can add some pieces of code to intercept and act on parts of the process (deny some email domains for example from signing-up).
We can monitor major events and errors.
Unfortunately, using Auth0 is not devoid of problems:
A user trying to sign-in with the sign-up page will be rejected, even if their credentials are correct. There is no way so far to fix this UX issue.
There are many parameters to configure, the effect of some of them is not clear. One example: if you use a custom URL to present your Auth0 service, the URL domain will be automatically picked-up for some redirects. You can’t control that.
By default, there’s no version control of your configuration. Fortunately there is a Terraform provider, allowing you to save all your configuration as, horror,… more YAML files. But there’s no guarantee that the Auth0 UI uses the last version of your configuration.
Special mention: ChatGPT
2024 is the year when I really started to use AI. While I haven’t used it much for coding, I have extensively used ChatGPT to solve all sort of issues when dealing with technologies new to me. And I am not the only one:
I must say that the ability to have a conversation with a knowledge base about a given problem is really a game-changer.
Where do we go from there?
Don’t get me wrong, I think it’s great to be a technologist. More than ever, we have to possibility to create incredible systems by building on top of amazing services. My biggest complaint is that, when building in the large, we lose all the good tools we have when building in the small:
Version control.
Typechecking.
Linting.
Unit / Integration tests.
Code completion / code navigation across libraries.
Specifications / models for our code.
Dependency injection.
Integrated documentation.
Rich development environments and terminals.
Dependency bots.
Hot reloading.
Notebooks.
Deployment as code.
CI / CD.
Note that the situation above is far from perfect in the small! There’s no actual environment where all those features are available at the same time by default. We can get somewhere near this picture by, once more, becoming technologists and assemble all tools we need (with the same issues as before).
I think that we can massively improve the situation by fundamentally make everything look like code. Especially code that deals with code:
Deployment code: to associate services with resources.
Observability code: to define how we want to monitor our systems.
Testing code: to assert properties of our code.
Documentation code: to give executable examples.
Configuration code: to describe how the code is assembled.
Graphical code: to represent and modify the configuration in a 1-1 correspondence.
This leads me to my second highlight for 2024.
Unison
Around April I decided to take the plunge and learn more about the Unison programming language and platform. I explain some of my motivations in “What is so unique about Unison?”, but in retrospect a huge motivation is present in what I wrote above about “code everywhere”:
Unison services are deployed using Unison code, monitored using Unison code.
The documentation is type-checked and executable.
Dependencies are tracked very finely so that tests can be cached.
There’s is not data to serialize / deserialize between services or persistent storage. It’s all just Unison terms.
Step 1
My overall goal for this year was to write and deploy a service to export some bank transactions from one of my bank accounts and import them into my accounting software. I started by writing a modest MultiMap
library to get a better feel for the development cycle.
Step 2
When I first tried to connect to my bank, via HTTP, I quickly realized that Unison was missing some cryptography primitives (RSA) to sign some payloads. Time to re-open my Haskell toolbox and contribute to the language itself! (+ a bit of glue on the base library side).
Step 3
I then realized that having a command-line parsing library would be nice in order to run my service with different arguments, in particular API keys and account numbers which I did not want to store as term.
That sounds antithetical with my “everything as code” objective and it is! Still, I took this huge detour to:
Create the potions library for parsing command-line options.
Talk about it at the Unison forall conference, because this is an interesting problem to solve and a good example of how to use some Unison features to design a library.
In retrospect, I think it would be a good idea to make a small library for embedding encrypted data, so that one key only can unlock the whole configuration of an application as Unison terms, and make that a secret in a Unison Cloud Environment
.
Step 4
I was finally able to come back to my application, write some code to make HTTP queries, retrieve transactions, decode them from JSON (shocking, not everyone is using Unison yet!), write a bit of logic to create accounting transactions and post them to my favorite accounting software, YNAB (thanks for having an API!).
That led me to another pet peeve of mine. I think that we are plagued by the lack of dependency injection in our industry. We are sometimes lucky to have nice compositional software, thanks to functional programming, so we know how to assemble code. But when it comes to say “give me the same piece of code with one slightly different bit” we just don’t have the words.
Configuration is a good example of this. We use all sorts of tricks to say “I want the same configuration but change x and y for the development environment”: environment variables, “overlays”, scripts to write YAML, templating languages, etc…
I think that we should recognize this problem as a dependency injection problem, and have the possibility to replace any piece of a software system, from the billing component, to an API key. Ideally the language used should be the same as the programming language. This is not a big ask, in principle we just need a bit of meta-programming to do so. Here is an example on how to support DI in Haskell. An added benefit is that we automatically get a picture of the system structure because we have “code about code” at our disposal.
Given that my application was starting to grow (with 13 components in different configurations for local our Cloud testing), I set out to write a library to support dependency injection in Unison. This is still work in progress (I use it but it’s undocumented for now) but I’ll have the opportunity to share more about it next year.
And now?
My application is still not online, and I can only blame myself because I just took another detour (one more time against the spirit of “everything as code”, I’m not so consistent there) since I implemented a TOML parser (to be released soon). That was a great exercise in parsing but also in exploring the complexity of what is supposed to be a simple configuration language (I hope to blog about that experience separately).
When that is done, I will finally deploy my application in “production” (I’ve only deployed it and made it run for a limited amount of time for now).
All in all, it was a great year for my use of Unison. I think that this language has both niche and tremendous potential, if that makes sense! And special mention to the Unison team, you’re all amazing!
Tidbits
In no specific order, here are some other thoughts about 2024 from a tech side.
Flox
I have been using flox, my favorite package manager, for 2 years now. I really like the peace of mind that Nix brings to the table for managing my environments, with the very reasonable set of command-line instructions that Flox provides. This year brought services, easier CI/CD, installing from flakes, better support for shells, and many, many fixes and improvements. Many thanks to the Flox team for their great support, even with my sometimes convoluted configuration!
Elixir
I started using Elixir + Phoenix (Web framework) + Ecto (Persistence framework) at work. I really appreciate:
The functional programming angle.
A working REPL with hot-reloading.
Hot-reloading also in Phoenix.
On the other hand, I am still not a fan of dynamic languages:
IMO they encourage sloppy data modeling (is this a string, an atom, a Map, a struct?).
They make the codebase harder to navigate and understand (especially when macros are involved).
They make refactoring harder and riskier.
Error can be hard to decipher.
Type signatures can be added but are only checked on demand, and not particularly quickly.
I really hope that Gleam is going to replace Elixir in many applications but rewrites are expensive and risky so I wouldn’t bet on it.
Blogging
I didn’t blog a lot this year, even if I wanted to. Only 3 posts, including this one, and one of the 3 is not even about tech, it’s about Free Will!
Conferences
I initially wanted to go to ICFP Milan but that eventually did not work out. I was however invited to give a presentation at the Philips Developer Days (Thanks Rodolfo!) and really enjoyed the afternoon over there because: 1. It was in the medical sector where lots of interesting software is being developed, 2. The talks were really excellent and somewhat connected to mine (about modularity), 3. The venue was quite unusual!
jj, zed, ocaml
No, I’m not falling asleep on my keyboard. Those are 3 things I tried this year but did not pursue:
OCaml: I didn’t do much simply because I was taken aback by the syntax (yes, I know, it is a silly reason).
Zed: I tried it (Helix also) but 2024 is still not the year when I will get super effective with an editor.
jj (Jujutsu): I’m convinced that we can do better than Git, and
jj
seems to be it. I tried it a bit but did not quite understand how to work effectively with a Github workflow based on branches. I will probably give it another go next year.
Onwards to 2025!
That’s it for 2024. If you have read so far, I hope that this post triggered some interesting thoughts for you (don’t hesitate to share!). It definitely helped me put some vague feelings into words.
I feel now ready for 2025 and I hope you are too, it’s gonna rock!
Subscribe to my newsletter
Read articles from Eric Torreborre directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by

Eric Torreborre
Eric Torreborre
Software engineer at the intersection of functional programming, product development and open-source