Summary
A modular monolith keeps one deployment unit while enforcing clear internal module boundaries. For solo developers and small teams working more often with AI, it is often more practical to start this way than to take on microservices/MSA operational complexity too early.
As someone who runs a company and still writes code, I spend more time building with AI than before. The question has shifted from “Should this be MSA because it may grow?” to “Can I keep deployment simple while giving both humans and AI clear module boundaries?” This article compares monoliths, modular monoliths, and microservices, then turns that into practical decision criteria.
Table of contents
What this article covers
- Background
- The three architectures in plain language
- Monolith vs Modular Monolith vs MSA
- Why modular monoliths fit solo developers plus AI
- Design criteria that matter in practice
- Development and operations considerations
- Migration path from modular monolith to microservices
- Common points of confusion and failure modes
- When each architecture fits
- Conclusion
- References
Background
For a while, microservices were treated as the default answer for serious systems. Split services by capability, deploy them independently, isolate failures, and let teams move independently. Those benefits are real at the right scale.
But small teams feel the cost quickly. Even five services mean more authentication paths, networking, deployment pipelines, logs, tracing, failure points, and data consistency problems. You may end up spending more time connecting services than building the product.
A modular monolith is not a return to an unstructured old monolith. The idea is closer to this: think about domain boundaries as seriously as you would in MSA, but keep deployment and operations simple until the business actually needs distribution.
The three architectures in plain language
Traditional monolith
A monolith has one codebase, one deployment unit, and one runtime. It is fast to start, easy to run locally, simple to debug, and can rely on straightforward database transactions.
The problem appears when boundaries disappear. Order code directly changes member tables, payment code knows shipping internals, and every feature depends on every other feature. At that point it is not “one deployable application”; it is “one application everyone is afraid to change.”
Modular monolith
A modular monolith is still deployed as one application, but its internals are divided by domain modules. A member, order, payment, and notification module should own its own rules and expose only intentional entry points.
src/
member/
order/
payment/
notification/
shared-kernel/ # keep this small
This is the healthy shape: one runtime for operational simplicity, but separate modules for understanding, testing, and future extraction.
MSA / Microservices
Microservices split a system into independently deployed services, usually aligned with business capabilities. Each service tends to own its own data and communicates through HTTP, gRPC, messaging, or events.
The benefits are independent scaling, independent deployment, and stronger failure isolation. The cost is distributed systems work: timeouts, retries, idempotency, observability, eventual consistency, and deployment automation.
Monolith vs Modular Monolith vs MSA
| Criterion | Monolith | Modular Monolith | MSA / Microservices |
|---|---|---|---|
| Deployment unit | One | One | Many services |
| Internal boundaries | Easy to lose | Enforced by domain modules | Enforced by service boundaries |
| Initial speed | Fast | Fast, with architecture discipline | Often slower |
| Operational complexity | Low | Low to medium | High |
| Data consistency | Simple local transactions | Still manageable if ownership is clear | Distributed transactions or eventual consistency |
| Failure isolation | Weak | Code isolation, not process isolation | Stronger |
| Team fit | Small teams | Solo to small teams with domain complexity | Domain-aligned teams |
| Working with AI | Hard when context grows | Good module-sized context | Small service context, but more ops knowledge |
| Future scaling | Hard if boundaries are missing | Easier extraction if boundaries are real | Already distributed, but pays distributed cost |
Why modular monoliths fit solo developers plus AI
AI works better with bounded context
When a project is tangled, prompts get longer and riskier. With real module boundaries, you can tell the AI: “Work only inside the order module. Call payment through its public interface. Do not edit payment internals.” That instruction only works when the codebase already enforces the boundary.
Deployment simplicity protects development speed
For solo developers, every extra service becomes another thing to deploy, monitor, secure, and debug. AI may write code quickly, but one person still owns production. A single deployable application is often the right bottleneck reducer.
Testing can still be modular
You do not need separate services to test by boundary. A good baseline is:
module unit test → module integration test → application smoke test
This gives AI-generated changes a safety net without forcing a distributed runtime too early.
Design criteria that matter in practice
Start with domain boundaries
Do not begin with pretty folders. Begin with ownership: which part of the business changes for which reason? Member, order, payment, settlement, and notification often change for different reasons, so they are good module candidates.
Create module access rules
The common failure mode is splitting folders while still importing another module’s internals. Expose application services, facades, or events. Hide repositories and domain internals.
One database is fine; unclear ownership is not
A modular monolith can start with one database. The important rule is table ownership. If the order module freely updates payment tables, future extraction will be painful.
Keep shared code small
A large common package becomes a hidden monolith. Keep shared code limited to stable types and utilities. Business rules should live in the owning module.
Development and operations considerations
Think about observability early
Even in one application, logs should include module name, request ID, and domain identifiers.
module=order request_id=... order_id=... event=OrderCreated
This habit makes future service extraction much easier.
One deployment does not mean huge releases
Use small pull requests, feature flags, module-level changelogs, and smoke tests. A single deployable artifact can still be released safely in small increments.
Measure before splitting
Do not extract a service just because one part “might” need to scale someday. First check whether the bottleneck is real, whether caching or query tuning helps, and whether independent deployment would actually pay for itself.
Migration path from modular monolith to microservices
A healthy strategy is not “start with microservices just in case.” It is “build boundaries now, extract later when there is a real reason.”
1. Enforce boundaries in code
Use package dependency checks, architecture tests, or tools such as Spring Modulith to make module rules part of CI, not tribal memory.
2. Clarify data ownership
The candidate module should own its tables. Other modules should stop writing those tables directly and go through public APIs or events.
3. Introduce internal events first
You do not need Kafka on day one. Start by expressing facts such as “OrderCreated” and “PaymentCompleted” internally. That makes future messaging much more natural.
4. Extract only when the reason is strong
Good reasons include independent scaling, independent deployment, failure isolation, or separate team ownership. Without one of those, keeping the module inside the monolith is often better.
Common points of confusion and failure modes
“If I split folders, is it modular?”
Not necessarily. The test is dependency direction, not folder names. If every module can call every internal repository, the boundary is not real.
“Is MSA always more modern?”
Microservices are powerful, but not free. Service discovery, gateways, monitoring, tracing, deployment automation, and data consistency become product work.
“Does one database block future extraction?”
Not by itself. Missing ownership is the bigger problem. A single database with clear table ownership is often easier to evolve than many services sharing the same tables.
“Does AI make architecture less important?”
No. It makes boundaries more important. AI moves fast inside clear constraints; without constraints, it can make broad, risky edits.
When each architecture fits
Choose a traditional monolith when
You are building a prototype, a short-lived internal tool, or a simple CRUD system. If the product may live for a while, add at least light module boundaries early.
Choose a modular monolith when
You are a solo developer, a small startup team, or a team building with AI where the domain is becoming complex but the organization is not yet split by services. This is the default I would choose for many new AI-assisted products today.
Choose MSA when
You have domain-aligned teams, real independent deployment needs, clear scaling or isolation requirements, and enough operational maturity to run distributed systems.
Conclusion
My practical conclusion is this: for solo developers plus AI, and for many small teams, a modular monolith is the most realistic default. It keeps deployment simple while giving humans and AI understandable boundaries.
That does not mean “never use microservices.” When the organization, traffic, deployment needs, and failure-isolation needs justify the cost, MSA can be the right move. The key is to start with boundaries, not with service count.
What do you think? In the AI-assisted development era, should modular monoliths become the default architecture for solo developers and small teams?
References
- Microsoft Azure Architecture Center – Microservices architecture style: https://learn.microsoft.com/en-us/azure/architecture/guide/architecture-styles/microservices
- Microsoft Azure Architecture Center – Use domain analysis to model microservices: https://learn.microsoft.com/en-us/azure/architecture/microservices/model/domain-analysis
- AWS Prescriptive Guidance – Decomposing monoliths into microservices: https://docs.aws.amazon.com/prescriptive-guidance/latest/modernization-decomposing-monoliths/welcome.html
- Martin Fowler – Microservices: https://martinfowler.com/articles/microservices.html
- Martin Fowler – Monolith First: https://martinfowler.com/bliki/MonolithFirst.html
- Spring Modulith Reference Documentation: https://docs.spring.io/spring-modulith/reference/
- Thoughtworks – When (modular) monolith is the better way to build software: https://www.thoughtworks.com/insights/blog/microservices/modular-monolith-better-way-build-software
- Team Topologies – Key concepts: https://teamtopologies.com/key-concepts
Leave a Reply