Integrate with decoupled systems
In modern architectures, your Xperience project may need to interact with other external systems that operate independently, without shared databases and other internal resources. You can integrate Xperience with these decoupled systems by establishing asynchronous messaging through a messaging service, which facilitates the exchange of messages between the systems. Depending on your use case, Xperience can act as either message producer, message consumer, or both.
The following architectural overview illustrates the flow of messages between an Xperience project and a decoupled system:
Decoupled integrations can be utilized in a wide range of scenarios. Some examples include:
- send data submitted by customers through forms into your CRM
- synchronize product data in Xperience with your inventory management system
- communicate with various types of business and ticketing systems
This page explains how to set up a decoupled integration with Xperience, covering the key concepts, configuration steps to consume and produce messages, and an example implementation.
Key concepts
The following concepts address the key points of decoupled integrations in Xperience:
- Message triggers – determine when messages should be sent from Xperience
- Message contents – define what data is exchanged
- Messaging service – handle the transmission of messages between systems
- Messaging libraries – use code abstractions for working with the service
Message triggers
When sending messages from your project, you need to determine what actions require communication with the decoupled system. In Xperience, significant operations are represented by events. Event handling allows you to implement custom logic to send messages when those events occur.
Learn about events and event handling
See the corresponding pages about global events and object events or consult the global events reference to learn about available events and their handling.
You can also choose to send messages directly from appropriate places in your custom code without relying on the provided events.
Message contents
Messages represent structured data to be exchanged between systems. To ensure consistency, a message can be implemented as a class in a shared library, accessible from both systems.
For example, you can transfer:
- Content item data
- Page data
- Submitted form data
- Contact data
Messaging service
To enable communication with a decoupled system, you need to set up an external messaging service. You can use cloud-based messaging services such as Azure Service Bus or self-managed message brokers, for instance, RabbitMQ.
Note: An external messaging service is required both for SaaS and private-cloud deployments, as it’s not a part of the system or Xperience Portal.
Depending on your requirements, you can configure different communication patterns:
- Message queues – a single stream of messages typically received by one consumer.
- Topics and subscriptions – enable one-to-many communication and categorization of messages by topic.
Messaging libraries
You can use existing libraries that already implement typical messaging patterns to send and receive messages. These libraries provide an abstraction on top of the actual messaging service, making it easier to switch between different messaging platforms without significantly changing your application code.
The sample code on this page uses the Rebus library. Similar functionality can be provided by other frameworks and libraries, for example, Mass Transit and Wolverine.
Xperience as a message producer
The following steps outline how to configure Xperience to send messages, using the Rebus library and Azure Service Bus with the queue messaging pattern. You can use other services and libraries to achieve the same result.
- Prerequisites:
- Set up a queue in your Azure Service Bus namespace.
- Install the Rebus and Rebus.ServiceProvider NuGet packages.
- Define a message based on your scenario.
- Configure the Rebus library in the
Program.csfile:C#Program.csusing Rebus.Config; using Rebus.Routing.TypeBased; ... builder.Services.AddRebus(configure => configure .Transport(t => t.UseAzureServiceBusAsOneWayClient("##connectionString##")) .Routing(r => r.TypeBased().Map<OutgoingMessage>("##queue-name##"))); ... - Handle events and send messages from the handler.
- See the Example section for a full implementation.
Xperience as a message consumer
The following steps outline how to configure Xperience to receive messages, using the Rebus library and Azure Service Bus with the queue messaging pattern. You can use other services and libraries to achieve the same result.
Prerequisites:
- Set up a queue in your Azure Service Bus namespace.
- Install the Rebus and Rebus.ServiceProvider NuGet packages.
- Define a message based on your scenario.
Configure the Rebus library in the
Program.csfile:C#Program.csusing Rebus.Config; builder.Services.AddRebus(configure => configure .Transport(t => t.UseAzureServiceBus("##connectionString##", "##queue-name##")));Create a message handler by implementing the
IHandleMessagesRebus interface:C#SampleMessageHandler.csusing Rebus.Handlers; public class SampleMessageHandler : IHandleMessages<IncomingMessage> { public Task Handle(IncomingMessage message) { // Custom logic to process the message } }Register the handler in the
Program.csfile:C#Program.csbuilder.Services.AddRebusHandler<SampleMessageHandler>();
Example
When a brand new article is published, you might want to automatically promote it on social media to gain more traction. This example shows an integration where Xperience sends a message to a decoupled system handling social media publishing when a new article is published for the first time.
Prerequisites
This example uses the Rebus library and Azure Service Bus. To follow along with this example, install the Rebus and Rebus.ServiceProvider NuGet packages, set up a queue in your Azure Service Bus namespace and obtain the corresponding connection string.
It’s important to note that the principles shown in this example are not limited to specific platforms and can be implemented with other messaging libraries and services.
Define the message
Create a class in a library shared by both systems.
public class NewArticlePublishedMessage
{
public string ArticleTitle { get; init; }
public string ArticleSummary { get; init; }
public string ArticleText { get; init; }
public string ArticleUrl { get; init; }
}
Configure the Rebus library
Add the Rebus library to your project with the following configuration:
using Rebus.Config;
using Rebus.Routing.TypeBased;
...
builder.Services.AddRebus(configure => configure
.Transport(t => t.UseAzureServiceBusAsOneWayClient("##connectionString##"))
.Routing(r => r.TypeBased().Map<NewArticlePublishedMessage>("##queue-name##")));
...
Handle events and send messages
Create a handler for the WebPageEvents.Publish.Execute event that will send messages when the event takes place. Assign the handler to the event in a custom initialization module.
using CMS;
using CMS.Core;
using CMS.ContentEngine;
using CMS.DataEngine;
using CMS.Websites;
using Rebus.Bus;
using Microsoft.Extensions.DependencyInjection;
// Registers the custom module into the system
[assembly: RegisterModule(typeof(Custom.CustomMessagingModule))]
namespace Custom
{
public class CustomMessagingModule : Module
{
private IBus bus;
private IWebPageUrlRetriever webPageUrlRetriever;
// Module class constructor, the system registers the module under the name 'CustomMessagingModule'
public CustomMessagingModule()
: base(nameof(CustomMessagingModule))
{
}
// Contains initialization code that is executed when the application starts
protected override void OnInit(ModuleInitParameters parameters)
{
base.OnInit(parameters);
// Injects the Rebus IBus service used to send messages
bus = parameters.Services.GetRequiredService<IBus>();
// Injects the IWebPageUrlRetriever service used to retrieve the URL of the article
webPageUrlRetriever = parameters.Services.GetRequiredService<IWebPageUrlRetriever>();
// Assigns the custom handler to the event
WebPageEvents.Publish.Execute += Article_Published_EventHandler;
// Implement the Article_Published_EventHandler
// See the code block below for the implementation
}
}
}
// Sends a message to the message queue when a page of the 'ArticlePage' content type is published for the first time
private void Article_Published_EventHandler(object sender, PublishWebPageEventArgs e)
{
// The content type of the articles to be promoted
string eligibleContentType = "Acme.ArticlePage";
// Excludes pages that are not published for the first time and the pages of content types other than 'Acme.ArticlePage'
if (e.IsFirstTimePublished == false || e.ContentTypeName != eligibleContentType)
{
return;
}
// Obtains necessary data from the event arguments ('PublishWebPageEventArgs')
int articleId = e.ID;
string articleLanguage = e.ContentLanguageName;
ContentItemDataEventContainer articleData = e.ContentItemData;
// Retrieves article field values from the data container and assigns them to newly instantiated variables
// 'ContentItemDataEventContainer.TryGetValue()' returns true if the specified key exists in the container and outputs its value to the provided variable
articleData.TryGetValue("ArticleTitle", out string articleTitle);
articleData.TryGetValue("ArticlePageSummary", out string articleSummary);
articleData.TryGetValue("ArticlePageText", out string articleText);
// Retrieves the article URL using the 'IWebPageUrlRetriever'
WebPageUrl articleUrl = webPageUrlRetriever.Retrieve(articleId, articleLanguage).GetAwaiter().GetResult();
// Creates a new message with the article data
NewArticlePublishedMessage message = new NewArticlePublishedMessage()
{
ArticleTitle = articleTitle,
ArticleSummary = articleSummary,
ArticleText = articleText,
// Assigns the absolute URL of the article to the message property
ArticleUrl = articleUrl.AbsoluteUrl
};
// Sends the message to the queue
bus.Send(message);
}