Tag 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

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.