What Works in a Monolith Can Break a Microservice
When DRY and Code Reuse Work Against Your Architecture
I read books and articles, watched YouTube videos, and practiced until DRY became part of how I designed my code.
DRY became second nature—every function, class, and module is structured to eliminate duplication. It feels like an absolute truth.
Then, the first encounter with microservices. Everything had to change, whether you liked it or not.
A good backend engineer masters APIs. A great backend engineer masters System Design.
If you need to practice System Design, CodeCrafters lets you create your own Docker, Git, Redis, and more.
Hands-on Projects + Practice = Job Offer
Sign up and get 40% off if you upgrade.
Thank you to our sponsors who keep this newsletter free!
Microservices promise scalability, autonomy, and resilience. One of the biggest pitfalls I encountered was disguised as an optimization shortcut: sharing business logic or models via common libraries.
It seemed like the right thing to do—avoid duplication, centralize logic, and enforce consistency—but in reality, this approach created hidden coupling that undermined the core principles of any microservices.
Three Common Mistakes That Will Kill Your Microservice Autonomy.
1. Overloading Shared Libraries
One of the biggest mistakes in microservices is centralizing too much logic into a shared library. A common example is a shared authentication and authorization module. At first, this may seem like a great way to maintain consistency across services, but it introduces a tightly coupled dependency that makes independent deployments difficult.
Imagine multiple microservices depending on the same authentication module to validate user credentials and permissions. Every service that relies on the library might need to be updated and redeployed if a change is needed. This problem is multiplied tenfold if that library is maintained by the only guy in the team that has been working on it since its creation.
A better approach is to use a dedicated authentication service that microservices can call via API, ensuring loose coupling while maintaining consistency. If the latency starts messing with your services, you can always use Cache.
2. Bloated Shared Domain Models
Another mistake is the excessive use of shared domain models across microservices.
Imagine starting with a simple Order object that contains only an order ID and Status.
Over time, additional fields like payment details, customer preferences, and delivery tracking get added to the same shared model. Services that only need a small portion of the object must now pull in unnecessary dependencies.
A service that only requires order IDs and statuses ends up processing the entire bloated object. If another service updates the shared model, it can introduce breaking changes, forcing unrelated services to update their code, even if they don’t use that field.
While shared domain models may seem like a way to enforce consistency, they actually introduce tight coupling between services. Instead of being loosely coupled, services become dependent on the same definitions, making independent evolution much harder.
Instead of a clean separation, you now have an accidental distributed monolith where services are intertwined through shared dependencies.
The better approach is for each service to own its own version of the model, exposing/consuming only the necessary fields via APIs.
3. The "utils-commons" Anti-Pattern
Do any of these names sound familiar: “util, commons, formats”? I’ve seen all of them, and they’re almost always a warning sign.
Imagine you create a formatters module for handling dates and order titles. It starts simple, providing standardized date formatting across services.
Then, another developer adds currency formatting, followed by another who introduces order title formatting rules specific to their region.
Now, ask yourself what happens if someone needs to introduce a small change to one of those functions. Why bother when they can just add a new one, right?
Before long, the module becomes a dumping ground for various unrelated utilities.
It’s difficult to understand which service is using what.
So, sooner than later, you will start seeing different versions of the same format since people will be afraid to break something while changing a specific version.
Instead of creating large, catch-all utility modules (e.g., "commons", "util"), keep functionality specific to each service. This prevents the creation of unmaintainable, non-cohesive modules.
The Death Star is Really Deadly
For many reasons—some by design and some by sheer bad luck—this one shared library ends up being used by all microservices, causing a star schema dependency pattern. As stated above, this is a big red flag in microservice architecture.
What is even worse: How will you ever know which microservice uses which shared library functionality? If you have only a few microservices, you can still research them, but what if you have 10 or more?
The complexity increases exponentially, making maintenance and upgrades a nightmare.
There are some exceptions, but most of the times, shared libraries brings:
1. Hidden Coupling
When multiple services depend on the same library for critical business logic, they become tightly coupled. A change in the shared library can have a ripple effect, forcing multiple services to upgrade in sync.
2. Versioning Hell
Each service evolves at its own pace, but a shared library introduces versioning conflicts. If one service needs an update while another cannot afford a breaking change, teams are forced into complex version management or, worse, coordinated deployments.
3. Cascading Failures
A bug in the shared library doesn’t just affect one service—it spreads across the entire system, increasing the fallout zone of failures. Fixing it requires not just one deployment, but many as dependencies you have.
4. Deployment Complexity
Microservices should enable independent deployments. However, shared libraries create situations where updating one service requires changes in others, negating the benefits of a distributed system.
A basic rule of thumb for shared libraries in a microservice architecture is that something shouldn’t be a library if it:
Contains business logic or domain-specific code
Has frequent changes based on new requirements
Causes coupling between consumers
What Works In A Monolith Can Be Disastrous In A Distributed System.
Instead of eliminating all duplication, microservices prioritize decoupling, autonomy, and independent deployability—even if that means duplicating some logic.
The best microservices aren’t the ones with the least duplicated code—they’re the ones that can evolve and deploy without coordination.
The Hardest Lesson: Duplication Can Be a Good Thing
Letting go of monolithic DRY instincts felt wrong at first. But once you see how hidden dependencies made independent deployments difficult, you realize that strategically duplicating logic is often the easier, safer, and more scalable choice.
Instead of focusing on minimizing code repetition, you should focus on minimizing dependencies and maximizing service autonomy. Accept that some duplication is necessary and can be beneficial.
Duplication of business logic is often a worthwhile trade-off for maintaining independent services and scalability since the cost of avoiding duplication is often greater than the cost of duplication itself.
Next time you’re tempted to “optimize” by centralizing business logic, ask yourself: Am I optimizing for maintainability or introducing hidden coupling?
In microservices, the best shortcut is often avoiding shortcuts altogether.
A service that owns its logic owns its future—shared dependencies only create shared problems.
System Design Classroom is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.
Articles I enjoyed this week
1 Simple Technique to Scale Microservices Architecture by Neo Kim
Coding Interviews were HARD Until I Learned These 20 Tips by Ashish Pratap Singh
Lifelong Learning: 88+ Resources I Don't Regret as a Senior Software Engineer by
Thank you for reading System Design Classroom. If you like this post, share it with your friends!
Great post, Raul!
Quality knowledge insigh Raul! Duplication is way much better than wrong abstraction.