Saturday, 8 December 2018

How Many Projects in a .NET Solution?

The number of projects I use in a .NET solution has changed a lot over recent months.

I used to create separate projects for just about every concern in my application. For example, a typical 3-tier application might have the following projects:
  • MVC or Web API
  • Data (e.g. Entity Framework)
  • Data.Contracts (repository interfaces)
  • Business (core business logic classes)
  • Models (Entity Framework model classes)
  • Dtos (cross-cutting DTO classes)
  • Common (common logic and data such as shared constants and extension methods)
  • Common.Logging (usually some kind of abstraction over a logging library such as NLog)
  • Test projects for each 'implementation' project above

I'm now of the opinion that this is massive overkill. It may appear give separation of concerns, but I think this is an illusion. The projects can all reference each other (with the obvious exception around circular references) so all you get is additional complexity and longer build times.

My last few applications (all APIs) have had the following projects:
  • Api (very thin project containing the Web API controllers and not much else)
  • Core (the bulk of the code is in here; it's everything - domain logic, etc. - that marks my application out as unique)
  • Infrastructure (any communication with things outside my application - e.g. file system, database - is in here; generally contains implementations of interfaces defined in the Core project)
  • Single Tests project

Why fewer projects? Faster build times for one. And simpler structure (I certainly find it easier to find things now). Areas of the application can still be logical separated with namespaces. You don't need physical separation you get by building them as different dlls.

The choice of four projects is not arbitrary. It's based on ideas from the Ports & Adapters pattern. Very briefly, Ports live on the boundary of your domain and handle inputs and outputs to/from the domain. Adapters are actors outside of the domain that interact with it in some way. Primary Adapters make requests to the domain; Secondary Adapters receive requests from the domain. For example, a Web API controller would be a Primary Adapter, whereas a database access layer of some kind would be a Secondary Adapter.

So rephrasing the project explanations in terms of Ports & Adapters:
  • Api (Primary Adapter for Web API technology)
  • Core (Ports and core domain)
  • Infrastructure (Secondary Adapters)
  • Tests (Primary Adapter for tests)

As you can see, there is essentially a project per Primary Adapter. This is because each Primary Adapter is generally a deployable or executable program based on a specific technology or framework (e.g. Web API or WPF).

The same approach could be taken with Secondary Adapters (single project per Adapter), however I find this to quickly get out of hand as you will generally have more Secondary Adapters compared to Primary Adapters, and integration with them is usually achieved by just using a simple class library. So my Infrastructure project splits the Adapters out into folders instead.

The only other thing to mention is the organisation of the Core project. The bulk of the code is in here so it's important to have a structure that makes sense. I generally start out with a set of standard top-level folders (Common, Models, Dtos) and then add additional folders/sub-folders as required. I also have a Contracts folder containing interfaces to be implemented by one or more Adapter.