I restructured our backend's folders exactly once. Three years and 20 engineers later, I still haven't touched it again.
Organize by domain/feature first and technical layer second. A folder should map to an owner, not a technical concern.
architecture
Problem
New engineers couldn’t find where code belonged. Every PR added a file to whatever folder felt closest, and within a year “utils” and “services” had become dumping grounds nobody trusted.
Why it happens
- Folders get organized by technical layer (controllers, services, models) instead of by business domain, so a single feature scatters across five folders
- There’s no owner of the structure, so every engineer makes a locally reasonable choice that’s globally inconsistent
- Nobody revisits the structure until it’s already painful, and by then a rewrite feels too risky
- “Shared” or “common” folders get created for anything that doesn’t obviously belong somewhere else, and become the default answer within weeks
Better approach
- Organize by domain/feature first (billing/, auth/, notifications/), technical layer second
- Each domain folder owns its own controllers, services, and models: no cross-domain reaching in
- Put a one-line README in each top-level folder describing what belongs there and what doesn’t
- Review new top-level folders in PRs the same way you’d review a new dependency
Example
A billing feature touching invoices, refunds, and webhooks lives entirely inside billing/, with billing/invoices.py, billing/refunds.py, billing/webhooks.py. A new engineer working on refunds never has to open auth/ or notifications/ to find what they need.