2.3 Million Lines Later: Retiring Our Legacy API

By Phil Richards, Lead Architect at Aluma

There are moments in engineering that feel bigger than a PR or a release note. They're quiet, often unceremonious, but carry the weight of years. This is one of those. What started as a seemingly routine bit of code deletion ended up feeling more like turning a page—on old decisions, legacy trade-offs, and the kind of technical debt that comes bundled with experience. This post is a look back on what we let go of, what we’ve built in its place, and why this moment—however understated—feels like a meaningful milestone in the journey of Aluma.

Not Just a Spring Clean

I recently merged a pull request that caught me off guard – despite the fact that I'd created it myself. It was something that we'd been talking about as a team for ages, years in fact, and I was expecting it to feel like a massive relief... but in truth, I felt a little empty, like the wind had been let out of my sails.

This PR wasn't any old change to the codebase. It was the removal of what amounted to v1 of the Aluma API. It was the removal of the culmination of years of work in itself, and although it was a relief to see it go, it was definitely a bittersweet moment. I definitely hadn't expected to feel quite so emotional about it; I'm not normally a sentimental kind of person. Sometimes the strangest things just catch you.

However, this blog post isn't just about our growth as a company, or how we've evolved our tech stack and architecture, or even about serendipity and the butterfly effect - it's actually about all those things combined. This moment of transition has brought together multiple threads of our journey in a way that deserves a moment of reflection.

A Line in the Sand (and 2.3 Million Gone)

2.3 million lines of code! I had no idea we even had that much code in our repository to start with. Maybe some of it was generated code, but regardless - it was momentous. It represented a genuine line in the sand, and its timing coincided with significant transition for us at Aluma. For a small team, built with caffeine, sweat and tears (and the occasional late night), it warranted a moment to reflect on how far we've come.

Blog 1

2.3M lines deleted

Those lines made up our Documents API - our first attempt at building a scalable document processing platform. The Documents API had served its purpose, helping us understand our customers' needs and the complexities of the problem space. But as with many first versions, we'd learnt enough to know we needed a different approach moving forward. But the removal of this code wasn't just about tidying up; it marked the completion of our transition to the new Tasks API and a different architectural approach that I'll dive into shortly. Sometimes you need to clear away the old to make room for the new.

More Than Just Deleting Code

This massive code deletion represents the final chapter of a much longer story. The truth is, the initial effort in our Tasks API began years ago, in January 2022, once we'd recognised the limitations of our original Documents API. The asynchronous Tasks API quickly became our preferred API, designed with the benefit of everything we'd learnt from our first attempt.

Blog 2

The first commit message of the workflows svc / tasks api

Since then, the Documents API has been on life support - maintained but not enhanced, while we directed all new development toward the Tasks API. We've been gradually migrating customers and functionality to the new system for quite some time. This PR represents the final step in that long transition - the point where we could finally pull the plug on our legacy approach.

The Tasks API hasn't just coincided with our growth - it's been instrumental in enabling it. Since its implementation, we've been able to handle increasingly larger processing volumes that would have brought the Documents API to its knees. We're now experiencing rapid increases in our customer base and processing demands, with projections suggesting we'll need to handle many times our current load in the coming months. It doesn't seem long ago that we'd be excited if a year-long project of 1 million pages came our way - but now we routinely handle at least that in a day. This kind of scaling was precisely what we designed the Tasks API for - its asynchronous nature and more efficient architecture have proven to be a crucial foundation for our expansion. The Documents API, with its synchronous API and architectural issues, simply wouldn't have been able to keep pace with our current volumes, let alone our growth path.

From Microservices to Micro...lith?

When we created the Documents API, it was our first attempt at a SaaS offering. Like many companies, we jumped on the microservices bandwagon too soon. The promise of decoupled services and scalability was alluring. However, we soon realised that the overhead of managing more moving parts, code duplication and inter-service communication quickly became a real pain and a barrier to progress. Microservices can work in situations where there are multiple teams that can maintain their own service, without having the hassle of sharing code between teams. Indeed, it was developed to allow multiple teams within large organisations to work with autonomy, and to avoid the effort involved in avoiding 'siloing' - that is, ensuring all teams work together with shared responsibility for the codebases. Teams are responsible for their own tech, own code repositories, and so on. Of course, we had none of that, since we are just a single dev team. So we didn't gain any of those advantages; we just inherited the disadvantages of extra complexity.

On the upside, we learned *so much* by effectively bombarding ourselves with problems. We invented a slightly custom, slightly hybrid pub/sub/rpc mechanism over RabbitMQ (not recommended), we dabbled with MongoDB and SQL Server and Postgres (stick with Postgres), we also tried out different languages (mainly C# and Go) for backend. We had Typescript and Vue on the frontend and only narrowly avoided falling into the inevitable series of rabbit holes that would've been giving each microservice its own microfrontend and composing the UI somehow, somewhere before presenting it to the user. We tried to use semantic versioning for our libraries, publishing them in an external package manager - despite the fact that we were the only consumers of them. We really tried to make Microsoft Service Fabric work for us when Kubernetes was young and unproven, and nearly ended up tearing our hair out. We almost ended up dealing with event sourcing and eventual consistency, which, given our API was synchronous, would've created the mother of all headaches. We even had delusions of grandeur about launching our own app store.

Looking back, and putting it as simply as that, I wonder how we managed to make any of it work at all.

Note to any teams who haven't already settled on their tech stack: Go and Postgres (with the occasional dash of redis) on the backend make a winning combination. Typescript and svelte are great for frontend. We're sticking with these.

At the other end of the spectrum, and the reason microservices became popular, you've got the monolithic architecture – a single process, single codebase approach that handles all incoming requests. While simpler in many ways, monoliths have their own issues, and can become problematic to scale and evolve.

That's why we've adopted what we've come to call a 'hybrid monolith' or 'microlith' architecture – a slightly custom approach that takes the best bits of both worlds. It's a single codebase, which simplifies development and maintenance (unlike microservices). We've opted for fewer, larger services to reduce the management overhead (unlike microservices). We're still using containerisation in Kubernetes. And crucially, these fewer, larger services are stateless and can be scaled out to meet uneven demand (unlike monoliths).

Comparing Approaches:

Monolithic architecture:

  • Single process
  • Single codebase
  • Handles all business logic and DB connectivity
  • Can provide a stateful API
  • Often associated with enterprise-y tooling, e.g., Java, .NET
  • Likely also handles user auth, admin and so on

Microservices:

  • Each team within a company or dept are responsible for their own service, which may be responsible for single or multiple RESTful endpoints
  • Designed to make the most of 'silo-ing', as opposed to working to avoid it
  • Could well be multiple codebases / multi-repo
  • Limited or no code sharing between services and teams
  • Request routing handled by a reverse proxy
  • User auth (and admin) handled by a separate service
  • Services tend to be stateless, and as a result can scale out (as well as up)
  • Containerised

Our 'Microlith' architecture:

Eschews the single process model of the monolith, but:

  • Is a single codebase
  • Fewer, larger services
  • A single service can play multiple 'roles' - in our case 'frontend', 'worker', 'scheduler', and these are deployed as separate service types (deployments in kubernetes)
  • Service for user auth, admin
  • Service for main 'work' API endpoints
  • Reverse proxy to handle routing
  • Containerised
  • Shares code
  • Single team owns all services

The gist of the architecture is that a single team can reap the rewards of a containerised, microservices-like architecture with modern tooling, but avoid the pitfalls of over-compartmentalising à la pure microservices.

We may well have settled on this 'microlith' architecture by ourselves, but I also had a phrase at the back of my mind that helped convince me that this was the right path. If you've ever heard a saying, or read a phrase, that has resonated so much that it's become indelibly etched in your memory, you'll know what I'm talking about here. In this case, I saw a tweet in 2020 from Brad Fitzpatrick which linked to a blog post from a colleague of his from Tailscale, which described how and why they maintain their own fork of the Go codebase. Although it was an interesting and enjoyable post, it was, on the face of it, a completely unrelated subject. But it contained a single line from the author which stayed with me and became a kind of guiding principle for me for the years to come.

This is where the butterfly effect comes in - that seemingly random tweet, about an unrelated topic, from someone I've never met contained a single line that would end up influencing our entire architectural approach. What started as a casual read about Tailscale's Go compiler fork ended up shaping fundamental decisions about our system architecture years later. It's fascinating how these small, chance encounters with ideas can ripple outward in ways we never could have predicted.

And what was this line that helped shape our architectural destiny? It was so simple:

"Code with the engineers you have"

It was a simple, pragmatic principle: "Code with the engineers you have." Paraphrasing, "play to your strengths". We realised that we shouldn't be trying to emulate the architecture of a FAANG company; we needed to build something that worked for us, playing to our team's strengths and acknowledging our size and context. The 'microlith' is the embodiment of that principle for Aluma right now, where we take the bits of both ends of the architectural spectrum and make them work for us. I don't want to be prescriptive about it, because that's the point - it's custom to our team. You must pick the approach that works for you.

Powering up for more growth

So, what has embracing this philosophy and our 'microlith' architecture actually enabled us to achieve? Well, it's fundamentally about preparing for significant growth while working smarter. This architectural shift is allowing us to handle a massive increase in processing volume – we're projecting at least a 20x growth in the number of pages we process within the next few months. Recent throughput testing has shown promising results, demonstrating the potential we've unlocked. Our Tasks API, supported by this more streamlined architecture, is a key enabler, allowing us to scale efficiently and meet this anticipated demand.

Our current performance goal is ambitious but an obvious one, given our current scale: to reach 1 million pages processed per hour. It's a big number, but it's very achievable. It doesn't scare us now! And it's a *real* jump from where we were just a year or two ago. This architectural shift, this "not just a spring clean," is a crucial step on that journey, laying the foundation for the scale we envision. And once we hit that, who knows what will be next.

Looking ahead

Of course, we know that scaling our infrastructure is only one part of the equation. As we grow, we also need to keep improving the user experience, making our platform more intuitive and easy to use, and streamlining the onboarding process for new customers. These are challenges we're excited to tackle head-on.

We may even need to tackle eventual consistency... but at least our API is asynchronous now.

Ushering in a new season

This isn't just a story about deleting code or changing architectures; it's about evolution, learning, and adapting to meet the challenges of a rapidly growing business. It's about building something that's not only scalable and efficient but also sustainable and enjoyable for our team to work on.

What might appear from the outside as a purely technical decision – the deletion of code, and removal of an API – carries the weight of the heart and soul our team pours into Aluma.io. As real people, these milestones affect us, marking not just architectural shifts but the culmination of years of dedication. And while these decisions can be tough (or catch us off-guard!), they have paved the way for the significant progress we're now experiencing, setting the stage for an exciting new chapter.

About the Author

Phil Richards, Lead Architect at Aluma, has always had a deep-seated need to learn how things work, and fix, and construct – a trait that began at an early age with taking pens apart and putting them back together again. This desire to peel back the layers of the onion has fueled a lifelong interest in tinkering, and the inclination to understand and rebuild made software development a great fit for his career. For Phil, coding provides an ideal space for design and building, offering just the right amount of space for creativity... Just don't leave your favourite pens nearby.

We love and use tailscale, but have no affiliation with them. Thanks to Josh Bleecher Snyder for the blog post.


More from the blog