Microservices vs. Monoliths yet again
Welcome to the wonderful, and sometimes wonderfully confusing, world of building software!
If you’ve been around developers or tech discussions, you’ve probably heard a blizzard of buzzwords: microservices
, monoliths
, cloud-native
, serverless
, agile
. It can feel like trying to learn a new language where everyone uses big words, but not always with the same meaning.

It’s enough to make anyone’s head spin, and sometimes, it feels like we’re all just pretending to understand each other while secretly hoping no one asks us to explain it in detail.
One of the biggest soap operas in the tech world over the last few years has been the dramatic tale of "Microservices vs. Monoliths." The story often goes like this: Once upon a time, there were big, clunky things called monoliths. They were slow, hard to change, and generally made developers sad. Then, like superheroes, along came microservices – small, nimble, and ready to save the day! Everyone should abandon their old monoliths and rush to build everything with microservices, and then we’ll all live happily ever after.
It’s a nice story, isn't it? Simple, clear, with a good guy and a bad guy. The only problem is, like most simple stories about complicated things, it’s not entirely true. In fact, blindly following this story has led many well-meaning teams down a path of accidental complexity, frustration, and a lot of late nights trying to fix things that didn't need to be so complicated in the first place. We’ve all been there, or know someone who has, trying to debug a system that feels like a tangled ball of Christmas lights – in July.
The aim of this (rather long) chat is to try and untangle some of that spaghetti. We’ll look at what these terms really mean, explore why the simple story isn't always helpful, and discuss a more thoughtful way to design software. Hopefully, by the end, you’ll feel a bit more confident about these ideas and a little less likely to accidentally build your own "multiverse of madness,"
as a wise (and slightly stressed) developer once put it.
The Popular Myths: Why Simple Stories Can Lead to Big Problems
Why did the "monoliths are bad, microservices are good" story become so popular? Well, we love simple answers.
It’s easier to pick a side than to understand all the messy details in between. Also, the tech industry loves new, shiny things. When a company like Netflix or Amazon talks about how they use microservices, everyone wants to copy them, even if their problems and resources are completely different.
It’s like seeing a Formula 1 race car and deciding you need one to drive to the grocery store. It might look cool, but it’s probably not the most practical choice.
Let’s look at some of the common, overly simple arguments you hear:
- Monoliths are impossible to scale! Is that always true? Or is it that badly designed monoliths are hard to scale? A well-structured monolith can often be scaled quite effectively, at least up to a certain point
- Microservices make you agile and fast! They can, but they can also slow you down immensely if you’re not prepared for the extra complexity they bring in managing all those tiny pieces. Imagine trying to coordinate a hundred tiny ants to carry a crumb versus one strong beetle. The ants could be faster if perfectly coordinated, but that coordination is a huge task in itself
- With microservices, if one part fails, the rest of the system keeps working! This is a nice idea, called fault isolation. But in reality, if your
user login
microservice fails, can yourshow user’s shopping cart
microservice really keep working meaningfully? Often, these services depend on each other - You can use the best programming language for each microservice! This is technically true (polyglot programming). It sounds exciting – a little Python here, some Go there. But think about your team. Do you have experts in all those languages? It can quickly become a maintenance nightmare
- Microservices are easier to understand because each one is small. A single microservice might be small, yes. But understanding how 50 small microservices interact can be much harder than understanding one larger, well-organized codebase
The danger of these simple stories is that they make us jump to solutions before we’ve even properly understood our problem. We start building with microservices
because it's the "modern way," without asking if it's the right way for us, right now. This can lead to what’s sometimes called résumé-driven development
– choosing technologies because they look good on a CV, not because they are the best fit for the job
Thinking in Layers: A More Sensible Way to Look at Software Design
So, if "monolith vs. microservice" is too simple, how should we think about it? A more helpful approach is to break down the design of software into a few different aspects or "dimensions." This lets us see that there are many more choices than just two. Let's explore three key dimensions:
- Modules: How we organize the code itself into logical, understandable pieces
- Repositories: Where we store that code and how teams collaborate on it
- Artifacts: The actual runnable things we deploy to servers, and how many of them there are
Understanding these dimensions separately helps us make more informed decisions and see that the path from a monolith
to microservices
(if that's even the right path for you) is not a single jump, but a series of choices along these different lines.
Dimension 1: The Art of Organization – Understanding Modules
Before we even think about servers or networks, we need to think about how to organize our code so that other devs can understand and work with it. This is where modules come in.
What Exactly is a Module?
Imagine you're writing a book. You wouldn't just write one gigantic, unbroken stream of text, would you? You'd organize it into chapters, perhaps sections within chapters, and paragraphs within sections. Each of these units deals with a specific part of the story or a particular idea. Modules in software are very similar.
A module is a way of grouping related code that performs a specific function or handles a particular area of responsibility within your application. Think of it as a well-defined "black box" with a clear job. For an e-commerce website, you might have:
- A UserAccounts module (handles logins, registrations, profiles)
- A ProductCatalog module (manages product information, categories, search)
- An Ordering module (takes care of shopping carts, checkouts, order history)
- A PaymentProcessing module (interfaces with payment gateways)
Each module has its own internal workings, but it presents a clear "face" (an API or interface) to other modules that might need to use its services.
Why Bother with Modules? The Benefits are Big!
- Reduces Brain Strain (Cognitive Load): Modules allow developers to focus on one part of the system at a time
- Better Teamwork: Modules provide natural boundaries for work
- Easier Testing (Sometimes): It can be easier to write and run tests for a smaller, well-defined module
- Potential for Reuse: A well-designed module can sometimes be reused
- Clearer Boundaries and Responsibilities (Encapsulation): Good modules hide their internal complexity. This is huge for long-term maintainability
The Goldilocks Problem: When is Modularization Just Right?
While modules are great, you can also go too far.
- Don't Chop Too Early: Trying to break a new project into many tiny modules can be counterproductive
- Avoid Module Soup: Hundreds of tiny modules can create its own complexity.
- The Distributed Monolith Nightmare (A Sneak Peek): This is a particularly nasty trap where separate pieces are incredibly chatty and dependent. Often worse than a well-structured monolith
Crucial Point: Modules Are NOT Automatically Microservices!
You can have a beautifully modular system that is still a monolith in terms of how it's deployed. A module is a design and organization concept for your codebase. A microservice is typically a deployment and runtime concept.
This is so important it's worth repeating. You can, and often should, design your monolith with clear module boundaries. This makes it a good monolith, and it also gives you options to change how you deploy it later if you need to.
Dimension 2: Home Sweet Code – Navigating Repositories
Once you've started thinking about modules, the next question is: where does all this code live?
A repository (often shortened to repo) is simply the place where your source code is stored and managed, usually with a version control system like Git
. The way you structure your repositories can have a big impact on how your team works.
The Monorepo: Everyone Under One Roof
A monorepo is when all the code for your entire system – all the different modules, perhaps even for multiple related projects – lives in one single, large repository.
The Good Stuff:
- Visibility: Everyone can see all the code
- Atomic Changes: Easier to keep everything consistent
- Easier Refactoring (Sometimes): Tools can help more easily
- Unified Tooling: One set of build tools, testing scripts, etc
The Not-So-Good Stuff:
- Slowness at Scale: Common operations can become slow without specialized tooling
- Complex Build Systems: Needed to avoid building everything every time
- Noisy Neighbors: Changes can accidentally break unrelated parts
- Permission Granularity: Can be harder to give different teams access to only their parts
Many developers dream of the simplicity of having everything they need in one place, in one IDE window, without juggling multiple projects. A well-managed monorepo can sometimes get close to this dream, but it takes effort.
Multiple Repositories (Polyrepo): A House for Every Module (or Group)
The other common approach is to have a separate repository for each module, or for each small group of closely related modules.
The Good Stuff:
- Clear Ownership: Very clear which team owns which repository
- Independent Lifecycles: Each repo can have its own release schedule
- Smaller, Faster Operations:
git clone
is faster, builds are typically faster - Tooling Freedom (Sometimes): Different teams might choose different tools
The Not-So-Good Stuff:
- Cross-Cutting Changes are a Nightmare: Coordinating multiple PRs is slow and error-prone
- Dependency Management Hell: Ensuring compatibility can become a full-time job
- Code Discovery: Harder to find code or understand connections
- Duplicated Effort: Boilerplate code, build scripts, CI/CD configs might be duplicated
Is There a 'Right' Answer?
No, not really. Both monorepos and polyrepos have their pros and cons. The best choice depends on your team, system complexity, company culture, and available tools. Some very large companies use monorepos successfully (like Google and Facebook), and others use many repositories successfully.
The key is to understand the trade-offs and choose consciously, rather than just picking one because it's trendy.
Dimension 3: Hitting the Servers – The World of Artifacts (and Where Microservices Really Live)
We've organized our code into modules, and we've decided where that code will live in repositories. Now, the crucial question: how do we actually run this software? This brings us to artifacts.
An artifact is the packaged, runnable unit of your software that you deploy to a server. This could be a .jar
or .war
file for Java applications, an executable program, or, very commonly these days, a Docker container image
.
The big decision here is:
- Do you package all (or most) of your modules into one single artifact and run it as one big process? This is often called a monolithic deployment.
- Or do you package individual modules into many separate, smaller artifacts, each running as its own independent process? This is the heart of what people usually mean by a microservices architecture.
The Single Artifact Approach (Monolithic Deployment)
The Upsides (Why it's often a good place to start):
- Simpler to Build and Deploy: One thing to build, test, and deploy
- Easier Debugging (In-Process): Step through code across module boundaries easily
- No Network Worries (Internally): Communication is fast and reliable
- Simpler Transactions: Wrap operations in a single database transaction
- Easier to Reason About: Easier to understand data flow and control
The Downsides (Why people want to move away from it sometimes):
- Scaling Challenges (Sometimes): Can't scale just the busy part
- One Bad Apple Spoils the Bunch: A bug in one module can crash the whole app
- Technology Lock-in: Harder to mix and match technologies
- Slower Release Cycles (Potentially): Entire artifact may need re-testing and re-deployment
The Many Separate Artifacts Approach (Microservices Architecture)
Here, your UserAccounts module might become the user-account-service
artifact, the ProductCatalog module becomes the product-catalog-service
artifact, and so on. Each runs as its own little program, often in its own Docker container
, and they talk to each other over the network.
The Allure (Why this is so popular):
- Independent Scaling: This is a big one. Scale only busy services
- Technology Diversity (Polyglot): Pick the best tool for each job
- Fault Isolation (The Dream): If one service crashes, others ideally keep working
- Independent Deployments & Team Autonomy: Faster development cycles for individual teams
The Price You Pay: The Huge Complexity of Distributed Systems
This is where things get really tricky, and it's the part that the simple "microservices are great!" story often glosses over. When your different pieces of software run as separate processes and have to talk to each other over a network, you have just entered the challenging world of distributed systems.
And it comes with a whole suitcase full of new problems you didn't have before.
- The Network is a Liar (and Slow, and Unreliable): Calls can fail for many reasons
- Data On The Move (Serialization): Converting data (e.g.,
JSON
,Protocol Buffers
) takes time and CPU - Service Discovery: Where Are You? How services find each other
- Load Balancing: Who's Next? Distributing requests among service copies
- Things Will Fail – Be Prepared! (Fault Tolerance): Requires Retries, Circuit Breakers, etc.
- Transactions are Hard (Really Hard!): Leads to concepts like Eventual Consistency and Sagas
- Seeing What's Going On (Observability): Needs Centralized Logging, Distributed Tracing, Monitoring and Alerting
- More Moving Parts = More To Go Wrong: Operationally much more complex
So, Is It Worth It?
Sometimes, absolutely yes! For very large applications, with many teams, where the benefits outweigh the massive increase in complexity, microservices can be a powerful approach. But for many smaller to medium-sized applications or teams, the added complexity can easily crush them. It's a trade-off, and you need to be honest about whether you need those benefits enough to pay the price in complexity.
The Journey, Not a Destination: Software Loves to Evolve
One of the biggest mistakes people make is thinking they have to choose "monolith" or "microservices" on day one and then stick with it forever. Software development is a journey of learning and adaptation. Your system will change, your team will change, your business needs will change.
Starting Simple is Often Smart: For many new projects, especially if the requirements are not perfectly clear or the team is small, starting with a "modular monolith" is often the most sensible approach. This means:
- Design your code with clear module boundaries from the beginning
- Keep all your code in one repository (a monorepo) for simplicity
- Deploy it all as one single artifact
This allows you to build and iterate quickly, without getting bogged down in the complexities of distributed systems before you even have a working product.
Evolving Gracefully (If and When Needed): If you've built a good modular monolith, you have options for the future.
The key here is those good module boundaries you designed at the start. If your monolith is a spaghetti code mess with no clear internal structure, trying to carve out a microservice from it later will be like performing complex surgery with a butter knife – painful and probably messy.
Don't Over-Engineer for a Future That May Never Come. There's a strong temptation to build for "what if" scenarios. This is called premature optimization (or premature complexification) and it's often a trap. Follow the YAGNI principle:
You Ain't Gonna Need It.
Build what you need now, design it well so it can evolve, and then evolve it when the need actually arises.
Conclusion: It's Your Software, Your Team, Your Choices
Phew! That was a lot of information. If you've made it this far, thank you for sticking with it.
The main takeaway I hope you get from all this is that there's no single "right" way to design software. The "monolith vs. microservices" debate is often a false choice. Instead, think about those three dimensions we discussed.
When you're faced with an architectural decision, try to step back from the hype and ask:
- What problem are we really trying to solve?
- What are the actual needs of our users and our business?
- What are the skills and size of our team? Can we realistically manage the complexity?
- What's the simplest thing that could possibly work well right now, while still giving us options to evolve later?
Don't choose an architecture because it's trendy or because Google uses it. Choose it because it makes sense for you. And remember, it's okay to start simple. It's okay to have a well-structured monolith. It's okay to evolve your system piece by piece as you learn and grow.
The goal is to deliver value and to build systems that don't make you want to tear your hair out every time you need to make a change.
Building software is a continuous learning process. We’ll all make mistakes, we’ll all learn from them, and hopefully, we’ll all get a little bit better at it over time.
The Obligatory Disclaimers
This Isn't Everything: The world of software architecture is vast and deep. I've simplified many things and skipped over many other important topics. This is just one perspective, aimed at clearing up some common confusion. There's always more to learn!
We're all figuring it out: Do I have all the answers? Absolutely not. Does anyone? Probably not. The best we can do is share our experiences, learn from each other, and try to make slightly fewer mistakes tomorrow than we made yesterday.
Good luck with your software adventures!