Draft / Scheduled Content
This article is a draft or scheduled for future publication. The content is subject to change.
Microservices: The Great Architecture Grift of the 2010s
The Distributed Monolith
Around 2014, a collective madness infected the software engineering industry.
Suddenly, the “Monolith” was declared a legacy relic. It was a big, scary, unmaintainable ball of mud. The future belonged to “Microservices”—small, independent, single-responsibility services communicating over the network. We were promised independent scaling, independent deployments, technology diversity, and clean domain boundaries.
So, teams of five developers carved up their simple web apps into twelve different services.
Ten years later, the bills have come due.
Instead of decoupled, agile microservices, most organizations ended up with a distributed monolith. They have all the coupling of a monolith (if you change a field in the User service, three other services break) but with all the complexity, latency, and failure modes of a distributed system.
We traded in-memory function calls for HTTP requests. It is one of the worst architectural bargains in the history of computer science.
The Microservices Tax
When you move your application boundaries from a single process to the network, you pay a massive tax:
1. Network Reliability
In a monolith, calling another module is a CPU instruction. It takes nanoseconds and it is guaranteed to succeed.
In microservices, calling another module is a network request. It can time out. It can fail because of packet loss. It can get rate-limited. It can fail because the target service is restarting.
Suddenly, your code is littered with retry logic, circuit breakers, backoff algorithms, and fallback handlers. You have to learn about the CAP theorem the hard way.
2. Data Consistency and the Death of JOINS
This is where microservices truly hurt. Each microservice must own its database.
Need to show a list of orders with the customer’s name and the product’s current stock? In a monolith, that is a simple SQL query:
SELECT o.id, u.name, p.stock
FROM orders o
JOIN users u ON o.user_id = u.id
JOIN products p ON o.product_id = p.id;
In a microservices architecture, you can’t join across databases. You have to:
- Query the Order service for orders.
- Extract the user IDs and product IDs.
- Make an HTTP request to the User service to fetch users.
- Make another HTTP request to the Product service to fetch products.
- Manually stitch the data together in memory on the API Gateway or client.
If one of those requests fails, what do you show the user? What about transactions? If a user buys a product, you need to charge their card (Billing service), deduct inventory (Inventory service), and create an order (Order service). If the inventory deduction fails after the card is charged, how do you roll back?
You are forced to implement complex distributed transaction patterns like Sagas, Outbox patterns, and asynchronous message queues (RabbitMQ, Kafka). You are building a database engine rather than writing business logic.
sequenceDiagram
participant Gateway
participant OrderService
participant InventoryService
participant BillingService
Gateway->>OrderService: Create Order
OrderService->>InventoryService: Deduct Inventory
alt Inventory is Available
InventoryService-->>OrderService: Inventory Deducted
OrderService->>BillingService: Charge Card
alt Billing Fails
BillingService-->>OrderService: Billing Failed
OrderService->>InventoryService: Rollback/Compensate Inventory (Saga Pattern)
OrderService-->>Gateway: Order Failed (Billing Error)
else Billing Succeeds
BillingService-->>OrderService: Card Charged
OrderService-->>Gateway: Order Created Successfully
end
else Inventory is Out of Stock
InventoryService-->>OrderService: Out of Stock
OrderService-->>Gateway: Order Failed (Out of Stock)
end
3. Operational Overhead
Instead of monitoring one application, you are monitoring twenty. You need centralized logging (ELK stack), distributed tracing (Jaeger/Zipkin) to figure out which service is slow, service discovery (Consul/DNS), and complex CI/CD pipelines to deploy everything.
You need a platform team just to keep the lights on.
The Myth of Technology Diversity
One of the main arguments for microservices was: “You can write the Billing service in Go, the Recommendation service in Python, and the frontend service in Node.js!”
In practice, this is a nightmare.
It means every developer has to be a polyglot, or your teams are completely siloed. Shared libraries (like database helpers or logging formats) have to be rewritten for every language. Security patches have to be applied to five different runtimes.
Most successful companies that use microservices eventually enforce strict language guidelines (e.g., “everything is Java” or “everything is Go”) to prevent this chaos. The technology diversity benefit turned out to be a trap.
The Modular Monolith: A Better Way
The problem with monoliths wasn’t the single process. The problem was bad code design. Developers wrote spaghetti code, coupling modules tightly together in memory.
But you don’t need to put a network boundary between modules to keep them clean. You just need discipline.
Enter the Modular Monolith.
In a modular monolith, your application is a single deployable unit, but its internal structure is strictly segregated. Modules communicate through defined, public interfaces (APIs). Database tables are logical segregated (using schemas or prefixes) and modules are not allowed to query other module’s tables directly.
Monolithic Process
+------------------------------------------+
| User Module -> [Public User API] |
| | |
| v (In-memory method call) |
| Order Module -> [Public Order API] |
+------------------------------------------+
If you design your modular monolith correctly:
- It is extremely fast (no network latency).
- It is easy to test (no mock network services).
- It is simple to deploy (a single container or binary).
- If you ever need to scale a specific module, you can extract it into a separate service later because the logical boundaries are already clean.
It is far easier to split a clean monolith into microservices than it is to merge a mess of microservices back into a monolith.
Conway’s Law and Why Microservices Exist
Microservices were not invented to solve technical problems. They were invented to solve organizational problems.
Conway’s Law states that organizations design systems that copy their communication structures. When companies like Amazon or Netflix grew to thousands of developers, they could no longer work on a single codebase without stepping on each other’s toes. Merge conflicts, deployment queues, and coordination overhead were killing productivity.
Microservices allowed them to break the organization into independent teams that could ship code without coordinating with other teams.
If your team is less than 50 developers, you do not have this problem. You can coordinate easily. Splitting your app into microservices is solving a coordination problem you don’t have, by introducing technical problems you don’t want.
Choose the Monolith First
Don’t let the blogs of tech giants dictate your architecture. They are playing a different game with different resources.
Build a monolith. Keep it modular. Keep it clean. Spend your cognitive energy on building features that your users actually care about. If you ever reach the scale where the monolith is genuinely blocking your engineering velocity, you will have the money and the context to refactor it. Until then, stay in one piece.
Related Content
Modular Monoliths Win More Fights Than Microservices
Why the modular monolith is often the most honest architecture choice: less ceremony, fewer distributed systems surprises, and more time spent shipping actual product instead of negotiating with Kubernetes.
Why Clean Architecture is a Maintainability Nightmare
Robert C. Martin's 'Clean Architecture' promises to decouple your business logic from external frameworks and databases. In practice, it leads to a sprawling wasteland of boilerplate, interface layers that pass data straight through, and excessive mapping functions. Here's why YAGNI should trump architecture dogmatism.
Software Architecture Is Mostly About Boundaries
A practical guide to drawing boundaries that survive contact with reality: APIs, modules, ownership, and the uncomfortable fact that most bugs are boundary bugs wearing a fake mustache.