Category Archives: Helix

Sitecore SolR Sorting Challenge

As I promised in my last post (please read it first) here is a solution to address the SolR sorting issues.

The Problem

The issue is that different pages, usually have different date fields to represent how they should be sorted and if we want to adhere to the Helix principles, the Solr feature must NOT KNOW ABOUT PAGE TYPES.

For example, a news page will have a news date, calendar event might use the start date and an some page will not have a date field and therefore will have to use created and or updated.

Typically, I see solutions that deal with this issue at retrieval time i.e. index all the different fields and then have a specific “order by” clause for each page type.

The biggest disadvantages of this approach is that you cannot sort a list with different page types i.e. get the 10 latest items that are either news, event or articles.
In addition, you have to manage all the different order by clauses. Which will destroy the Indexing/SolR abstraction as you will have to expose the IQueryable<T> in order to apply the order by clause.

Solution

I prefer to deal with the sorting issue at indexing time and have a single dedicated SolR field which is used to sort all item types. This allows you to sort news, articles, calendar events, etc. in the same way.

You still must deal with the issue that the SolR implementation should not know about which field to use for a give item type. To overcome this issue we use a configuration file that defines the mapping between an item of a specific type and which field to use for sorting.

Template to Field Mapping

The following configuration defines which field should be stored for sorting for each item template, if a field mapping is not defined, the item updated value is used.

In sitecore,  it is easy to map the configuration below to a C# class (i.e. SortFieldMappingRepository) for more information, about how to do this see my blog post on Structured, Type Safe Settings in Sitecore.

<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/" xmlns:environment="http://www.sitecore.net/xmlconfig/environment" xmlns:role="http://www.sitecore.net/xmlconfig/role/">
	<sitecore>
		<feature>
			<SolRIndexing>
				<SortFieldMappingRepository type="Feature.SolRIndexing.Infrastructure.ComputedFields.Sorting.SortFieldMappingRepository, Feature.SolRIndexing" singleInstance="true">
					<mappings hint="raw:Add">
						<!--News, NewsDate -->
						<sortFieldMapping templateId="{AE6B4DF2-DF36-4C6D-ABDA-742EE6B85DE9}" sortFieldIdId="{3D43D709-DFAE-4B4F-8CB2-DF80D9B83857}"/>
						<!-- Calander, StartDate-->
						<sortFieldMapping templateId="{A8DD1F59-08AB-4BF0-BE76-8873A8F00628}" sortFieldIdId="{6369AC75-036B-48D8-95E2-F16998F8E777}"/>
						<!-- Video, VideoDate -->
						<sortFieldMapping templateId="{3D9D8B7A-FCB2-459B-908B-1E31F0C975FB}" sortFieldIdId="{E9993C21-1EF0-4C30-83D4-5F69923CEC3E}"/>
						<!-- Article, ModifiedDate Field -->
						<sortFieldMapping templateId="{F6B599F4-11C4-4C65-B253-95F3C40EBA18}" sortFieldIdId="{DC6C0E49-1705-4F3E-80EF-83176E482DBC}"/>
					</mappings>
				</SortFieldMappingRepository>
			</SolRIndexing>
		</feature>
	</sitecore>
</configuration>

Define the SolR index Field

Then we define the SolR index field used for sorting and specify that the SortComputedIndexField class is responsible for adding the sort date to the index.

<sitecore>
	<contentSearch>
		<indexConfigurations>
			<defaultSolrIndexConfiguration>
				<documentOptions>
					<fields hint="raw:AddComputedIndexField">
							<!-- Sorting-->
							<field fieldName="_sort" returnType="datetime" >Feature.SolRIndexing.Infrastructure.ComputedFields.Sorting.SortComputedIndexField, Feature.SolRIndexing</field>

					</fields>
				</documentOptions>
			</defaultSolrIndexConfiguration>
		</indexConfigurations>
	</contentSearch>
</sitecore>

The SortComputedIndexField class is responsible for providing the value for the sort field and it calls the CalculateSortDateService to determine the sort value.

namespace Feature.SolRIndexing.Infrastructure.ComputedFields.Sorting
{
    public class SortComputedIndexField : AbstractComputedIndexField
    {
        private readonly CalculateSortDateService _calculateSortDateService;

        public SortComputedIndexField(CalculateSortDateService calculateSortDateService)
        {
            _calculateSortDateService = calculateSortDateService;
        }

        public SortComputedIndexField()
        {
            _calculateSortDateService = ServiceLocator.ServiceProvider.GetRequiredService<CalculateSortDateService>();
        }

        public override object ComputeFieldValue(IIndexable indexable)
        {
            Item item = indexable as SitecoreIndexableItem;
            if (item == null)
                return null;

            if (!item.Paths.FullPath.StartsWith(Constants.SitecoreContentRoot))
                return null;
            return _calculateSortDateService.CalculateSortDate(item);
        }
    }
}

The CalculateSortDateService class iterates over the field mappings, defined in the configuration and uses the field value for the date if the field is found, otherwise the updated value for the item is used.

namespace Feature.SolRIndexing.Infrastructure.ComputedFields.Sorting
{
    public class CalculateSortDateService
    {
        private readonly SortFieldMappingRepository _sortFieldMappingRepository;

        public CalculateSortDateService([NotNull]SortFieldMappingRepository sortFieldMappingRepository)
        {
            Assert.ArgumentNotNull(sortFieldMappingRepository, nameof(sortFieldMappingRepository));
            _sortFieldMappingRepository = sortFieldMappingRepository;
        }

 
        public DateTime CalculateSortDate([NotNull] Item item)
        {
            Assert.ArgumentNotNull(item, nameof(item));
            var mappings = _sortFieldMappingRepository.Get();
            if (mappings == null)
                return item.Statistics.Updated;

            foreach (var sortFieldMapping in mappings.Where(m => m != null))
            {
                if (item.TemplateID != sortFieldMapping.TemplateId)
                    continue;

                Field dateField = item.Fields[sortFieldMapping.SortFieldId];
                if (dateField == null || string.IsNullOrWhiteSpace(item[sortFieldMapping.SortFieldId]))
                    continue;

                return new DateField(dateField).DateTime;
            }
            return item.Statistics.Updated;
        }
    }
}

Sorting Extensions

The last part is to provide the ability to sort the result set and for this we introduce the SortDateSearchResultItem class and a few extensions methods to add sort ascending & descending.

namespace Feature.SolRIndexing.Infrastructure
{
    public class SortDateSearchResultItem : SearchResultItem
    {
        [IndexField("_sort")]
        [DataMember]
        public virtual DateTime SortDate { get; set; }
    }
}

namespace Feature.SolRIndexing.Infrastructure.ComputedFields.Sorting
{
    public static class SortingQueryableExtensions
    {
        public static IQueryable<T> SortDescending<T>(this IQueryable<T> query) where T : SortDateSearchResultItem
        {
            return query.OrderByDescending(item => item.SortDate);
        }
        public static IQueryable<T> SortAscending<T>(this IQueryable<T> query) where T : SortDateSearchResultItem
        {
            return query.OrderBy(item => item.SortDate);
        }
    }
}

I hope this post will help, Alan

SolR

Introduce a (SolR) Sitecore Search Abstraction

After my previous post on Supporting Integrations, I received a few comments asking why was SolR was in the integration’s module group, as it is part of the sitecore API.

In this blog i will explain why and in more detail how to isolate a SolR integration.

Yes Sitecore Search is part of the Sitecore API, but it relies on an 3rd party system! Please read my previous post about why you need to identify, separate and isolate modules with external dependencies, as Sitecore Search API faces exactly the same challenges.

With the bonus that there are 3 supported implementations (Lucene, SolR and Azure Search) which are almost the same, but not quite!

Sitecore Search Issues

In most of the helix-based solution I have seen indexing is implemented in the framework layer which provides some helper extensions. Then each feature uses indexing module with Sitecore Search API to implement their requirements. This typically leads to the following issues:

  • Duplicated code across features
  • No clear definition of the indexing/constraints/sorting requirements for the solution.
  • Non-consistent implementation across the solution i.e. Predicate builder vs LINQ.
  • Optimization is difficult.

With each feature implement their indexing requirements, it leads to duplicated code as it feature needs to build the query to add sitecore root item, base templates, language etc. for each request, before adding the feature specific part of the query.

Therefore when fixing a bug or performance issues you must track down all the places where Search is used and then determine if they require the same fix and or the optimization.

How to abstract away the SolR Search Implementation

  • Identify the indexing requirements
    • Introduce an abstraction in the foundation layer (Indexing).
  • Create the implementation (Solr Indexing) that implements the abstraction define by Indexing in the foundation layer.
    • Address the sorting issues (i.e. different items templates have different date fields)
  • Let the features use the indexing abstractions (i.e. Course, News, Calendar, etc.)

Identify the indexing requirements

There are 3 main components to define the indexing requirements constraints, pagination & sorting.

Constraints

Constraints define what the filters can be applied to reduce the number of items that are returned. In this example it will be possible to apply the following constraints:

  • Location in tree sitecore (i.e. site specific news folder, all content, etc.)
  • Language (i.e. return items with an English language version)
  • Template, i.e. does the item inherit from a specific template (i.e. news, calendar, etc.)
  • Taxonomy – return items based on their categorization (i.e. football, skiing, etc.)

Pagination

Defines the number of search result per page and which page you require.

Sorting

Is responsible for defining what is used to sort the result items and the direction (ascending or descending), for example using date to get the 10 latest news.

If you want to sort by date, one challenge is to determine how to sort he results, as different pages will have different fields. Some pages have no date apart from created/updated, news normally has a specific news date and calendar events have start/end dates.

The SolR implementation must NOT KNOW ABOUT PAGE TYPES, see my blog post with a solution.

The following code defines the indexing requirements.

public interface IConstraint
{
    Item RootItem { get; }
    Language Language { get; }
    ID BaseTemplate { get; }
    IEnumerable<Category> Categories { get; }
}
public interface IPagination
{
    int Number { get; }
    int Size { get; }
}
public enum SortDirection
{
    Ascending,
    Descending
}

Then we need to define the result of making a search and a repository to make the search

public interface IPagedSearchResult
{
    IEnumerable<Item> Results { get; }
    IPagination Pagination { get; }
    int TotalHits { get; }
    bool HasMoreResults { get; }
}
public interface IPagedSearchResultRepository
{
    IPagedSearchResult Get([NotNull] IConstraint searchConstraint, [NotNull] IPagination pagination, SortDirection sortDirection);
}

The definition of the search result could of been type safe, i.e. return a model of type T instead of the Item, but I wanted to keep the example simple and not use a specific binding framework.

Anyway I hope this post will help, Alan

Sitecore Helix – Supporting Integrations

This blog will outline how it is possible to identify, separate and isolate dependencies on external systems, by introducing an Integrations module group. See here for more information about module groups in Helix.

But why do we need a Integrations module group?

Any feature that requires integration to achieve its purpose, will introduce additional challenges relating to stability and additional system knowledge, than a standard feature layer module.

Stability

Helix is built on a number of principles that help deal with stability, the Stable-dependencies principle and Stable-abstractions principle for more details see my blog post.

Features that rely on 3rd party systems are by nature more unstable than any other feature/foundation modules. As it is not usually possible to control when external systems change, upgrade or fail. Therefore, quality assurance, test, and automated deployment for the website cannot protect against this type of change or failure.

System knowledge

Working with a 3rd party system, such as CRM, ERP, Marketing Automation, DAM, SolR, Custom API, etc. requires additionally system specific knowledge. So it is a good idea to use abstractions to hide the system specific complexities for several reasons:

  1. The web team might not have the specific system knowledge.
  2. The web team should not be distracted by the additional complexities of all the integrations.
  3. The team responsible for implementing the integration may have limited Sitecore knowledge and should not be distracted by the rest of the web solution.
  4. It is good practice to separate modules by their responsibilities by splitting the presentation and the retrieval of data from the external system.

Integrations Module Group

The intention/purpose of the Integrations module group is to clearly define which modules have a dependency on an external system and ensure they are only responsible for that integration to the external system.

Example

This solution is responsible for selling a wide variety of courses. The customer has its own custom course catalog API and a complex legacy enrollment system.

The following diagram shows the module architecture for the solution.

In the foundation layer the following modules were introduced to provide abstractions. If you are not familiar with IoC and Abstractions see my earlier post.

  • Course Catalog
    • Defines the abstraction/system agnostic logical data module for the course catalog.
    • Acts as an abstraction between the website and Custom Catalog API.
    • It helps to focus on the ideal model that supports the business objectives.
  • Enrollment
    • Defines the abstractions to support the process of initiating attendance on a course at a specific school and shopping cart.
    • Acts as an abstraction between the website and the enrollment legacy system.
    • It helps to focus on the ideal model that supports the business objectives.

In the Integrations Group in Features layer the following modules where introduced

  • EXT Course API
    • Responsible for getting data provided by the EXT Course API.
    • Provide the implementation of the Course Catalog (foundation layer) abstractions.
    • Responsible for caching the course catalog, as the API only supports periodical batch retrieval.
  • EPOC Enrollment Management
    • Responsible for integration of the functionality provided by the EPOC Enrollment Management SDK.
    • Provide the implementation of the Enrollment (foundation layer) abstractions.

In the Features layer the following modules where introduced

  • Course
    • Responsible for the presentation of the course catalog, retrieved via the course catalog abstractions defined in the foundation layer.
  • Enrollment
    • Responsible for presentation and controlling the process of initiating attendance on a course and displaying the shopping cart, using the abstraction  defined in the foundation layer.

Additional Bonus

Once the integration code is isolated in a single module and only responsible for the integration, it is easier using dependency injection to achieve the following:

  1. Update external system, as the code to change is clearly defined and separated from the presentation and website logic.
  2. Provide the ability to support more than one version of an integration (i.e. different sites use different version)
  3. Move the integrations modules to an integrations platform, if it is the domain model for the customer business.

I hope this blog post gives you some ideas on how to isolate and remove the complexities introduced by integrations from your Sitecore solution, Alan

In my next post, I will explain why and in more detail how to isolate the SolR integration.

 

 

Sitecore Helix- Modules that need to reference another module in the same layer Part 2

One module which is dependent on 2 or more modules

This is the second part in a 3-part series on dependency, if you have not read part 1 please read it first.

When a single module is dependent on 2 or more other modules in the feature layer there are few ways to solve this issue:

  1. Introduce an abstraction and implemented the abstraction in the project layer.
  2. Introduce an Abstractions in the foundation layer.
  3. Move the module to the project layer.

The Metadata Challenge

A typical example of a module, which can be dependent on 2 or more modules, is Metadata. In addition to providing keywords, taxonomy, etc. metadata is generally responsible for generating the title element in the head section and typical requirements could be as follows:

  1. If it is a news article, use the news title.
  2. If it is a blog post, use the blog post.
  3. If it is a product page, get the product title from the database.
  4. Otherwise return the item display name.

Metadata is a good example where implicit/soft dependency typically creep in, i.e. the metadata module needs the title for a given item and it uses the Sitecore field from the news module, blog module, product module, etc.

Solution 1 – Introduce an abstraction and implemente the abstraction in the project layer.

The only information that the metadata module needs that it can not determine itself is the title for a given item. Therefore, it is necessary to introduce an interface that satisfies that requirement, for an item give me the title. It is in fact very simple, see below.

namespace Feature.MetaData
{
	internal interface ITitleRepository
	{
		string Get(Item item);
	}
}

The context in the project layer is well suited to implement the ITitleRepository interface, as the project layer is responsible for aggregating/mediating the functionality provided by the feature and foundation layers.

Therefore, the next step is to implement ITitleRepository in the project layer, by getting the relevant information from the features (i.e. news, blogs, products) and provide the default behavior of returning the Display name if all else fails (see the example implementation below).

namespace Feature.MetaData
{
namespace Project.Context
{
	class TitleRepository : ITitleRepository
	{
		public string Get(Item item)
		{
			if (item == null)
				return string.Empty;

			// we assume that each repsository returns null, if the item is ot of the correct type...
			string value;
			//is the item a news?
			var news = _newsRepository.Get(item);
			if (!string.IsNullOrEmpty(news?.Title))
				return news.Title;

			//is it a blog post?
			var blog = _blogRepository.Get(item);
			if (!string.IsNullOrEmpty(blog?.Title))
				return blog.Title;

			//is a blog post?
			var product = _productRepository.Get(item);
			if (!string.IsNullOrEmpty(product?.Name))
				return product.Name;

			//default return display name...
			return item.DisplayName;
		}
		readonly ProductRepository _productRepository = new ProductRepository();
		readonly NewsRepository _newsRepository = new NewsRepository();
		readonly BlogRepository _blogRepository = new BlogRepository();
	}
}}

The last step is to use dependency inject to inject the implementation from the project layer, for the interface ITitleRepository.

Solution 2 – Introduce an Abstractions in the foundation layer

In some situations, it might be appropriate to introduce an abstraction in the foundation layer, but only if more than one feature depend on the abstraction. If only the Metadata is using the abstraction it is not sufficient to introduce an abstraction in the foundation layer.

In addition, you should consider why was the abstraction not identified and or present already, it could be a mistake but be sure.

See Part 1, where I have a step by step guide on how to implement this.

Solution 3 – Move the module to the project layer

Whilst this is an easy solution I do not recommend it.

The purpose of the project layer is to aggregate functionality provided by the feature layer and not provide functionality. For example:

  • Page Types module – determine which features are shown on given page type.
  • Context module is responsible for determining the context for each request
    • for example using dependency injection to decide which implementation should be used for any abstractions.

In addition the project is the least stable layer, and the majority of the Metadata functionality in my experience is very stable and similar across all projects, the only variation is how the title is generated and this is not enough to warrant it being moved to the project layer.

I hope this was helpful, and please continue to part 3.

Sitecore Helix – Modules that need to reference another module in the same layer Part 1

If you have not read my previous Helix and Modular Architecture post, I suggest you give it a quick read before this series on dependency.

In software, dependencies can either be explicit or implicit. Examples of explicit dependencies are when one assembly references another assembly. Implicit dependencies, sometimes referred to as soft or weak dependencies, are for example string references to Sitecore fields. See part 3 for more details on implicit/soft dependencies.

Dependency between modules typically happens in the feature layer and modules in the feature layer should not reference each other.

There are conceptually 3 groups of dependency issues:

  1. One or more module(s) dependent on one module.
  2. One module dependent on 2 or more modules ( see part 2).
  3. Modules that depend on each other (See part 3).

One or more module(s) dependent on one module

This is the most typical/common dependency issue to resolve. What to do when one or more modules need to reference another module (illustrated by the diagram above). There are a few solutions to resolve this issue:

  1. Consider that the modules boundaries/responsibilities are incorrect.
  2. Introduce an Abstraction in the foundation layer and keep the concrete implementation in the feature layer.
  3. Move the module to the Foundation Layer.
  4. Each module defines an interface and then a introduce a class in the project layer to mediate.

Solution 1 – Consider that the modules boundaries/responsibilities are incorrect.

This is a good solution when the module boundaries are not correct and or the modules are too small and have almost no responsibility. It is a common problem where there is a tendency to introduce new module/feature for each view.

For example, the breadcrumb and system menu should be part of the Navigation module, as they are both a form of navigation.

As all solutions develop and change we constantly need to consider if our module boundaries are correct and relevant for the customer’s domain.

Solution 2 – Introduce an Abstraction in the foundation layer

This is where you introduce an abstraction that satisfies the dependent modules requirements, in the foundation layer.

The External Profile Data challenge

A common challenge is when the profile/contact/customer/etc. data is stored in a 3rd party system i.e. Microsoft Dynamics CRM, Sales force, SQL database, Custom CRM, etc. For the blog post, we will assume it is stored in Microsoft Dynamics CRM.

Usually a Microsoft Dynamics CRM module is in the feature layer, and the solution will have several typical requirements:

  • Comments – Can only add comments if the user is logged in and you use their name from their profile data.
  • News – Can only see certain news articles if you are logged in.
  • XXX – Soon other modules, will need to know if the visitor is logged in and their name.

In general if a module depends on a 3rd party system they are very unstable, as you cannot control when their API will change. This is one reason that moving the code to the foundation layer is NOT a good idea.

Step 1 – Introduce abstractions

Introduce a new Profile module in the foundation layer, which contains the abstractions/interfaces that models the data/services for the Profile module.

It is much easier to model the data correctly and ensure it is relevant for the customer’s domain, when you do not have to concern yourself with the implementation details and or where the data is stored.

For this simple example, we will assume that the IProfileRepository returns NULL if the visitor is not logged in and the only data we need to retrieve or store is the visitor’s name. For the sake of simplicity, I have ignored how they are authenticated, and logged on.

namespace Foundation.Profile
{
interface IProfile
{
string Name { get; set; }
}
interface IProfileRepository
{
IProfile Get(); // returns null, if the visitor is not authenticated
void Update(IProfile profile);
}
}

Step 2 – Split into 2 feature modules

Whilst not strictly required, I would recommend splitting the Profile feature into 2 feature modules:

  1. Profile – which contains all the Profile UI i.e. Edit profile, Create profile etc.
  2. Customer Relations Management – This implements the abstractions declared in the Profile Foundation module and is responsible for integration with Microsoft dynamics CRM.

The reason I split into 2 modules is that the Profile module doesn’t need to know where its data is stored and probably does not require all the functionality exposed by Microsoft CRM, in addition it provides a number of benefits:

  1. Flexibility
    • This makes it easier to change where the data is stored/retrieved i.e. if you want to change CRM.
    • In my experience, a lot of enterprise customers store profile data initially in Sitecore, and then in later phases change to use their CRM.
    • Even if you use Sitecore, I would consider abstracting where contact/profile data is stored, as Sitecore’s API regarding this has a tendency to change.
  2. Multi-site support
    • In a multi-site solution likely that not all sites want to/can use Microsoft CRM; for example some sites might want to store it in xDB and or somewhere else.
    • It is possible to inject different implementations depending on the site context.
  3. Support multi versions
    • Microsoft Dynamics CRM will change their API and therefore in the transition period it will be possible to inject different version (i.e. a newer version if there is a specific query string, in a specific environment, etc).
  4. Simplifies development
    • The Profile feature is no longer concerned with the complexities of Microsoft CRM.
    • It is possible to mock out Microsoft CRM, so the profile module can be developed before the Microsoft CRM is implemented and or available.
    • The Microsoft CRM Integration does not have to be concerned with the Profile functionality and UI.
  5. Testing
    • Makes it possible to mock out Microsoft CRM and enable the testing of the other modules without Ax.
    • Enable side by side testing – call the same page twice, but inject in different versions of the implementation and ensure that they return the same result.
  6. CRM functionality
    • It is possible that other modules may require additional CRM functionality that is not relevant for the Profile module.

Step 3 – Change the feature modules to depend on the abstractions.

Change the feature modules (Comment, News, Profile, etc.) that require the profile data, to reference the profile interfaces in the foundation layer, and rely on dependency injection to resolve the concert implementation.

Step 4 – Setup Dependency Injection

The context project is responsible for injecting/registering the concrete implementation for the IProfileRepository and IProfile abstractions declared in the foundation layer.

In theory as there is currently only one implementation the Microsoft CRM module could be responsible for injecting/registering its own implementation, but like Page Types is responsible for aggregating what is shown on a page, the Context module is responsible for setting up the context for a given request.

Solution 3 – Move the module to the Foundation Layer

This solution is usually what people choose, but it is rarely a good idea! If the feature was abstract and or very stable, why was it not initially placed in the foundation layer?

So before doing this please consider, if the module is truly a foundation layer module or not and ensure the following

  1. Ensure you should not introduce an abstraction instead (see solution 1)
  2. Ensure that the module does not integrate with external systems (CRM, ERP, PIM, etc.) as by their nature they are very unstable, as you cannot control when their API will change, and therefore unsuitable for the foundation layer.
  3. Ensure as many classes as possible are declared as internal, to keep the public interface to a minimum.
  4. Ideally introduce interfaces and expose those instead of the concrete class implementations.

Solution 4 – Each module defines an interface and then a introduce a class in the project layer to to mediate.

This solution allows each feature to declare an interface to declare what it requires and then the project layer is responsible for routing the request to the relevant module. This solution is typically used to resolve the One module dependent on 2 or more modules (see part 2).

Hope this helped, please continue to part 2.

Sitecore Helix – Modules that need to reference another module in the same layer Part 3

Modules that depend on each other

This is the last part in a 3-part series on dependency, if you have not read the previous posts please read Part 1 and  Part 2.

Implicit (Soft/Weak) Dependencies

So how is it possible to have 2 modules that depend on each other? Surely that would not compile, due to the circular reference, right? But it is possible to have implicit dependencies that is where the C# projects do have to reference each-other – but they depend/communicate using non-compiled methods (usually using a string identifier or guid id).

Implicit dependencies are not only limited to modules that only depend on each other, but all types of module dependencies.

Here is a list of possible sources for soft dependencies:

  1. Url’s
    • Query strings – i.e. q for search term.
    • Path i.e. /…/Electronics/…/ – i.e. the product type (metadata/taxonomy/categories/etc.) is in the path.
    • Host
  2. Sitecore
    • Template ids
    • Field names/id’s
    • Item paths
    • Sitecore queries
    • Lucen / Solr indexesSitecore string references
    • Pipeline parameters
    • Context
  3. Form data
  4. Session
  5. Cache’s
  6. Request items
  7. Configuration
    • Sitecore.config
    • web.config
  8. External systems (i.e. SQL database, ERP, CRM, etc.)
  9. Local storage

The most common examples of soft dependencies are query strings or using the field/path/template string identifiers from another module.

For example: Metadata is a typical example where soft dependencies creep in. The metadata module needs the title for a given item. So it uses the sitecore field id’s from the news module, blog module, product module, etc. to get the title from an item.

Solution

Within a blog post it is difficult to give a clear step by step approach and or an example with how to deal with this circular references, but here is a number of solutions.

  1. Consider that the modules boundaries/responsibilities are incorrect (see solution 1 in part 1).
  2. Introduce an Abstraction in the foundation layer and keep the concrete implementation in the feature layer (see solution 2 in part 1).
    • Slight modification – take the common functionality and move it to a new module in the feature layer and introduce an Abstraction in the foundation layer, for the remaining 2 modules to use.
  3. Each module defines an interface and then a introduce a class in the project layer (see solution 1 in part 2)

I normally recommend abstraction as the best tool to deal with dependencies. But when 2 modules are only dependent on each other, it is normally because the modules boundaries/responsibilities are incorrect as they are violating the Common-closure principle.

I hope this series on dependencies has been helpful 🙂

 

Helix and Modular Architecture

Helix is Sitecore’ s code name for Modular Architecture, Helix is composed of 2 main areas:

  • Principles
  • Practical Applications (i.e. how we support/implement/conform to the principles/guidelines)

Unfortunately, many on slack & twitter are focusing on the Practical Applications and not the principles. The architectural principles are more important, than how we support/implement the website; So in this blog I will make a brief introduction to the principles.

Helix/Modular Architecture is primarily based on Packaging Principles. In addition, several concepts have been introduced to help support Packaging Principles:

  1. Layers
  2. Module (referred to a package in Packaging Principles)

Now whilst not strictly part of Modular Architecture – I believe all software development should adhere to SOLID principles.

Packaging principles

Is a way of grouping classes to make them more organized, manageable and maintainable! It helps us understand which classes can be packaged together which is called package (module) cohesion and how these packages should relate with one another called package (module) coupling.

“Building software without packaging, is like trying to build a sand castle one grain at a time” – Uncle Bob

The result of packaging in Helix terminology it is called a Module (not a package); therefore, for the remained of this blog I will use module i.e. Module Coupling/Cohesion etc.

Module Coupling

Module Coupling is the corner stone principle in Modular Architecture and determines how modules relate/depend on each other.

Stable-dependencies principle (SDP)

Depend in the direction of stability – a module should only rely on modules that are more stable than itself.

A stable piece of code is one where its interface does not change over time.

Features are expected to change over time and are less stable, as requirements change and or new requirements occur. Unstable code is not bad but a reality!

stable

Stable-abstractions principle (SAP)

Abstractness should increase with stability. Modules that are maximally stable should be therefore maximally abstract. Unstable modules should be concrete. The abstraction of a module should be in proportion to its stability

Acyclic dependencies principle (ADP)

The dependencies between modules must not form cycles, i.e. no circular references are enforced by Visual Studio for C# but not JavaScript, Sitecore Templates, Query strings, web services etc.

Module Cohesion

The following principles help identify what should be packaged together as a module.

Common-closure principle (CCP)

The classes in a module should be closed together against the same kinds of change. A change that affects a module affects all the classes in that module and no other module.
What changes together, should live together.

Common-reuse principle (CRP)

When you depend on one class in a module you depend on all the classes in that module, not just the one you are using.

Reuse-release equivalence principle (REP)

Essentially means that the module must be created with reusable classes — “Either all the classes inside the module are reusable, or none of them are”. The classes must also be of the same family.

Layers

layers

Layers help by visualizing and enforcing the stable dependency and stable abstraction principles of module coupling. Each layer defines the stability of the modules and the direction of dependency.

Modules in the feature layer should not reference each other. A layer is physically described in your solution by folders in the file system, solution folders in Visual Studio, folders in Sitecore along with namespaces in code.

A layer is physically described in your solution by folders in the file-system, solution folders in Visual Studio, folders in Sitecore along with namespaces in Foundation Layer (previously call framework).

Foundation Layer

This layer is the most stable and contains only modules which are not subject to change, and if they do change it will have implications for all modules, typical foundation modules:

  • Taxonomy
  • Dictionary
  • Indexing

Feature Layer

Modules in this layer resembles the customer domain and need to be flexible, and therefore are more likely to change, typical feature modules:

  • Navigation
  • Search
  • Metadata

Project Layer

The project is the least stable layer and can reference all modules as it is used to aggregate the functionality provided by the feature and foundation layers, typical project modules:

  • Page Types
  • Design
  • Context (Responsible for dependency injection (IoC), of course a feature can internally use DI/IoC)

Module

The result of packaging a number of classes together is called a MODULE. Each module is represented by a single Visual Studio project.

A module divides domain functionality into loosely coupled modules with clear boundaries, where each Module can contain Presentation, Business logic, Sitecore Content (Templates, layouts, setting items, etc.) and Data (Sitecore, SQL, etc.).

module

I hope this blog post was helpful and I plan to do a series on common pitfalls which are usually related to the following 2 issues:

  1. One or more modules needs to reference another module in the same layer (which they should NOT do).
  2. how to identify a module and what should be in it.