Monolith
A deployment model where the entire application runs as a single unit.
A monolith is an application deployed and run as a single unit. All the code lives in one process, shares the same memory, and uses the same database.
Monoliths get a bad reputation, but the problems people blame on them are usually caused by poor design, not by the deployment model itself. A codebase without clear boundaries turns into a Big Ball of Mud whether it's a monolith or a set of microservices. The difference is that in a monolith, the mess stays in one place. In a distributed system, it spreads across the network.
A well-structured monolith with clear bounded contexts is a solid starting point for most projects. Each context can have its own domain model, its own package, and well-defined interfaces at the boundaries. This gives you the modularity benefits people expect from microservices without the operational overhead of managing multiple deployments, network calls, and distributed transactions.
The modular monolith takes this further. You structure the code as if it could be split into separate services, but you deploy it as one. If you follow Clean Architecture and keep dependencies pointing inward, switching from a monolith to microservices becomes a deployment decision instead of a complete rewrite.
It's often a pragmatic decision to start with a monolith. Extract services when you have a real reason: independent scaling and deployment needs, or separate team ownership. Many successful microservice stories started with a monolith that got too big and was broken up. The Strangler Pattern can help with that transition when the time comes.
References
- When using Microservices or Modular Monolith in Go can be just a detail? — Compares monolith and microservice architectures built with Clean Architecture. Shows that a well-designed monolith and microservices share the same domain and application layers, differing only in deployment.
- When to avoid DRY in Go — Warns about the distributed monolith anti-pattern. Poorly separated services end up as the same monolith you tried to avoid, plus network overhead and complex tooling.
- The Over-Engineering Pendulum — Argues that sticking with a massive monolith forever is as extreme as starting with a distributed system. Advocates for modular monoliths with the option to extract services when needed.
- Software Dark Ages — Points out that microservices don't guarantee loose coupling. Microservices can be more coupled than a monolith without proper domain boundaries like bounded contexts.
- Distributed Transactions in Go: Read Before You Try — Demonstrates how splitting a monolith into services without proper patterns leads to a distributed monolith with inconsistent state and manual error recovery.
- Common Anti-Patterns in Go Web Applications — Identifies the distributed monolith as a common anti-pattern: tightly coupled monolith replaced with tightly coupled services, adding network overhead without gaining independence.
- The Distributed Monolith Trap (And How to Escape It) — Discusses when to start with a monolith, how to avoid the distributed monolith trap, and when splitting into microservices actually solves real problems like team independence.
- Synchronous vs Asynchronous Architecture — Covers how to avoid distributed monoliths when choosing between sync and async communication patterns. Includes a dedicated segment on distributed monolith pitfalls.
- Event-Driven Architecture: The Hard Parts — Recommends avoiding over-engineering: sometimes synchronous systems or simple monoliths are better than event-driven architectures. Start with sync if unsure.
- Is Clean Architecture Overengineering? — Discusses whether Clean Architecture fits better in monoliths or microservices. A monolith split into modules is similar to microservices, and both benefit from Clean Architecture when the domain is complex enough.