Monolithic Architecture
The Project That Proved Monolith Is Not a Dirty Word
When I started building my multi-tenant POS system, I made the mistake of going straight to microservices. Six services, Docker Compose, service discovery, inter-service JWT propagation β all of it before I had written a single business rule.
Three weeks in, I was spending more time wiring infrastructure than building the product. I scrapped it and started over as a monolith. Within two days I had the core order flow working end to end.
The monolith shipped. The microservices rewrite came later β only after the domain was well understood and the team had grown.
A monolith is not a failure. It is often the right tool.
Table of Contents
What Is a Monolith?
A monolithic application is deployed as a single, self-contained process. All functionality β authentication, order processing, inventory, reporting β runs in the same process, shares the same memory space, and is deployed as a single unit.
All modules communicate through in-process function calls β no HTTP, no message queues, no serialisation overhead. This is one of its biggest strengths.
Anatomy of a Monolithic Application
Here is the structure I used for the early version of my POS system before splitting it:
Each module is logically separated, but they are all part of the same codebase and process.
Types of Monoliths
1. Single-Process Monolith
Everything runs as one OS process. This is the typical monolith most people think of.
2. Modular Monolith
Modules have explicit, enforced boundaries inside a single deployment. Dependencies between modules go through defined interfaces, not direct imports. This is the sweet spot: the simplicity of a monolith with enforced internal structure.
I cover modular monolith in detail in Article 02: Modular Monolith Architecture β including the $50,000 lesson that convinced me it was the right starting point.
3. Distributed Monolith (Anti-Pattern)
Multiple services that are still tightly coupled β every deployment requires coordinating all of them, and they share a database. This is the worst of both worlds. I have built one accidentally and it is painful.
When to Choose Monolithic Architecture
I reach for a monolith when:
The domain is not yet understood. If I do not know the natural boundaries of the system, splitting it prematurely creates the wrong seams.
The team is small (1β3 people). Microservices have a coordination overhead that kills small teams.
Speed of iteration matters. In-process function calls, shared transactions, and a single debugger session make development faster.
The load is predictable and modest. No need to scale individual components independently.
I am building a proof of concept. Starting monolithic, then extracting services once the domain stabilises, is a proven strategy.
When to Avoid It
A monolith starts to hurt when:
Different parts need to scale independently. If the reporting module saturates CPU while orders need low latency, you need separation.
Multiple teams own different areas. Shared codebase β merge conflicts β coordination overhead.
Deployment becomes a bottleneck. A bug fix in the payments module requires redeploying the whole application.
Technology diversity is required. You cannot run a Python module and a Go module in the same process.
Practical Example
Here is a minimal FastAPI monolith β the kind I would build on day one of a new project:
The key insight: OrderService calls InventoryService and PaymentService via direct Python calls. No serialisation, no network latency, no partial failure handling needed. The entire operation lives inside a single database transaction.
Migrating Away From a Monolith
When the monolith starts to show strain, I use the Strangler Fig pattern:
Identify the module causing the most pain (usually the one with the most independent scaling needs or team ownership issues)
Extract it behind an interface first β do not change callers yet
Deploy the extracted service alongside the monolith
Route traffic to the new service gradually
Retire the old module from the monolith once proven stable
This is exactly how I split the chatbot service out of my POS monolith β payments stayed in the monolith for another year because there was no reason to extract it yet.
Lessons Learned
Starting monolithic is not technical debt β it is pragmatism. Technical debt is a bad monolith with no module boundaries.
Name your modules like services from day one. Even in a monolith,
orders/,inventory/, andpayments/directories with explicit interfaces make extraction easier later.A single database is a feature, not a problem. ACID transactions across modules are one of the monolith's biggest advantages.
Do not let "monolith" become an excuse for a big ball of mud. Enforce module boundaries even when the compiler does not force you to.
Extract when you feel the pain, not when you read that you should.
Last updated