Using Sitecore service bus

With latest Sitecore 9.0.1 release, the EXM became part of the platform. A big improvement in EXM, and more importantly to Sitecore 9 is the introduction of service bus implementation, which apparently, can be used for much more purposes.
The implementation is based on Rebus and is being used in Sitecore with “smart endpoints, dumb pipes” methodology. Read more about service bus methodologies.

Rebus is built the way that it is able to support different transport layers including

  • SQL database
  • Azure Service Bus
  • RabbitMQ
  • MSMQ
  • and even the file system

At the moment only SQL database transport provider is implemented in Sitecore, however I would expect Azure Service Bus implementation to be in place very soon in the platform.

Just for clarification, the service bus in Sitecore is being used in order to transfer messages between Sitecore instance roles – ContentDelivery, ContentManagement, Processing, Reporting, Standalone, DedicatedDispatch as well as xConnnect services – xConnect search indexer and Marketing automation engine. The need in such messaging mechanism is self explanatory as roled instances normally exist in isolated environmetns with limited boundaries. At the moment, EXM uses the service bus for transferring CD instance events that needs to be handled in CM role server, as well as introduction of DedicatedDispatch role adds a need in messaging for triggering dispatches.

As being said, this is a huge improvement which could see additional usage later on in the product as well as can already be used as developers for custom needs.

Custom use of service bus

Everybody knows Translate mechanism in Sitecore which uses dictionary items in order to keep translations for phrases. Recently we have implemented an extension to Sitecore translator which supports automatic phrase items creation with default values on them in case the translation phrase is missing in the database.
In Sitecore 9.0.1 it is finally possible with usage of the service bus, and so we have implemented the Pintle.Dictionary sitecore module. https://github.com/pintle/pintle-dictionary.

In this section I will demonstrate how the service bus is being used in the module to serve custom developer needs and will cover sending and handling of a custom message between content delivery and content management sitecore roles.

Before to start implementing custom service bus few nuget packages need to be installed to the solution:

  • Sitecore.Framework.Messaging.Abstractions
  • Sitecore.Framework.Messaging.Configuration

Create message object

Create a normal .net class for the message object. It will be serialized into JSON by Rebus on transport level:

using System;

public class CreateDictionaryItemMessage
{
	public string DictionaryKey { get; set; }
	public string Language { get; set; }
	public string DefaultValue { get; set; }
	public Guid DictionaryDomainId { get; set; }
	public string Database { get; set; }
}

See whole file at CreateDictionaryItemMessage.cs

Create service bus object

Create an empty class for the message bus

public sealed class DictionaryMessageBus
{
}

Yes, that simple, no need to inherit anything or implement any methods. See whole DictionaryMessageBus.cs file.

Configure service bus

Now its time to configure the message bus. There is new section in sitecore configuration for messaging and here is how configuration looks for the DictionaryMessageBus with CreateDictionaryItemMessage transfer object:

<configuration
	xmlns:patch="http://www.sitecore.net/xmlconfig/"
	xmlns:role="http://www.sitecore.net/xmlconfig/role">
	<sitecore>
		<Messaging>
			<Rebus>
				<Pintle.Dictionary.Messaging.DictionaryMessageBus>
					<Transport>
						<SqlServer>
							<OneWay role:require="(Standalone or ContentManagement) and !ContentDelivery">false</OneWay>
							<OneWay role:require="ContentDelivery">true</OneWay>
							<ConnectionStringOrName>messaging</ConnectionStringOrName>
							<TableName>Sitecore_Transport</TableName>
							<InputQueueName>DictionaryMessagesQueue</InputQueueName>
						</SqlServer>
					</Transport>
					<Routing>
						<TypeBasedMappings>
							<TypeMappings>
								<CreateDictionaryItemMessageMapping>
									<Type>Pintle.Dictionary.Messaging.CreateDictionaryItemMessage, Pintle.Dictionary</Type>
									<DestinationQueue>DictionaryMessagesQueue</DestinationQueue>
								</CreateDictionaryItemMessageMapping>
							</TypeMappings>
						</TypeBasedMappings>
					</Routing>
					<Options role:require="(Standalone or ContentManagement) and !ContentDelivery">
						<SetNumberOfWorkers>1</SetNumberOfWorkers>
						<SimpleRetryStrategy>
							<ErrorQueueAddress>Error</ErrorQueueAddress>
							<MaxDeliveryAttempts>1</MaxDeliveryAttempts>
							<SecondLevelRetriesEnabled>false</SecondLevelRetriesEnabled>
						</SimpleRetryStrategy>
					</Options>
					<Logging Type="Sitecore.Messaging.SitecoreLoggerFactory, Sitecore.Messaging"/>
				</Pintle.Dictionary.Messaging.DictionaryMessageBus>
			</Rebus>
		</Messaging>
	</sitecore>
</configuration>

See whole Pintle.Dictionary.Messaging.config file. This config file will go to both CD and CM instance, the only one difference between them will be usage of OneWay parameter which is true on CD configuration as long as it only sends messages. Please, refer to rule based configuration in Sitecore 9 for more info.

Initialize service bus

Once configured, the message bus needs to be initialized using `initialize` sitecore pipeline.

using System;
using Messaging;
using Sitecore.Framework.Messaging;
using Sitecore.Pipelines;

public class InitializeDictionaryMessaging
{
	private readonly IServiceProvider serviceProvider;

	public InitializeDictionaryMessaging(IServiceProvider serviceProvider)
	{
		this.serviceProvider = serviceProvider;
	}

	public void Process(PipelineArgs args)
	{
		this.serviceProvider.StartMessageBus();
	}
}

See whole InitializeDictionaryMessaging.cs file.

Pipeline injection should be in place in the configuration file :

<configuration 
	xmlns:patch="http://www.sitecore.net/xmlconfig/"
	xmlns:role="http://www.sitecore.net/xmlconfig/role">
	<sitecore>
		<pipelines>
			<initialize>
				<processor type="Pintle.Dictionary.Pipelines.Initialize.InitializeDictionaryMessaging, Pintle.Dictionary" resolve="true" />
			</initialize>
		</pipelines>
	</sitecore>
</configuration>

See whole Pintle.Dictionary.Pipelines.config file.

Send message

Now the bus is configured and initialized and is ready to accept messages. In order to send message, the service bus object needs to be resolved as IMessageBus from the IoC container:

using Messaging;
using Sitecore.Framework.Messaging;
using Sitecore;
using Sitecore.Data.Managers;

public class DictionaryItemRepository
{
	private readonly IMessageBus messageBus;

	public DictionaryItemRepository(IMessageBus messageBus)
	{
		this.messageBus = messageBus;
	}

	public virtual void Create(string dictionaryKey, string defaultValue, string language)
	{
		language = language ?? Context.Language?.Name;
		var lang = LanguageManager.GetLanguage(language);

		this.messageBus.SendAsync(new CreateDictionaryItemMessage
		{
			DictionaryKey = dictionaryKey,
			DefaultValue = defaultValue,
			Database = this.settings.ItemCreationDatabase,
			Language = lang.Name,
			DictionaryDomainId = this.GetDomainItem(lang).ID.Guid
		});
	}
}

See whole DictionaryItemRepository.cs file.

Handle message

Now the message is being send, we need to implement the handler for it. In order to do it, we need to implement `IMessageHandler` interface on our handler class:

using System.Threading.Tasks;
using Sitecore.Abstractions;
using Sitecore.Data;
using Sitecore.Data.Managers;
using Sitecore.Framework.Messaging;
using Sitecore.Globalization;

public class DictionaryItemMessageHandler : IMessageHandler
{
	private readonly DictionaryItemRepository repository;

	public DictionaryItemMessageHandler(DictionaryItemRepository repository)
	{
		this.repository = repository;
	}

	public Task Handle(CreateDictionaryItemMessage message, IMessageReceiveContext receiveContext, IMessageReplyContext replyContext)
	{
		if (this.ValidateMessage(message))
		{
			var database = Database.GetDatabase(message.Database);
			var language = LanguageManager.GetLanguage(message.Language);

			using(new DatabaseSwitcher(database))
			using(new LanguageSwitcher(language))
			{
				this.repository.CreateItem(
					message.DictionaryKey,
					message.DefaultValue,
					message.DictionaryDomainId,
					language,
					database);
			}
		}

		return Task.CompletedTask;
	}
}

See whole DictionaryItemMessageHandler.cs file.

In order to subscribe the handler to the send event, we need to register it in IoC container on required role.

using Messaging;
using Sitecore.Framework.Messaging;
using Microsoft.Extensions.DependencyInjection;
using Sitecore.DependencyInjection;

public class ServicesConfiguratorCm : IServicesConfigurator
{
	public void Configure(IServiceCollection serviceCollection)
	{
		serviceCollection.AddTransient();
	}
}

See whole ServicesConfiguratorCm.cs file.

In our case only CM instance should be handling create dictionary item messages and therefore we should use role configuration syntax again:

<configuration 
	xmlns:patch="http://www.sitecore.net/xmlconfig/"
	xmlns:role="http://www.sitecore.net/xmlconfig/role">
	<sitecore>
		<services>
			<configurator type="Pintle.Dictionary.Configuration.ServicesConfiguratorCm, Pintle.Dictionary" 
			              role:require="(Standalone or ContentManagement) and !ContentDelivery"/>
		</services>
	</sitecore>
</configuration>

See whole Pintle.Dictionary.DependencyInjection.config file.

As for message handlers, there are options to use defer strategies, for example, if initial message handler fails, we can postpone handling to the future and the message bus will take care of handling the ‘re-try‘. I will describe deferral message handlers in my next post.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s