A question was asked about “How to organise your CM vs CD config in a Helix Solution”. This was about configs in your specific implementation, not the default Sitecore configs that need to be enabled or disabled for a specific Sitecore environment.
For example, you may have some specific custom pipelines or event handlers that should only run on the CM instance.
Some suggested that these custom configs should be enabled/disabled/modified as part of the deployment process using PowerShell, possibly using an XML file which provides a mapping of files for the environments. This could then be maintained by the developers.
I suggested a different approach, and “tagging” your config nodes to make them easier to patch… The approach was new/unknown to some so I thought I would (re)blog it.
Managing Helix Configuration
The Helix documentation states the following:
The key to long-lasting good configuration management is isolating not only the implementation specific changes and additions from the standard Sitecore and .NET configuration, but also keeping any changes and additions to configuration together with the business logic which needs it. The first makes it possible to easily identify changes – making upgrades much easier – and the latter makes it easy to identify the reason for changes – making issue resolution and implementation changes stress-free.
I agree with the above statement. Having to leave the Visual Studio solution, hunt down a deployment script (which may or may not be in Source Control) and then modify it for all builds, even current ones that possible may not have a feature merged back in yet… well it starts to get a bit complicated and disconnected for my liking.
Include Folder Structure
The solution I am currently working on is not based on Helix principles. Nonetheless, the process would work the same given that the folder structure of a Helix solution follows set of conventions and strict structure.
We define our config files in a
z.Project folder to ensure they are patched last. We also define a “Role” folder below this, in our instance we only have CM and CD servers (no processing, reporting, publishing etc):
Within your Foundation/Feature/Projects, add a subfolder to your module config which defines overrides to patch for each specific environment. Since everything is just part of your solution, everything is kept nicely and tightly together with your code. Remember, these are solution wide scoped and controlled by the developers.
A Helix based project would be organised like so:
Patch Override Syntax
Normally the patch syntax is a little verbose and places dependency on specific
types in multiple config files. The following syntax is a rehash from my previous post about site specific pipelines, which will help with this issue.
The premise is to “tag” your config nodes with something unique and then use that attribute to patch override for your environment specific config.
For example, given the following config:
<pipelines> <initialize> <processor type="MyProject.CMS.Pipelines.CustomRouting, MyProject.CMS" patch:before="*[@type='Sitecore.Mvc.Pipelines.Loader.InitializeRoutes, Sitecore.Mvc']" desc="MyProject.CustomRouting" /> </initialize> <httpRequestBegin> <processor type="MyProject.CMS.Pipelines.CustomItemResolver, MyProject.CMS" patch:after="*[@type='Sitecore.Pipelines.HttpRequest.ItemResolver, Sitecore.Kernel']" name="MyProject.CustomItemResolver" /> </httpRequestBegin> </pipelines> <events> <event name="item:saved"> <handler type="MyProject.CMS.Events.CustomItemSavedHandler, MyProject.CMS" method="OnItemSaved" hint="MyProject.CustomItemSavedHandler"> </handler> </event>
We can patch this from the config in our environment override much more simply without having to specify the full type element.
<pipelines> <initialize> <processor desc="MyProject.CustomRouting"> <patch:delete /> </processor> </initialize> <httpRequestBegin> <processor name="MyProject.CustomItemResolver"> <patch:delete /> </processor> </httpRequestBegin> </pipelines> <events> <event name="item:saved"> <handler hint="MyProject.CustomItemSavedHandler"> <patch:delete /> </handler> </event> </events>
If the definition of any of the code ever changes, you only need to edit one config file, not the environment specific configs as well.
Note that our patch delete syntax only uses the additional attribute we specified. There is also a mix of attribute usage here –
hint. It really doesn’t matter what you use (but beware that in certain elements like list parameters
hint has meaning for example).
Environment Specific Deploy Step
We use Octopus Deploy but the process should be the same for any continuous deployment tool.
Add a deployment step, and if the current server is CM then delete the
WebCD folder. That’s it, the logic is that simple.
Helix? Well remember those conventions and folder structure I mentioned earlier, this is where it makes our life simple again. Habitat uses these same folder conventions in the gulp scripts to publish on build for example. So in a similar manner a script such as this should suffice:
cd C:\inetpub\wwwroot\habitat.dev.local\Website\App_Config\Include Remove-Item *\ContentDelivery -Recurse -Force -ErrorAction Ignore
The above PowerShell will delete all folders called
ContentDelivery located within subfolders of the
Include folder, e.g.
If you have multiple server roles then tweak the script to delete all sub-folders that do not match the current environment.
Other Attribute Use Cases
The use of additional attributes to manage configs is useful in other scenarios as well.
For example, if you want to patch the list of sites in the HtmlCacheClear handler:
<event name="publish:end"> <handler type="Sitecore.Publishing.HtmlCacheClearer, Sitecore.Kernel"> <sites> <site name="habitat">habitat-demo</site> </sites> </handler> </event>
The use of the additional attribute will mean the
site definition is now unique and appended rather than overwriting existing/default definitions.
As mentioned, this article is a rehash from my previous post about site specific pipelines. The same still applies here for site-specific implementations. For example, if you have pipelines in
Feature that needs to be opt-in, then specific
Projects can patch into those using the attribute tag. Since Foundation is not aware of Projects, it cannot add them in to start with, but Projects can reference and be aware of Foundation so this does not break Helix principles. Again, the same benefit applies: the processor is only defined once and if the implementation/type is changed then it only needs to be done in a single place.
As always, have a better solution, a different solution or think there is a flaw in this then get in touch – I would be happy to see how others have approached this same issue.