On my current project I wanted to do a little pit stop after our 1st sprint to ensure that the solution was running as optimally as possible. We’ve fleshed out the solution structure and have pulled together all the core pieces of technology – so within Visual Studio, the solution looks like:
Solution ‘MyApp’
+---Data
| +---Visual Studio 2008 Database GDR Project files (do not build in debug mode)
+---Deployment
| +---WiX Installer Projects for deploying “Presentation” projects (do not build in debug mode)
+---Domain
| +---MyApp.Domain Project (contains core contracts + domain entities + specifications)
+---Framework
| +---MyApp.Core.* (any projects with cross cutting concerns, caching, logging, utils, or reusable libraries etc)
+---Infrastructure
| +---MyApp.Infrastructure (external infrastructure - databases, search engines, email gateways etc)
+---Presentation
| +--- MyApp.Web, MyApp.Web.Controllers, MyApp.WCFEndpoints
\---Services
+--- MyApp.Services, MyApp.Management.Services
The solution structure is based around trying to solidify some of the notions in Domain Driven Design and some of the ideas mentioned in Jeffery Palermo’s Onion Architecture (which owes a lot to Alistair Cockburn’s notion of a Hexagonal Architecture). We’ve created solution folders called “Data”, “Deployment”, “Domain”, “Framework”, “Infrastructure”, “Presentation” and “Services” so that when the team come to work on a new sprint backlog item and start to decompose the feature that they are going to be creating it becomes pretty obvious where the code should go if you ask yourself the question “is this a domain entity? Is this code generic and could be reused throughout the solution? Is this code going to be integrating with external / 3rd party systems?”.
I raised this solution structure on the Sharp Architecture Forum, Kyle Baley responded that he had moved to a single project solution containing all the the different architectural layers – as this offered the best build performance. Patrick Smacchia often posts advice on partitioning code through .NET Assemblies and Jeremy Miller has also blogged that Separate Assemblies != Loose Coupling but there is a big difference in running a project as a solo dev and running a project with a team, amongst multiple teams, especially when you are trying to reuse IP and cross fertilise ideas and practices. It’s also often forgotten that Visual Studio Solutions and Projects are development constructs not deployment constructs. With tools like PostSharp and ILMerge more behaviours can be added post build and multiple assemblies (and their dependencies) can be packaged into a single deployable unit.
The Problem
Doing a build after running “Clean Solution” takes 18 seconds and doing a “Rebuild Solution” takes 15 seconds. Not exactly huge, but it definitely feels sluggish for the number of projects in the solution. (If you want to measure build times within Visual Studio – you need to change the Build Output verbosity via Tools > Options > Projects and Solutions > Build and Run > “MSBuild project build output verbosity” and change it to normal).
Admittedly all the projects under the “Framework” folder should be included as assembly references, but we’re early on in the project and we’re constantly making changes – once the code churn level reduces, we can factor those out. We’re using a lot of 3rd party frameworks which means each project has lots of assembly references which by default are set to be CopyLocal = true. Hopefully you are aware that CopyLocal = true is EVIL.
A Solution
To speed up the build – set CopyLocal = false on all 3rd Party Assemblies, but ensure that Project References are still set to CopyLocal = true (otherwise you’ll stop seeing your changes!). There is one exception; you need to ensure that CopyLocal = true on your “Presentation” projects i.e. your web application – otherwise when you load the site you will see ReflectionTypeLoadException errors.
There’s one further problem in our solution – we’ve adopted embedding our BDD specs within our projects and we’re using TDD.NET / ReSharper Gallio Unit Test Runner inside VS to execute them. This means that the majority of our projects actually require local copies of the referenced assemblies in order to execute the test, but we don’t want to pay the performance tax of Visual Studio copying those assemblies every time we do a build. So I created a MSBuild target that we can run once to configure the solution (if you don’t want to run tests inside your projects skip to The Result):
<Target Name="ImportReferencedAssemblies">
<!--Process the given VS SLN file and return all CS Projects – this task is in HvR.MSBuild.Tasks.dll-->
<GetCSProjectsForSolution Solution="@(SolutionToBuild)">
<Output TaskParameter="Output" ItemName="SolutionProjects" />
</GetCSProjectsForSolution>
<PropertyGroup>
<ConfigFolder>$(BuildPath)\..\ReferencedAssemblies\</ConfigFolder>
</PropertyGroup>
<!--Delete any pre-existing ReferencedAssemblies—>
<RemoveDir Directories="%(SolutionProjects.Location)\bin\$(Configuration)\ReferencedAssemblies\" />
<!--Get all Files within the master ReferencedAssemblies folder and prepare them to be copies to each project bin\(Debug | Release) folder—>
<CreateItem Include="$(ConfigFolder)\**\*.dll"
AdditionalMetadata="ToDir=%(SolutionProjects.Location)\bin\$(Configuration)\ReferencedAssemblies\">
<Output TaskParameter="Include"
ItemName="ConfigFilesToDeploy" />
</CreateItem>
<!--Now copy all of the files to the appropriate folders-->
<Copy SourceFiles="@(ConfigFilesToDeploy)"
DestinationFolder="%(ToDir)\\" />
</Target>
Essentially this script processes the Visual Studio .SLN file, returns all the CS Project file locations, then copies the contents of the ReferencedAssemblies folder to the bin\Debug\ReferencedAssemblies folder of each project.
Next you have to create a App.Config file that you can share across all your projects, add this as a “Linked File” in any projects where you want to run unit tests. The App.Config file should contain the following configuration section:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<probing privatePath="Debug;ReferencedAssemblies" />
</assemblyBinding>
</runtime>
</configuration>
This tells the .NET Framework Fusion Assembly Loading Engine at runtime to look in the Debug and Debug\ReferencedAssemblies folders for any assemblies referenced by the project. Hey presto, unit tests will now execute successfully without throwing an ReflectionTypeLoadException
The Result
After making those small changes; doing build after running “Clean Solution” takes 9 seconds (saves 9 seconds) and doing a “Rebuild Solution” takes 5 seconds (saves 10 seconds) – which means we can now do a build and run an ad hoc unit test in the time it used to take to just do a build.
If you would like the custom MSBuild Task so you can try this on your own solution, you can download it from my CodePlex project.