Most projects use a “tier folders” layout where functionality is grouped into folders based on the tier in which they reside.
When tier folders grow large they can be difficult to navigate while working on a single feature. This is because a feature crosses a thin vertical slice of the application while tier folders divide source code into horizontal slices. A developer must glance over many unrelated classes while looking through the tier folder tree in an IDE.
Feature folders can solve this problem. If we arrange functionality by feature rather than by tier a developer can quickly locate every class related to a feature. This leads to a more cohesive development experience.
It becomes easier to verify that a feature has been tested: The test project can be arranged by feature so a developer can glance at the feature folder and corresponding test folder to see if any tests are missing. This is no substitute for running a code coverage analysis but it does reveal testing oversights earlier.
If the feature folders concept were followed religiously it would be difficult to reuse any common code; the entire application would become a collection of isolated transaction scripts. Individual features should still be built atop a common domain model and there is still a need for a services folder which contains domain service logic that is shared between features. Lean towards writing functionality in a feature folder first then refactor into a shared service if it needs to be reused. This will keep features from creeping back into a tier folder layout.
A React + MobX SPA might look like this using tier folders:
src/ services/ ApiService.ts OrderService.ts domain/ Order.ts Account.ts components/ ShowOrderComponent.tsx EditAccountComponent.tsx stores/ ShowOrderStore.ts EditAccountStore.ts models/ ShowOrderModel.ts EditAccountModel.ts
As features are added, the components, stores, and models folders will grow and become harder to navigate when focusing on an individual feature. When we refactor this into feature folders it becomes easier to navigate:
src/ services/ ApiService.ts domain/ Account.ts Order.ts features/ edit-account/ EditAccountComponent.tsx EditAccountStore.ts EditAccountModel.ts show-orders/ ShowOrderComponent.tsx ShowOrderStore.ts ShowOrderModel.ts OrderService.ts
When developing a Web Api there should still be a separation between controllers and domain logic but Api models and domain services can be refactored into commands and command handlers in the domain. Controllers then become very thin and effectively just delegate commands to handlers.
A Web Api might look like this with tier folders:
ProjectName.Domain/ Model/ Order.cs Account.cs Services/ OrderService.cs AccountService.cs ProjectName.WebApi/ Controllers/ OrderController.cs AccountController.cs Models/ EditAccountModel.cs ShowOrderModel.cs
And after refactoring to feature folders (and separating queries from commands) it would look like this:
ProjectName.Domain/ Model/ Order.cs Account.cs Features/ EditAcccount/ EditAccountCommand.cs EditAccountCommandHandler.cs Queries/ ShowOrderQuery.cs ProjectName.WebApi/ Controllers/ OrderController.cs AccountController.cs
In this design the AccountController delegates an EditAccountCommand (a simple DTO) to an EditAccountCommandHandler. The OrderController invokes the ShowOrderQuery and maps the result to a shape appropriate for the consumers of the Api. I find that commands and command handlers make controllers so thin that individual actions don’t need to be refactored out into feature folders. This allows us to maintain a REST-style Api surface while still giving us the freedom to do feature folders beneath the Api.
Side effects of feature folders
Feature folders make the onboarding experience easier for developers joining an existing project. A developer can focus on narrow sections of a feature folder project and become productive sooner, whereas a tier folder project raises questions about where particular modules of a feature should be stored and usually require the developer to comprehend every tier before they can begin work.
Feature folders improve refactorability because each feature is compartmentalised. For the same reason, commits to feature folders are easier to code review because diffs tend to be more cohesive than in tier folder projects.