The bigger a Visual Studio solution gets the more important is a good structure. When either constructing a monumental application, a web of small connected applications (e.g. micro services), a single application with lots of different entry points (e.g. a Xamarin, ASP and WPF version of the same application) or a set of applications and tools that use the same logic components, you will get to the point where you need to define discrete components in your code.
The Big Picture
Components
Just like a file tree in your operating system, your solution is built as a tree. There are solution folders that are a purely virtual construct to structure your solution. They do not have to be part of the file system structure, but are held as a node in the .sln
-file. They can be nested at will.
The second important structure element are your solution projects. Generally each project will compile to one assembly when you build your solution. This makes projects a tool to structure your deployed code. Also it is a scoping tool, als you can define classes and their members as internal. This makes those classes or members visible like public members or types, but only within that assembly. This is a very useful tool if you want a decoupled application and expose only interfaces and factories or builders and want to hide the concrete implementations from the rest of the world.
Folders placed inside of projects in the file system act in the same way within the solution, as child nodes in your solution tree.
Notation
Paths on the tree of a file system get constructed using delimiter notation. A windows file system path can be written as C:\users\foo\someDirectory
with the delimiter \
. Unix systems write them as /home/foo/someDirectory
with the /
-delimiter.
The same concept applies to the path on the tree that builds your solution. There the path is called a “namespace” and the nodes are separated by a period (.
).
Example:
In the a solution named “CoreSkills” there is a solution folder “Examples”. The Examples folder contains a project named “StructuredData”. The StructuredData project contains a file system folder called “Models” and within that folder there is a class called “JSON”.
The fully qualified name (meaning the full namespace followed by the actual class name) would be CoreSkills.Examples.StructuredData.Models.JSON
.
Reasons for Namespaces
Why should we use namespaces instead of just naming the class and use that name?
The most obvious reasons are preventing conflicts and providing context.
Picking up the example from above JSON
might be a very suitable name for the class, but without using namespaces there is a big risk of someone else naming their class JSON
as well, as JSON is a popular format for storing data or moving it between systems. Also the use of the class is only vaguely told by its name, but when you look at the full qualified name with the namespace included you see that it is used for depicting structured data, not for transport or persistence.
When coding close to the origin you can call it by its short name JSON
, but the further away from its origin namespace you use it, the more of the name space would be appropriate to use. If you are for example in the class CoreSkills.Examples.Persitence.Serializer
you could call the type StructuredData.Models.JSON
to distinguish it from a type called CoreSkills.Examples.Persitence.JSON
, referred to as JSON
in that context.
Or you could call both JSON classes by their fully qualified class name for maximum clarity.
So using namespaces gives you the ability to choose how much verbosity you want to have in your code as a trade off against readability, while granting context.
Reasons for a nested solution structure
There are three main reasons for nesting your solution structure.
- Readability
- Security
- Maintainability
To be honest maintainability emerges from the other two, but it is important enough as a reason to be mentioned on its own.
Readability
When you use nested solution folders, solution projects and project-internal file system folders you get multiple readability benefits.
The most obvious one is the solution tree folding in your IDE. Most IDEs like Visual Studio or anything built by JetBrains allow to collapse or expand your solution tree as you please. This way you only have to look at what currently is relevant to your work.
When your solution starts to grow you do not want so see 50 or more projects next to each other. If you set aside an individual namespace for all your tests that also allows you to put them aside when you are only working on your business logic and not your tests.
Also it is a good visual indicator to have the namespaces written as assembly file names, so you can tell at the first glance, what CoreSkills.Examples.Persitence.dll
does.
The same applies when you decide to use a layer pattern. Then the structure dictates that your namespace contains phrases like Foundation
, Feature
and Project
or phrases like Persistence
and UI
.
Security
As briefly touched above solution structuring can be used as a tool to slim out your public api. There is less risk of exploits or you breaking other people’s code if you only ever expose interfaces and factories/builders. If you declare everything internal that only really matters internally no one can rely on it.
But keep in mind, that you still need to be able to unit test your external API. Depending on your testing philosophy you might have to weigh your priorities here.
Maintainability
Last but not least a good solution structure enables a maintainable code base. If you cannot break other people’s code as easily you also are less likely to break your own.
It’s easier to introduce new, different logic and test your code if you heavily rely on interfaces.
If you have distinct namespaces for distinct parts of your application you will find what you need faster, are less likely to produce duplicate code inadvertently and are less likely to put new code in bad places.
Also it is much easier to extract or replace components if you draw clear lines of concerns and structure in your code base. You can pull out parts of your code to build a NuGet package from it or deprecate full projects.
For quick and dirty tooling you could even pick up a single assembly, drop it into a different solution and use it there without having the plain text code in there.
The final lesson
In the end finding the right structure for your solution and your name spaces is up to you, but you should definitely think at least twice about your structure when you start a new solution or significantly grow an existing one.
Your future self will thank you for the time you spend on it tenfold!