In software engineering, change doesn't only happen at the language or framework level. One thing that quietly evolves alongside everything else is how we structure our project folders.
Many junior developers treat folder structure as just a matter of tidiness. But folder structure actually reflects the engineering mindset of its era.
The more mature our understanding of software engineering, the more deliberately we organize our code.
---
Evolution Timeline (1990–2026)
Before diving into each approach, it helps to see the big picture — how these changes actually unfolded over time.
| Period | Approach | What Drove the Change |
|---|---|---|
| 1990–1999 | No convention (flat files) | The web was brand new — the only goal was "make it work" |
| 2000–2004 | MVC enters the web | Struts (2000), Spring Framework (2003), Martin Fowler's book (2002) |
| 2005–2009 | MVC becomes the standard | Ruby on Rails (2005), Django (2005) — "convention over configuration" |
| 2010–2014 | Layered + DDD goes mainstream | Node.js/Express, Domain-Driven Design adopted widely, microservices begin circulating |
| 2012–2016 | Clean Architecture & Microservices | Uncle Bob introduces Clean Architecture (2012), Docker (2013), Netflix & Amazon pioneer microservices |
| 2016–2019 | Feature-Based & Container era | Kubernetes widespread (2016–2018), NestJS (2017), serverless functions take off |
| 2020–2022 | Modular Monolith returns | Microservices deemed too complex for many teams, monorepo tools (Nx, Turborepo) rise in popularity |
| 2023–2026 | Plurality & context-first | No single best architecture — the right choice depends on scale, team, and iteration speed |
---
The Beginning: When Applications Were Simple
In the early days of application development, system requirements were relatively modest:
Naturally, folder structures were kept simple.
/controllers
/models
/views
This layout was popularized by the MVC (Model-View-Controller) pattern.
Files were grouped by type — all controllers in one place, all models in another.
Strengths
Weaknesses
As the application grows:
When Simple MVC Makes Sense
This structure works well for:
Real-world example: a new startup building a waitlist registration page with email notification. Simple MVC is more than enough.
---
The Growth Phase: Layered Architecture
As applications grew more complex, the layer-based approach emerged:
/controllers
/services
/repositories
/models
Business logic moved into /services, and database access was isolated in /repositories.
This was an important step forward — developers started internalizing concepts like:
The Positive Impact
Code became more structured, and each layer had a clearer responsibility.
However, as systems scaled further, a new problem surfaced.
To understand the "Order" feature, a developer now had to open:
Everything was still scattered — just more neatly organized.
When Layered Architecture Makes Sense
This approach fits well when:
Real-world example: a 3-developer team building an API for a retail point-of-sale app. Features include products, transactions, and reports. Layered architecture makes task delegation easier without adding unnecessary overhead.
---
The Modern Era: Feature-Based Structure
As teams and products continued to grow, the focus shifted from "file type" to business feature.
A structure like this emerged:
/modules
/users
/orders
/payments
And inside each module:
/orders
order.controller.ts
order.service.ts
order.repository.ts
order.model.ts
order.dto.ts
order.spec.ts
Why This Became Preferred
Because developers work around features, not file types.
When asked to work on the Order Module, everything needed — including tests — lives in one place.
Best Fit For
When Feature-Based Makes Sense
Real-world example: a fintech startup building a lending app with modules for users, loans, repayments, notifications, and reports. Each squad can own their module independently without constant merge conflicts.
---
The Maturity Stage: Clean Architecture
As software engineering matured, deeper requirements emerged:
Clean Architecture addresses all of these:
/src
/Domain
/Entities
/ValueObjects
/Repositories (interfaces)
/Application
/UseCases
/DTOs
/Infrastructure
/Database
/ExternalAPIs
/Presentation
/Controllers
/Middlewares
The Philosophy
Dependencies must point inward — toward the business rules — never outward.
This means:
When Clean Architecture Makes Sense
This is not for every project. It's the right choice when:
Real-world example: a digital insurance platform subject to strict financial regulation. The domain rules for premium calculation and claims processing are highly complex. Clean Architecture lets the team swap payment providers and update business rules without risking breakage elsewhere.
---
Enter Microservices
When a single application becomes too large to manage, organizations began decomposing their systems into independent services:
/user-service
/payment-service
/product-service
/notification-service
/api-gateway
Each service has its own internal structure, its own database, and its own deployment pipeline.
This is no longer just about folder structure — it's an evolution toward system architecture.
When Microservices Make Sense
Microservices are not a default solution. They solve a specific class of scaling problems:
Real-world example: an e-learning platform with millions of users. The video-streaming service requires dedicated CDN infrastructure, while the quiz service runs on a small server. Microservices allow both to be scaled independently based on their own demands.
---
Between Monolith and Microservices: Modular Monolith
When Clean Architecture is already in place but the team isn't ready for the operational complexity of microservices, there's one option that's often skipped: Modular Monolith.
A Modular Monolith is still a single deployment unit — like any monolith — but module boundaries are enforced explicitly by tooling, not just folder conventions.
/modules
/orders
/public ← the only part other modules may access
index.ts
/internal ← hidden from the outside world
service.ts
repository.ts
/notifications
/public
index.ts
/internal
service.ts
What sets it apart from Feature-Based:
orders module can directly import any class from the notifications moduleorders can only access what notifications explicitly exposes through its public APIinternal keyword (C#), or import-linter (Python) — not just team conventionWhy This Matters
Modular Monolith serves as a very useful bridge:
When Modular Monolith Is the Right Fit
Real-world example: a Series B fintech startup with 15 developers. The system has outgrown Feature-Based but doesn't yet need microservices infrastructure. Modular Monolith lets them maintain iteration speed while keeping domain boundaries clean and ready for future splitting.
---
Can Every Architecture Be Tested
Yes — all of them can. But the quality and ease of testing vary significantly.
| Architecture | Unit Test | Integration Test | Mocking Ease |
|---|---|---|---|
| Simple MVC | Hard — logic and I/O are mixed in the controller | Possible, but requires a real database | Must mock many things at once |
| Layered | Medium — service layer is testable, but coupling often lingers | Easier per layer | Repository can be mocked |
| Feature-Based | Easy — test files live inside the module | Per-module, isolated | Dependency injection helps |
| Clean Architecture | Very easy — Domain Entity is a pure object with zero dependencies | Only at the Infrastructure layer | Ports/interfaces are designed to be mocked |
| Modular Monolith | Easy — same as Clean Architecture per module | Module contract test via public API only | Mock only through the module's public interface |
| Microservices | Easy per service | Requires contract testing between services | Each service is independent, but external services must be mocked |
Key Insights
Simple MVC — testing is possible but painful. It usually ends up as heavy integration tests because business logic cannot be separated from HTTP and the database.
Clean Architecture — the only architecture explicitly designed with testability as a primary goal. The domain layer can be tested 100% without a database, without HTTP, without any framework.
Microservices — introduces a new challenge: contract testing. Unit testing per service is straightforward, but ensuring two services can actually communicate correctly requires additional tooling such as PactNet (.NET) or Pact.js (Node.js).
If testing is the top priority, the architectural choice narrows to Clean Architecture — and the larger the system, the greater the benefit.
---
What's Really Evolving
It's not the folders themselves.
What evolves is our understanding of software complexity.
Folders are just a representation of engineering thinking.
From simply tidying up files, to managing change, teams, risk, and business scale.
---
Is Modern Structure Always Better
No.
For small projects, simpler structures are often more effective.
A small internal tool:
/controllers
/models
Is perfectly sufficient.
Forcing enterprise architecture onto a small project leads to overengineering — wasting time on infrastructure setup that isn't needed, and slowing down iteration at a stage that should be fast.
Quick Reference Guide
| Team Size | Complexity | Recommended Architecture |
|---|---|---|
| 1–2 people | Low | Simple MVC |
| 2–5 people | Medium | Layered |
| 5–20 people | High | Feature-Based |
| 10–30 people | Very High | Clean Architecture or Modular Monolith |
| 20–50 people | Enterprise (single deployment) | Modular Monolith |
| 50+ people | Enterprise (distributed) | Microservices |
The Right Principle
Choose a folder structure based on:
Not because of internet trends.
---
Conclusion
Yes, folder structure evolves alongside software engineering itself.
The journey looks roughly like this:
> Simple MVC → Layered → Feature-Based → Clean Architecture → Modular Monolith → Microservices
Each stage emerged to solve the problems of the stage before it.
Folder structure is not decoration.
It is the footprint of how humans have learned to build increasingly complex software.
---
Closing Thoughts
A mature developer doesn't ask:
> "What is the best folder structure?"
They ask:
> "What structure best fits the problem I'm solving right now?"
That is genuine engineering thinking.