Enterprise Accelerator: Helix Solution Structure
When building an Enterprise Component Layer (ECL) it’s important to think about how you want your overall solution to be organized. Before we start, I highly recommend you familiarize yourself with a few topics:
- Sitecore Helix – Patterns, Principles and Conventions: Helix is an architectural pattern outlined by Sitecore to help guide developers into organizing their solutions in a consistent and flexible manner. For our purposes, you can think of the Enterprise Layer as our Foundation and Feature modules for our business logic. Each tenant website that we introduce to the CMS should be thought of as living within the Project layer.
- Setting Up a Sitecore Solution – Part 1 Visual Studio and Projects: This blog post outlines how Brainjocks tackles a tenant solution. There are a few critical yet simple concepts to wrap your head around. Firstly, we separate TDS into multiple projects: Core, Master, and Master.Content. The Core project houses any updates to the Core database that we may introduce, while the Master project houses our Templates, Layouts, Placeholder settings, Rules, and any other elements that we want to ensure are deployed with each and every deployment. The Master.Content project contains elements which we do not want to deploy to managed environments. The Master.Content project simply exists for the developer’s convenience when setting up their local environment. Secondly, we introduce an entire project to house environment configuration. We do not use config transforms, and instead opt for file copy operations (which we’ll automate) to ensure that each environment receives the configuration it needs. Finally, we introduce three class libraries: Web, Custom, and Data. I personally like to consolidate this to only two: Web (for the MVC layer) and Custom (for any business logic or Sitecore extensions), but this is totally up to your own development philosophy. One important note here is that Brainjocks SCORE enables MVC Area support (although newer editions of Sitecore support MVC Areas), and we separate each Tenant into its own MVC Area at the web layer. This ensures separation of concerns, and allows for clean and modular tenant installation.
- Setting Up a Sitecore Solution – Part 2 TDS and Build Configurations: The second part of this blog post outlines how we utilize TDS specifically, and where we install Sitecore. As you’ll see in the video listed below, for local developers each tenant website receives its own Sitecore installation. We also create build profiles and organize the environments project to align with the environments we’ll be deploying to (Integration, QA, Production, etc). The only thing different in 2018 is that Sitecore references can now be pulled from Nuget rather than a local .dll copy.
- Brainjocks SCORE Scaffolding Overview: This video gives you some context on the idea of scaffolding. We need the ability to create a new tenant consistently and quickly. Later on I’ll show you how to get your Enterprise Layer to install in a similar fashion to SCORE for local development.
Problems to Solve
Taking this approach definitely creates some interesting problems. We’ve just introduced the concept of N+1 solutions, where N is the number of brand sites we wish to build. That’s a lot to set up. We’re also deploying to a single website in IIS. Configuration organization becomes a major problem, and this should be managed with an end-state vision in mind. You cannot look at a single solution and infer how you want the application to work – instead, you should think about how you’re going to organize the file system on a multi-site Sitecore application and back it up into your solution layers accordingly.
For example, in this scenario I would argue that the Sitecore CMS owns the web.config for the application. Tenants should not be modifying the web.config without good reason, and the Enterprise Layer likely shouldn’t modify it either. In reality, you as the architect of your CMS own the web.config. We’ll be modifying it for a few things, such as enabling Sitecore.Ship or a robots.txt handler, but in general you should not touch the web.config and you should be very hesitant with any modifications to it. In fact, I don’t even check it into source control.
Additionally, you should never edit a native Sitecore configuration file. Instead, you want to patch in your own modifications to the CMS. The way we do that is introducing .config files to the <sitecore installation directory>/Website/App_Config/Include folder. You’ll notice that this is where Sitecore keeps a large majority of its configuration. You can see the fully calculated configuration by navigating to http://<sitecore>/sitecore/admin/showconfig.aspx. To figure out how the configuration should look, Sitecore is performing a breadth-first search of the App_Config/Include directory. This means that you want your solutions to introduce folders that house the various configuration elements within the App_Config/Include directory that are alphabetically last.
In addition to introducing directories that are alphabetically last, you need to think of how your Tenant and ECL solutions organize their configuration. My general recommendation is to do the following:
- Prepend all config folders with zzz so that they fall alphabetically last.
- Ensure through alphabetical ordering that your Enterprise patches come before your Tenant patches.
- Allow room for Sitecore Support patches, which your ECL solution should house.
- Allow your brands (e.g., Project layers) to overwrite ECL settings. Organize ECL settings so that your Helix Foundation configuration is loaded before your Helix Feature configuration which is loaded before your Tenant Project configuration.
So why shouldn’t I edit Sitecore config files or my web.config?
Upgradability. You lose the ability to easily upgrade and support multiple versions of Sitecore. If you vow to never touch the out-of-the-box CMS configuration files, then the only configuration you need to triage during an upgrade are the ones that you bring to the table. If you start touching Sitecore’s configuration files, how do you keep track of what you’re changing compared to the default settings of the CMS? During an upgrade, understanding what configuration changes are being introduced by your application is critical. Upgrades are already hard enough, do yourself a favor and save yourself some headache up-front.
Configuration across Environments
When drilling into one of the tenant configuration directories, you should see only elements revolving around that tenant.
I personally like to prepend all configuration files with the name of the tenant (or layer from ECL), as this is output via the /sitecore/admin/showconfig.aspx administration page.
This patch:source element can help you identify where a particular configuration element may be coming from, which is absolutely critical in a large scale multisite setup.
Additionally, the Brand.Sites.Environment.config file patches alphabetically after the Brand.Sites.config file. This is by design. Brand.Sites.config should contain a site definition for your tenant that should live across all environments. For the environmental specific elements, you want to modify those within your Brand.Sites.Environment.config file.
For example, your Brand.Sites.config may look like this:
<?xml version="1.0" encoding="utf-8"?> <configuration xmlns:patch="http://www.sitecore.net/xmlconfig/" xmlns:set="http://www.sitecore.net/xmlconfig/set/"> <sitecore> <events> <event name="publish:end"> <handler type="Sitecore.Publishing.HtmlCacheClearer, Sitecore.Kernel" method="ClearCache"> <sites hint="list"> <site hint="BrandA">BrandA</site> </sites> </handler> </event> <event name="publish:end:remote"> <handler type="Sitecore.Publishing.HtmlCacheClearer, Sitecore.Kernel" method="ClearCache"> <sites hint="list"> <site hint="BrandA">BrandA</site> </sites> </handler> </event> </events> <sites> <site patch:before="site[@name='website']" name="BrandA" virtualFolder="/" physicalFolder="/" requireLogin="false" rootPath="/sitecore/content/BrandA" startItem="/home" database="web" domain="extranet" allowDebug="false" cacheHtml="true" htmlCacheSize="10MB" registryCacheSize="0" viewStateCacheSize="0" xslCacheSize="5MB" filteredItemsCacheSize="2MB" language="en" enablePreview="true" enableWebEdit="true" enableDebugger="false" disableClientData="false" searchIndex="branda-web-index" itemNotFound="/sitecore/content/BrandA/home/404" scheme="https" xmlSitemapFilename="_assets/branda-sitemap.xml" robotsTxtItem="/Settings/robotstxt File" /> </sites> <settings> <setting name="Enterprise.Project.BrandA.SomeSetting" set:value="some-default-value"/> </settings> </sitecore> </configuration>
And your Brand.Sites.Environment.config may look like this:
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/"> <sitecore> <sites> <site name="BrandA"> <patch:attribute name="hostName">www.my-local-brand-a.com</patch:attribute> <patch:attribute name="targetHostName">www.my-local-brand-a.com</patch:attribute> <patch:attribute name="cacheHtml">true</patch:attribute> </site> </sites> <settings> <setting name="Enterprise.Project.BrandA.SomeSetting" set:value="some-environment-specific-value"/> </settings> </sitecore> </configuration>
One of the best ways to handle local tenant assets is through enabling MVC Area support for Sitecore. Brainjocks SCORE helped us enable it historically, but in recent versions of Sitecore you can enable it through CMS configuration directly.
The Enterprise Solution
When it comes to effective organization of the Enterprise solution, I recommend you follow Helix principles. I also recommend you study Brian’s and SCORE’s default solution structure to get an idea of how to effectively handle environment configurations and TDS setup. For the enterprise layer, I like to follow a path similar to this:
Here, we’ve done a few things. We’ve separated the solution into multiple logical directories. We have root level folders for Configuration, Foundation, Feature, and Project layers. The root level .tds folder contains a global TDS settings file which all TDS projects reference. Foundation and Feature are for the logical Helix layers that they represent. Foundational level projects shouldn’t have any components or other MVC elements, while the Feature projects represent logical components and extensions to the web layer that you need to build. Each Feature level project has an MVC Area called
Enterprise, and all projects compile down to the same Area for production. Each Feature also has the option of bringing in configuration files, but anything environment specific is reserved for the Enterprise.Environments Configuration project. The Project layer contains our Scaffolding which we’ll get into in a later post. Finally, the Configuration layer contains any Sitecore patches that we need, an Enterprise.Environment project for environmental configs following Brian’s solution setup above, and an Enterprise.Package project which is used to generate a Nuget package of this Enterprise Component Layer. Distribution of this Enterprise Layer via Nuget is critical for scaffolding. As you saw in Brian’s video above regarding a SCORE installation, we’re going to set this solution up to distribute and install itself in the same way for local developers working at the Tenant layer.
Notice that each Foundation and Feature project also has a TDS counterpart. For any logical items which may be required for this module, we can include these within the TDS project. We also call out the
Source Web Project(s) setting in TDS to include the output of the project in compilation. For instance, on the
Enterprise.Feature.Events.Master TDS project, we call out the
Enterprise.Feature.Events C# project as the Source Web Project:
Then on the
Enterprise.Configuration.Master (not to be confused with
Enterprise.Foundation.Configuration.Master*) TDS project, we call out
Package Bundling and
Update Package file generation like so:
Generate Package During Build enabled, we are instructing TDS to create a Sitecore
.update package that represents the logical contents of the TDS Project along with the compilation output of its Source Web Project. With
Package Bundling enabled, a compilation will also include the contents of the bundled TDS projects as well as the output of each bundled TDS project’s Source Web Project output. This means that a compilation of the entire solution, from a DevOps point of view, produces a single**
.update file artifact which can be used in automated deployments.
* The Enterprise.Configuration collection of projects is intended to house configuration elements for the various layers. It also features a packaging mechanism for Nuget. The Enterprise.Foundation.Configuration project is a foundation module which other modules utilize to extract configuration settings from configuration files or Sitecore items accordingly.
** In reality, we generate a single .update package for the Core database, a single .update package for the Master database along with Content Management specific file-system assets, and a single .update package for Content Delivery specific file-system assets.
A few additional resources
As you can also see, each of our projects outputs a Unit Test project. We want as much unit testing as is reasonably possible on our Enterprise Layer. If you’re struggling with Unit Testing in Sitecore, I highly recommend you check out Pavel Veller’s introduction to FakeDB.
Also, it can be quite difficult to create Helix projects that line up correctly. There are lots of interconnected settings that can be easily missed. To remedy this, I recommend you check out some automation tips from Marc Duiker and the rest of the community. Automation is the key. You can do this with something like Yeoman, with Powershell scripts, with Project templates, etc – get creative, and use what works!