After working on .NET applications for the past six years, I recently spent a few months using Ember.js and AngularJS. Both originally supported organizing files in a project by type: separate top-level directories for models, controllers, views, etc. But this has changed over the past few years to prefer organizing by feature area—Ember with pods Angular with modules.
Angular Project Layout
Consider the structure of the angular-seed app:
app/ --> all of the source files for the application app.css --> default stylesheet components/ --> all app specific modules version/ --> version related components version.js --> version module declaration and basic "version" value service version_test.js --> "version" value service tests version-directive.js --> custom directive that returns the current app version version-directive_test.js --> version directive tests interpolate-filter.js --> custom interpolation filter interpolate-filter_test.js --> interpolate filter tests view1/ --> the view1 view template and logic view1.html --> the partial template view1.js --> the controller logic view1_test.js --> tests of the controller view2/ --> the view2 view template and logic view2.html --> the partial template view2.js --> the controller logic view2_test.js --> tests of the controller app.js --> main application module index.html --> app layout file (the main html template file of the app) index-async.html --> just like index.html, but loads js files asynchronously karma.conf.js --> config file for running unit tests with Karma e2e-tests/ --> end-to-end tests protractor-conf.js --> Protractor config file scenarios.js --> end-to-end scenarios to be run by Protractor
Note that instead of grouping view1.html and view2.html together in a views directory, they’re placed beside the corresponding controller and tests. You can read more about comparing this with the traditional Angular project structures in this post by Cliff Meyers. I’ll use the rest of this post to discuss the concepts in a .NET context.
Typical C# Project Structure
Here’s an example of your typical WPF MVVM project structure. There’s a Models directory, a Views directory, and a ViewModels directory. Easy, right? If you need to find the Customer model, go check the models directory.
App.sln Proj1.csproj Views/ Customer.xaml Order.xaml ViewModels/ CustomerViewModel.cs OrderViewModel.cs Models/ Customer.cs Order.cs Common/ Utilities.cs SharedCode.cs Proj1UnitTests.csproj ViewModels/ CustomerViewModelTests.cs OrderViewModelTests.cs Models/ CustomerTests.cs OrderTests.cs UtilityTests.cs
What happens if we need to add a new feature for shopping cart functionality? We just add the new ShoppingCart.cs tests and model, the new viewmodel files, and finally the view itself. This looks like 90+% of the .NET projects I’ve ever come across. How does it compare to a module oriented approach like that of the angular-seed app?
Feature-Oriented C# Project Structure
App.sln Proj1.csproj Customer/ Customer.cs Customer.xaml CustomerViewModel.cs Order/ Order.cs Order.xaml OrderViewModel.cs Common/ Utilities.cs SharedCode.cs Proj1UnitTests.csproj Customer/ CustomerViewModelTests.cs CustomerTests.cs Order/ OrderTests.cs OrderViewModelTests.cs UtilityTests.cs
Now our directories are organized by feature (Customer, Order) instead of by type (Model, View, ViewModel). Need to find the Customer model? It’s right where you’d expect, in the Customer directory. Instead of being beside other models on disk, it’s now co-located with the other Customer files that actually use it. What if we need to add in new shopping cart functionality? Just create the new ShoppingCart*.cs files all in the same folder and follow the same pattern for the tests.
If using the feature approach appeals to you, I think there’s one more logical step to take. The project structure above still has a duplicate directory tree for the tests. What if we move the unit tests beside the code they’re testing?
App.sln Proj1.csproj Customer/ Customer.cs CustomerTests.cs Customer.xaml CustomerViewModel.cs CustomerViewModelTests.cs Order/ Order.cs OrderTests.cs Order.xaml OrderViewModel.cs OrderViewModelTests.cs Common/ Utilities.cs UtilityTests.cs SharedCode.cs
Now it’s trivial to find related files without needing to traverse the entire app’s directory structure. It’s also clear exactly which files have corresponding unit tests and which ones are missing. Further, if we add in the ShoppingCart files, they all land in a single folder.
Handling Namespaces & Deployment
There are two related issues that still need to be addressed:
- Should namespaces still match the directory layout on disk? For a small example project it won’t matter much either way. For now, code in my Common directory is in the default namespace where it is automatically in scope for any source code in nested namespaces. As my project grows, I’ll see what works best.
- Do you deploy the unit tests in your dll? For my current app there’s no harm in doing so and it’s default result unless I put in extra effort. In a more commercial/public facing project, the safe answer is to just #ifdef your test code and exclude it for production builds. If references added for testing dlls aren’t actually used by code in the production compilation, the compiler can skip emitting those references in the output dll.
If this feature-oriented approach becomes more popular for .NET code, I’d expect the tooling to improve to support it directly. With the new project.json files for dnx projects, the production build could simply be to include *.cs and exclude *Test.cs files, whereas the test build could default to include all files.
Using a feature-oriented project structure appears as appropriate if not more so than the traditional .NET project structure. I’ll be continuing to experiment with it as my new default going forward. It’s simpler for many scenarios, and I expect it will scale as projects grow since features can easily be refactored into sub-features that use the same recursive directory layout.