Context
A 4-year-old Angular monorepo organised by technical layers (components/, services/, models/). Adding a feature meant editing dozens of unrelated files, and ownership was unclear. Onboarding new engineers consistently took longer than it should have.
Constraints
No rewrite. Tens of thousands of lines of business logic to preserve. Multiple teams working concurrently. Routing and shared infrastructure had to stay stable.
Decisions
Move to a feature-driven structure where each feature owned its components, state, services and tests. Define explicit boundaries between features (no cross-imports outside their public API). Keep `core/` and `shared/` for genuinely cross-feature concerns and audit them aggressively.
Implementation
Started with two pilot features migrated end-to-end as references. Introduced a public-API barrel per feature and an ESLint rule banning deep imports. Migrated remaining features incrementally, prioritising the ones with the most cross-cutting changes.
Outcome
Feature work became local: most PRs touch one feature folder instead of five technical layers. Ownership became visible from the file tree. New engineers reach productive PRs measurably faster.
Outcome metrics
- Maintainability
- Feature ownership visible from the directory tree
- Developer experience
- Most PRs scoped to a single feature folder
- Scalability
- Boundaries enforced by lint, not convention