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.

Multiple Solutions

Sitecore ecosystem Chart
Separating your concerns into multiple solutions will buy you some tremendous benefits down the road.  I’m not saying that it’s easy, but to me, the benefits outweigh the complexity. For each rectangle in the diagram to the right, we should have a dedicated solution (maybe multiple in the case of Sitecore or your Accelerator). The important takeaway is that you should separate your Enterprise layer from your Tenant layer, and each Tenant should be separated and isolated accordingly. I like creating not only a Solution for each tenant and the ECL, but a repository for each as well. This allows you to drive continuous integration at each layer of your Sitecore ecosystem. Each tenant brings along its own MVC Area rich with CSS, JavaScript, Layout definitions, and more (which helps to avoid file system conflicts and grants front-end flexibility), brings along its own configuration files (which helps avoid configuration conflicts), and brings along its own dlls (which allows you to customize your tenant specifically).

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.

Configuration Strategy

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.

Tenant configuration structure screenshot

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.

Website source code screenshot

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>

 

Keep in mind that the hostName and targetHostName attributes should align with your particular URL strategy. The important takeaway is that we can override settings found within the BrandA.Sites.config file with elements specific to the current runtime environment. We can also introduce any new settings that our brand may need. It’s important to note, however, that a local brand site should never change a Sitecore configuration element that would affect other brand sites. The key to multi-tenancy is ensuring that each tenant plays nicely with one another, and thus the tenants should not modify the underlying CMS.

Tenant Assets

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.

CMS site structure screenshot

This is an incredibly powerful capability of ASP.NET MVC in this context. It allows us to separate each brand into its own isolated collection of CSS, JavaScript, Thumbnail Images, Layout definitions, and more. In doing this, you allow for multiple teams to work on multiple brands in parallel, as each team can be an autonomous unit.  Each brand can select its own front-end frameworks accordingly, and they all install alongside each other cleanly.

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:

Solution Enterprise Screenshot

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:

Enterprise Configuration Master / Multi-project Properties screenshot

 

Enterprise Configuration Master - Update Package Screenshot

With 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!

Dylan McCurry

I am a certified Sitecore developer with a passion for the web. I hopped into the .NET space 5 years ago to work on enterprise-class applications and never looked back. I love building things—everything from from Legos to software that solves real problems. I have a strong foundation of backend skills, with sweet spots like security, portal solutions and APIs. Early on, before I had the benefit of SCORE, I made a lot of mistakes with Sitecore but learned a lot in the course of the struggle. I would like to support other developers by contributing my perspective on doing things “the Sitecore way,” rather than fighting the framework. Did I mention I love video games?

More posts from Dylan McCurry >

2 comments on Enterprise Accelerator: Helix Solution Structure

EdwardNovember 15, 2018 - Reply

An interesting article to read. What was the level of difficulty in created the helix solution and challenges?

Dylan McCurryNovember 16, 2018 - Reply

If I recall, we ran into a few issues that needed solving. Firstly, Helix is a complicated solution structure. There are a lot of projects that need to be set up, and this is really difficult to do by hand. We ended up using a ton of automation to help create new modules- we landed on the Powershell style automation, but others have used tools such as Yeoman. Another issue we faced was around TDS projects- we couldn’t find a clean way to automate the creation of these, so that led to a bit of pain. If you are using Unicorn, you may have a different experience. We ended up creating a Visual Studio template for our TDS project creation. Also, managing the list of modules within our Nuget package configuration was difficult- it was a checklist item for our pull requests.

Another issue that I see a lot with Helix is the over-use of modules. Some very simple features sometimes end up with their own foundation/feature layer for just a few classes. I like to consolidate some of these things into common modules to make it a bit easier in the long run. For instance, if you are going to customize your LinkManager, I don’t think it makes sense to bake that into a dedicated Foundation module.

Other than that, compilation and Visual Studio start up times were longer than I would like.

Add a Comment

Your email address will not be published. Required fields are marked *

Or request call back