The Monolith Dilemma and the Mono Repo Mirage

In the ever-evolving landscape of software development, we constantly seek better ways to structure our projects, manage our code, and streamline our development processes. Two approaches that have dominated discussions in recent years are monolithic architectures and mono repositories. In this post, we’ll dive deep into the challenges posed by monoliths and explore why mono repos, despite their initial appeal, may not be the panacea we’re looking for.

The Monolith Dilemma: When Bigger Isn’t Better

Monolithic architectures have been the go-to structure for many projects, especially in their early stages. A monolith is a single, large application where all the code for various features and functionalities resides in one codebase. While this approach can simplify initial development and deployment, it often leads to significant challenges as the project grows.

The Problems with Monoliths

  1. Dev Feedback Slowdown: As the codebase expands, compilation times increase dramatically. What once took seconds can stretch into minutes or even hours, severely impacting developer productivity and morale.
  2. Test Suite Bloat: Large codebases accumulate a vast number of tests. Running the entire test suite becomes a time-consuming process, often delaying deployments and slowing down the development cycle.
  3. Test Flakiness: With a high volume of tests, the likelihood of encountering flaky tests increases. Even if each test has a 99% stability rate, the overall stability of your test suite decreases exponentially with the number of tests. For instance, with 179 tests at 99% stability, your actual stability drops to a mere 17%!
  4. Extended Lead Times: The combination of slow compilation, lengthy test runs, and increased deployment complexity leads to extended lead times. This delay between writing code and seeing it in production can be frustrating for developers and stakeholders alike.
  5. Difficult Upgrades: Upgrading components or frameworks in a monolith is a massive undertaking. For instance, upgrading a web framework like React or a backend framework like .NET Core often requires changes across the entire codebase, making it a risky and time-consuming process. “It’s like changing tires on a moving car.” – Jeff Bezos

The Engineer’s Dilemma: To Add or Not to Add?

Picture this: You’re an engineer tasked with implementing a new feature. As you sit at your desk, coffee in hand, you find yourself at a crossroads. The path before you splits into two directions:

  1. Add to the existing monolithic system
  2. Create a new, separate system for the feature

Your mind races through the implications of each choice:

Option 1: Add to the Existing System

“Well,” you think, “the monolith already has everything set up. Authentication? Check. Infrastructure? In place. Deployment pipelines? Running smoothly. CI/CD? Configured and working. Monitoring? All set up.”

You can almost hear the siren call of the monolith: “Just add your feature here. It’ll be quick and easy. You know how everything works already!”

Option 2: Create a New System

As you consider this option, a wave of tasks floods your mind:

  • “I’ll need to wire up the authentication library.”
  • “What about infrastructure? That’s going to take time, i’ll need to create new terraform scripts and thing about capacity and resources.”
  • “CI/CD for a new system? More work.”
  • “And let’s not forget about monitoring and alerts. Ugh.”

Your product manager’s voice echoes in your head: “Remember, we’ve got to deliver this next sprint. We need to move fast!”

The Decision

As you weigh your options, the choice seems clear. Adding to the existing system will be faster, easier, and will let you meet those tight deadlines. Creating a new system feels like it would slow you down, potentially for weeks.

“I’ll just add it to the monolith,” you decide. “It’s not ideal, but it’s the most practical solution right now.”

And so, another feature joins the monolith. It’s a decision made countless times by countless engineers, each one logical in the moment, each one contributing to the growing complexity and challenges of the monolithic system.

This cycle repeats, sprint after sprint, feature after feature. The monolith grows ever larger, compilation times creep up, test suites expand, and the very problems that tempt us to create new systems become more pronounced.

It’s a vicious cycle, one that leaves many engineering teams wondering: Is there a better way? How can we break free from this pattern and create systems that are both efficient to develop and maintainable in the long run?

The Mono Repo Mirage: A Solution or Another Problem?

In recent years, mono repositories (mono repos) have gained popularity as a potential solution to some of the challenges posed by monoliths. A mono repo is a version control repository that contains multiple projects or applications. The idea is to maintain modularity while keeping all code in one place.

The Promise of Mono Repos

“Every solution breeds new problems.” — Arthur Bloch (Murphy’s Law)

Mono repos offer several potential benefits:

  1. Unified codebase: All projects are in one place, making it easier to share code and maintain consistency.
  2. Simplified dependency management: Dependencies can be shared and updated across projects more easily.
  3. Atomic commits: Changes across multiple projects can be committed together, ensuring consistency.
  4. Easier refactoring: Mass updating code between projects becomes simpler when everything is in one repository.

The Reality Check

While mono repos sound promising in theory, the reality can be quite different, especially for companies that aren’t tech giants like Google (which famously uses a massive mono repo).

Let’s consider a real-world perspective from a developer at Uber, a company known for its use of mono repos:

“It is horrible – everyone hates it. It does not work well with IDEs – feels like going back 20 years with IDE support. Dependency management is a nightmare – which is supposed to be the big selling point. Release tooling sucks – I see thousands of commits between my releases.”

There’s several key issues with mono repos:

  1. Poor IDE Support: Many modern IDEs struggle with the size and complexity of mono repos, leading to a degraded development experience.
  2. Dependency Management Challenges: Contrary to the promise of simplified dependency management, large mono repos can make this process more complex due to issues with large updates having large amounts of change that go with them, and large amounts of change it a high frequency of change repo compound against each other.
  3. Release Complications: With thousands of commits across various projects, identifying and managing releases becomes a significant challenge.
  4. Tooling Requirements: Effective use of mono repos often requires substantial investment in custom tooling. As our Uber developer notes, “Monorepo might work for Google who has an army to build tooling – for everyone else, stay far away from it.”
  5. Its not that easier to refactor: With the volume of change you get you cant do big refactors, you get too many merge conflicts and get kicked out of the merge train/queue

The Search Continues

While mono repos attempt to address some of the issues posed by monolithic architectures, they introduce their own set of challenges. For most organisations, mono repos may not be the silver bullet they appear to be at first glance.

So, where does this leave us? How can we address the challenges of monoliths without falling into the pit of mono repo complexity? Is there a middle ground that can provide the benefits of modular development without the drawbacks we’ve discussed?

In our next post, we’ll explore these questions and introduce the concept of “paved paths” – a promising approach that aims to combine the best of both worlds while avoiding their pitfalls. Stay tuned as we continue our journey from monoliths to more maintainable and scalable architectures!

Leave a comment