DEV Community

Mustafa ERBAY
Mustafa ERBAY

Posted on • Originally published at mustafaerbay.com.tr

Monolith is Still Not Dead: Why I Returned from the Microservices

While writing the shipping module of a production ERP, we decided to split everything into microservices just because it was trendy at the time. The result? At 04:12 AM on a Tuesday morning in 2021, due to a momentary MTU mismatch in the network, the service issuing the invoice and the service deducting the stock couldn't find each other; the production line stopped for exactly 4 hours due to data inconsistency.

I remember like it was yesterday slamming my coffee mug on the table that morning and asking, "What are we doing?" Instead of writing code, we were monitoring network packets, trying to resolve authentication between services, and doing backflips to manage distributed transactions. That was the day I rediscovered the value of monolithic architecture and the simplicity we had lost.

Sanity Lost in Pursuit of Trends

For the last ten years, such an atmosphere has been created in the industry that developers writing monoliths were looked down upon as if they were left in the stone age. At conferences and on blogs, everyone talked about Netflix-scale systems and romanticized microservices. I also got caught up in this hype, unnecessarily breaking systems apart in several projects, and hit the exact same wall every single time.

Microservices architecture was born not out of a technical necessity, but to solve the organizational problems of massive enterprises employing hundreds of developers. If you are a team of 10-15 people or developing a side project on your own, using microservices is no different than shooting yourself in the foot. You end up dealing with a ton of artificial problems like network overhead, serialization costs, and eventual consistency.

# Transaction management is simple in a monolith
with database.transaction():
    create_invoice(order_id)
    decrease_stock(product_id)
    # If an error occurs, everything is automatically rolled back
Enter fullscreen mode Exit fullscreen mode

The simple code block above runs safely in milliseconds within a monolith using a single database connection. When you attempt to do the same operation across microservices, you have to set up systems like the Saga Pattern, Outbox Pattern, or two-phase commit (2PC), which multiplies the margin of error.

One Database, One Process: The Power of Simplicity

I designed the backend of a financial calculator side project that I developed—which receives hundreds of requests per second—entirely as a monolith. It has a single PostgreSQL database, an Nginx reverse proxy in front of it, and a single monolith process running on FastAPI. I manage everything inside a single server with Docker Compose, and the system runs like clockwork.

When you configure database indexes correctly (B-tree and BRIN) and set up PostgreSQL's connection pool settings properly, a single database will carry you further than you can imagine. The " network latency" brought by distributed systems does not exist in a monolith because data flows much faster in memory or over the local network.

ℹ️ Pragmatic Approach

Software architecture is often about organizational flow, not code design. If your team sits in the same room, splitting the database does nothing but throw stones at each other over the network.

The Hidden Costs of Distributed Systems

When you switch to microservices, you don't just split your code; you also increase your operational burden at least fivefold. While you used to find errors from a single log file (journald) under systemd, you now have to set up distributed tracing tools. Finding which service the error started in and which HTTP request it propagated through becomes absolute detective work.

Last year, at a company I was consulting for, we spent a week resolving latency caused by a chain of 12 different services calling each other behind an API gateway. The problem was neither in the code nor in the database; it stemmed entirely from unnecessary network traffic between services. When we consolidated the system into 3 critical monolithic parts, latency dropped by 70%.

When to Split? (A Realistic Boundary)

I am not a monolith fanatic, nor am I saying every problem should be hit with a monolith. If a part of your system (for example, image processing or heavy AI planning algorithms) consumes too much CPU and slows down the rest of the application, you can isolate just that part and make it an independent service. We call this a "Modular Monolith," and I think it is the healthiest architectural approach right now.

When designing a modular monolith, you keep your codebase clean with folders and independent modules. If you truly need to separate a module tomorrow, turning it into a microservice is just a few hours of work because its boundaries are already well-defined. Instead of splitting everything from the start and drowning in network complexity, starting with a monolith and growing as you feel the pain is always less costly.

What about you? Have you ever pulled an all-nighter during a microservices adventure you embarked on just because it was "trendy," or are you one of those who appreciate the simplicity of a monolith? Let's discuss in the comments.

Top comments (0)