How a team that was new to Elixir over-delivered a big project in just three months.
Adopting a new language is more than just a technical journey. A language is only the right tool for the job if your engineers can wield it well. So, as a technical leader you don't just select a language or a framework based on its technical capabilities and how suited it is for the problems you need to solve, you also try to optimize for the skills your team already has, for a robust ecosystem surrounding that language, for strong community support, and so much more. Elixir lays claim to all of these features and I've seen teams ramp up on Elixir with astonishing speed, delivering robust, high-value projects to their organizations in record time. Don't believe me? I'll walk you through one such success story in which a small team of engineers who were mostly new to Elixir managed to over-deliver on a complex project in just three months. You'll learn why we chose Elixir, how Elixir enabled us to be successful, and some of the practices and techniques that will help
your own team adopt Elixir.
This post was sponsored by digital product consultancy DockYard to support the Elixir community and to encourage its members to share their stories.
The Project
Photo by Jo Szczepanska / Unsplash
This Elixir success story centers on a three month engagement with an EdTech company to deliver Flatiron School’s in-browser IDE (Interactive Development Environment) and curriculum management system into their own ecosystem. This meant that we would be porting over some existing codebases in both Ruby and Elixir, as well as building out a new Phoenix application that our partnering EdTech company would need to maintain. These applications together represented a non-trivial set of features:
- Students can click a button in the web app that forks and clones their own copy of a GitHub repository containing an interactive lesson.
- Students can write and run code for that lesson repository directly in their browser in an interactive development environment (IDE).
- Teachers can manage curriculum by creating, editing and deleting lessons.
- Teachers can manage curriculum for different cohorts of students by copying the master curriculum plan, editing it for their cohorts, and making it available to those cohorts.
The details of this feature set are not too important. Just understand that we had a lot to build and only a three month engagement to build it in. And, while we did have some legacy applications to maintain, we chose Elixir for the new application that needed to be built. This decision wasn't just the right technical fit for the tasks at hand, it's also the reason why we were so successful in delivering on this project. So, why did we choose Elixir in the first place?
Why We Chose Elixir
Photo by Jan Ranft / Unsplash
We had two viable choices for the new application that needed to be built---Rails and Phoenix. The team to which we were delivering the project had no Elixir experience at all, so a choice to build a Phoenix app represented a choice to adopt Elixir.
The first point in Elixir's favor was that it was technically the right tool for the job. The application we were building was going to be responsible for the curriculum management responsibilities detailed above. We needed concurrency to handle the high volume of lessons that would be deployed or made available to a given class of students at a point in time, and fault tolerance to gracefully handle communication failure with external systems---the app would serve as a touch point between two other apps as well as the GitHub API. The need for concurrency and fault-tolerance made Elixir an obvious choice for this application, but that alone wasn't enough to seal the deal.
At The Flatiron School, we had several strong Elixir evangelists who advocated for Elixir. They got other internal team members excited about the prospect of working in this new language, and they demonstrated a strong commitment to teaching and supporting Flatiron engineers, as well as engineers at our partner Ed Tech company, along their Elixir journeys. The enthusiasm and commitment of our Elixir evangelists helped us make a successful case for adopting Elixir to our partner company. It was decided that the new application that our two teams would build together during this three month engagement would be a Phoenix app. Before we dive into what we built and how, I want to talk a bit about the teams assigned to this project.
The Team
Photo by Pascal Swier / Unsplash
The team responsible for this project was a combination of two separate teams---a contingent from The Flatiron School and a contingent from the EdTech company to whom we were delivering the final product. Between these two teams, we had one engineer with over a year of Elixir experience, three with around six months of Elixir experience and three with no Elixir experience at all. All of our engineers with production Elixir experience were on the Flatiron side, whereas the engineers at our partner company (who would be responsible for owning and maintaining these applications after the three month engagement) were all brand new to Elixir and Phoenix.
It's also important to note that we weren't just facing the typical adoption challenge of introducing a new language to a group of people, we were also tasked with bringing two new teams of people together for the first time. Not only would we have to learn how to program in Elixir, we would also have to learn how to collaborate with a new group of people from an entirely different organization, with its own norms and practices.
With all of these challenges in front of us, we got to work.
The Journey
Our three month engagement proceeded in roughly three stages---technical design, establishing norms and practices, and shipping (lots of) Elixir code.
With only three month in which to deliver on our ambitious goals, we began the engagement with a brief design sprint. We used a one week sprint to identify product and technical requirements and map out the overall design of the new system, including the legacy apps we'd be porting over into a new ecosystem as well as the new app we would be building.
With a technical design in place, we were able to get to work, and we had a lot to do in order to establish both technical and process practices and norms. Elixir's robust ecosystem and tooling made it easy to establish technical norms around testing and test coverage, releasing and deploying, observability, code quality and more. Meanwhile, we also established practices like daily stand ups, weekly retros, and lots of pair programming. We'll dig into the details there and discuss how all of these contributed to our success in a bit.
With our norms and practices in place, we were able to start delivering value fast. About halfway through our three month engagement, we had delivered on our MVP, and by the end of the three months we had knocked off every single feature on our list and then some. Ultimately, we over-delivered on the agreed-upon features and left our partner company with a robust ecosystem that was easy to observe and debug and easy to grow and maintain as they took over ownership for the future.
Our Secrets to Elixir Success
How exactly were we able to over-deliver on this complex project, while bringing together teams from two entirely different organizations, and leveraging predominantly engineers with little-to-no Elixir experience? Keep reading to find out.
Elixir Has a Gentle Learning Curve
Our team of mostly new Elixir devs ramped up on Elixir with impressive speed, thanks in large part to Elixir's gentle learning curve. There are a few reasons for this. First, Elixir is highly eloquent–-–its easy-to-read syntax lowers the barrier to entry for new developers. The syntax will look especially familiar to anyone with a Ruby background, so our Ruby devs already had a leg up when it came to reading existing Elixir code and writing their first Elixir programs.
Elixir's syntax isn't the only language feature that contributes to its gentle learning curve. Elixir's pattern matching functionality, and things like guard clauses and the pipe operator make it easy to write clean and highly testable code. Pattern matching and guard clauses allow developers to implement control flow beautifully, without things like lots of nested if
conditions. Meanwhile, the pipe operator encourages developers to write small, pure, single-purpose functions that are strung together in easy-to-read flows. This kind of code has the added benefit of being easy to test, providing developers with a short feedback cycle between writing code and evaluating its behavior. As our developers unlocked the power of these and other Elixir features, they became more and more excited about working with Elixir. That excitement was infectious, and it inspired them and their colleagues to keep learning.
Elixir's robust documentation also greatly contributed to the speed with which our new Elixir devs were able to learn, and be productive in, Elixir. Elixir treats documentation as a first class citizen, so you'll find that everything from core language features to popular libraries are well documented. Elixir documentation is written with the ExDoc tool that makes it easy to generate documentation for your project and get it published to Hex Docs. The official Elixir docs are written with ExDoc and published to Hex Docs, and the rest of the Elixir world has followed suit in documenting their own libraries.
Official docs aren't the only resource out there for learning Elixir and working with new libraries. There are also a lot of Elixir community resources. Our team relied on Elixir School, a free, open-source online Elixir curriculum, as well as in the growing number of blog posts on Elixir topics. Elixir Forum, a community forum where users can post and answer questions and open discussion threads, also provided critical support to our developers throughout this project.
Elixir's language features, documentation, and growing community helped our team level up on Elixir in record time and supported them to solve complex challenges throughout the three month period of this project, far beyond the initial learning phase. Elixir's gentle learning curve isn't the only reason that our team was so successful in adopting this new language and delivering on some aggressive goals, however.
Elixir Has a Robust Ecosystem
While Elixir is still a relatively new language, its ecosystem is maturing fast. You'll find excellent support for everything from testing your code in development, to building and releasing your code in production, to instrumenting and observing it in the wild.
Fast and Comprehensive Testing in Elixir
Elixir's built-in testing framework, ExUnit, provides you with everything you need to exercise every pathway through your code. ExUnit even enables you to test asynchronous code flows and code flows that involve message passing between Elixir processes. Unsurprisingly, since ExUnit is written in pure Elixir, your tests will be highly concurrent and super fast. Mocking in your Elixir tests is also made easy thanks to the Mox library that lets you provide mocks based on the contracts defined in your code. So, while Elixir's language features encourage you to write highly testable code with small, pure, single-purpose functions, actually writing the tests for those functions is also a delightful experience. All of this adds up to a high percentage of test coverage, which helped our team move fast without breaking things when building out a new Elixir app and positioning new Elixir devs to maintain an existing one.
Easy Elixir Releases
An easy-to-use and comprehensive test framework is far from the only Elixir ecosystem feature worth mentioning. Elixir offers first-class support for releases through the Mix release tool. Releases allow us to compile our code and package it up, along with its runtime, into a single deployable unit. Each self-contained release package has everything it needs to run in the deployed environment, so you don't need to get your source code, or the Erlang VM (Elixir's runtime) onto production servers. Your team can even configure multiple versions of each light-weight release package, giving you the flexibility to deploy different pieces of your applications to serve different purposes. For example, you might have one release of an application that only runs some background workers, and another that runs the web server. Release building functionality is built directly into Mix, Elixir's build tool---no need to reach for a complicated third-party dependency. Your team can start building and releasing Elixir apps with native Elixir tooling. This helps contribute to fast deployment cycles.
First Class Observability in Elixir
Once your code is deployed to production, Elixir makes it easy to observe. Elixir treats observability like the first class citizen it is, thanks to the Telemetry library. The Telemetry library leverages Erlang Term Storage (ETS), a robust in-memory store, to dispatch and handle metrics and instrumentations for your code. It has fast become the standard for instrumenting Elixir code, and most Elixir libraries (like the Phoenix web framework and the Ecto database wrapper) use it to emit a standard set of observability metrics. Library authors and app developers alike are encouraged to use the library to do the same, and Telemetry makes it easy to report metrics and events to the destination of your choice, like Datadog or Prometheus. Elixir developers are therefore empowered to design code with observability baked-in right from the beginning. The result is that we gain a high degree of visibility into our applications, from both out-of-the box Telemetry instrumentation in the libraries we use as well as from our own code. Code that we can observe is code that we can debug, so Elixir's observability tooling contributes to fast bug remediation cycles.
When we're working in a new language for the first time, we're bound to introduce a bug or two. Visibility and ease of debugging become even more important here. This is just one more reason that adopting Elixir was such a smooth process for us.
An Elixir Library For All Your Needs
Elixir's increasingly mature ecosystem also means that you'll find a library for most of the common problems you'll be tasked with solving. During our three month project, we often had the experience of starting the day with a new problem to solve and no idea what technologies Elixir offered to solve it, and ending the day with a relevant library identified and implemented. In one instance, we needed a solution for JWT authentication in Elixir. Two new Elixir devs were deployed to investigate our options and they had the Joken Elixir library for working with JWTs implemented in our app by the end of that same day. This experience was repeated again and again–––we would need to solve some common problem, and sure enough we would quickly find an existing Elixir library to help us develop our solution. However, when Elixir's libraries fall short, you can plug the gaps with pure Erlang. Elixir supports Erlang interop, so dropping down to the Erlang level and leveraging Erlang functions is trivial. In one instance, when we had some encryption need that we couldn't solve with an existing Elixir library, we were able to write our own Elixir code that used Erlang's :crypto
module to get the job done.
Elixir Was The Right Tool For The Job
Photo by Barn Images / Unsplash
In addition to the language features mentioned earlier that made Elixir easy and fun to learn, Elixir's concurrency, fault-tolerance and distributed nature made it the perfect fit for solving the specific problems facing our team. We needed a curriculum management application for teachers that could process lots of lesson "deployments" at a given time, and recover from failure when communicating with external systems. And we needed an in-browser IDE for students that could manage lots of load, and handle crashes while still remaining stateful so that students wouldn't lose their work.
Elixir's GenServers and Supervision trees were the perfect fit for our curriculum management tool. We were able to build concurrent workflows that enabled teachers to deploy large amounts of content to different groups of students quickly. Elixir also gave us fine-grained control over failure scenarios. We could decide exactly how our program should behave in the event of a deployment failure, whether that failure was caused internally or happened as a result of an error communicating with an external system like the GitHub API. In Elixir, concurrency and fault-tolerance aren't afterthoughts. Elixir is built on top of Erlang's OTP (Open Telecom Platform), which provides a number of libraries and conveniences for managing exactly these pieces of functionality. So, building our application to behave in this manner, while not exactly trivial, wasn't nearly as onerous as it might have been in another language.
Also thanks to Erlang and OTP, we were able to support a distributed, clustered deployment of our in-browser IDE application right out of the box. We could manage a dedicated set of scalable resources for a cluster of IDE deployments, while still easily sharing state between nodes. Elixir's first-class support for distribution made it easy for us to handle a scenario in which, for example, an IDE fails in one node, and is restarted in another node without the current student losing any of their work or being aware of any failure or loss of connectivity.
Thanks to Elixir's powerful concurrency, fault-tolerance, and distribution features, we were able to quickly build robust and critical code pathways that solved for some of the hardest problems in programming, all with a relatively low degree of complexity.
Elixir Makes it Easy to Teach and Learn
Photo by Belinda Fewings / Unsplash
Finally, our Elixir success story wouldn't have been possible without the dedicated and talented engineers attached to this project. Elixir's gentle learning curve and robust ecosystem and tooling helped create passionate Elixir advocates who were committed to teaching their colleagues, along with new Elixir devs with an infectious excitement for learning. Together, this group deployed the following techniques to create a fun and supportive environment that helped us learn quickly and deliver value even faster.
Lots of Pair Programming
For this project, we took an aggressive approach to pair programming, pairing up an experienced and a new Elixir dev on almost every feature and task. This also helped us develop relationships and strengthen communication between the two teams (Flatiron engineers and devs from our partner EdTech company) that were coming together to deliver this project. Pairs were able to work quickly to deliver features while strengthening the Elixir skills of our newer Elixir devs.
Lunch and Learns
As the team built on their Elixir knowledge and delivered feature after feature, the excitement to share what they were learning grew. So, we scheduled regular "lunch and learn" discussions in which team members shared short presentations on Elixir topics over lunch. Topics included testing best practices, concurrency in Elixir, pattern matching, and more. This practice helped build on the excitement and sense of accomplishment that was already growing within the team and gave engineers an opportunity to go deeper on the new topics they were encountering every day.
Celebrating Wins
With so much happening so quickly, it wasn't hard to celebrate our wins. We blocked time out at the end of our daily stand-ups for pairs to demo their latest progress or for individual team members to share a quick tidbit that they'd learned. For bigger milestones, we held celebratory lunches and other team activities. All of this served to keep engagement high, helping us stay motivated to tackle complex problems in a new language.
Adopt Elixir for Much Success
Photo by Patti Black / Unsplash
Many teams and organizations reach for Elixir to solve complex technical problems well-suited for Elixir's concurrent, fault-tolerant, and distributed nature. This was certainly one of the motivating factors in our own choice to use Elixir during this three month project. But, as this case study shows, whether or not you need these compelling popular Elixir features, Elixir can empower your team to write clean, testable, well-instrumented code and have fun doing it. The learning curve to ramp up on Elixir is gentle, and individuals and teams can skill up relatively quickly to deliver value to your organization in record time. Armed with the teaching and learning practices described here, Elixir enabled our team to vastly over-deliver on a complicated project in a short period of time. Elixir is an obvious choice for greenfield development, and after we adopted Elixir, we never looked back.