Welcome to the eighteenth post in my series focused on providing a set of revision notes for the PL-400: Microsoft Power Platform Developer exam. Last time around, we explored some of the capabilities offered by the Microsoft Dataverse Web API, which can be particularly useful when you are building integrations targeting this platform. This post finished our discussion on the Extend the platform area of the exam, which has a 15-20% total weighting. We now move into the final exam area, Develop Integrations and our first subject area concerning events:
Publish and consume events
- publish an event by using the API
- publish an event by using the Plug-in Registration Tool
- register service endpoints including webhooks, Azure Service Bus, and Azure Event Hub
- implement a Common Data Service listener for an Azure solution
- create an Azure Function that interacts with Power Platform
Although this area of the exam has a somewhat low weighting (5-10%) when compared with the other subjects we’ve looked at, there is still some essential things to learn here that is not only relevant to the exam itself but also for your daily travels with the platform. Events are just one way in which you can integrate your Microsoft Dataverse environment alongside Microsoft Azure. Developing modern cloud applications involving core Microsoft products invariably means you must have a general awareness of the capabilities within Azure, so this topic provides an excellent opportunity to increase your knowledge in this area. Let’s dive in now to see what events are all about and how they relate to Azure!
As with all posts in this series, the aim is to provide a broad outline of the core areas to keep in mind when tackling the exam, linked to appropriate resources for more focused study. Ideally, your revision should involve a high degree of hands-on testing and familiarity in working with the platform if you want to do well in this exam. It would help if you also understand working with plug-ins and the Plug-in Registration Tool, which we’ve already covered in the series.
What are Events?
You may be worried at this stage that events are a whole new concept that will take considerable time to understand. Fortunately, that’s not the case at all and, if you have good familiarity working with the IExecutionContext Interface from within a plug-in, you will find yourself right at home. This is because Events encapsulate all data points that we would typically work within our execution context - whether that be shared variables, output parameters, Business Unit ID or more. An example of how an event can look, when passed out as a JSON object, can be seen below:
{
"BusinessUnitId": "f64c9d1f-d076-ea11-a812-000d3a86a586",
"CorrelationId": "dfe79039-5a08-4d0a-b4ee-9534625c8654",
"Depth": 1,
"InitiatingUserAzureActiveDirectoryObjectId": "00000000-0000-0000-0000-000000000000",
"InitiatingUserId": "c164a5aa-765e-4181-8771-537e8b1ebf3b",
"InputParameters": [
{
"key": "Target",
"value": {
"__type": "Entity:http://schemas.microsoft.com/xrm/2011/Contracts",
"Attributes": [
{
"key": "territorycode",
"value": {
"__type": "OptionSetValue:http://schemas.microsoft.com/xrm/2011/Contracts",
"Value": 1
}
},
{
"key": "address2_freighttermscode",
"value": {
"__type": "OptionSetValue:http://schemas.microsoft.com/xrm/2011/Contracts",
"Value": 1
}
},
{
"key": "address2_shippingmethodcode",
"value": {
"__type": "OptionSetValue:http://schemas.microsoft.com/xrm/2011/Contracts",
"Value": 1
}
},
{
"key": "isprivate",
"value": false
},
{
"key": "followemail",
"value": true
},
{
"key": "donotbulkemail",
"value": false
},
{
"key": "donotsendmm",
"value": false
},
{
"key": "emailaddress1",
"value": "[email protected]"
},
{
"key": "jobtitle",
"value": "Manager"
},
{
"key": "customertypecode",
"value": {
"__type": "OptionSetValue:http://schemas.microsoft.com/xrm/2011/Contracts",
"Value": 1
}
},
{
"key": "fullname",
"value": "John Doe"
},
{
"key": "isautocreate",
"value": false
},
{
"key": "ownerid",
"value": {
"__type": "EntityReference:http://schemas.microsoft.com/xrm/2011/Contracts",
"Id": "c164a5aa-765e-4181-8771-537e8b1ebf3b",
"KeyAttributes": [],
"LogicalName": "systemuser",
"Name": null,
"RowVersion": null
}
},
{
"key": "isbackofficecustomer",
"value": false
},
{
"key": "donotbulkpostalmail",
"value": false
},
{
"key": "donotpostalmail",
"value": false
},
{
"key": "donotemail",
"value": false
},
{
"key": "statecode",
"value": {
"__type": "OptionSetValue:http://schemas.microsoft.com/xrm/2011/Contracts",
"Value": 0
}
},
{
"key": "address2_addresstypecode",
"value": {
"__type": "OptionSetValue:http://schemas.microsoft.com/xrm/2011/Contracts",
"Value": 1
}
},
{
"key": "donotphone",
"value": false
},
{
"key": "createdon",
"value": "/Date(1598771723000)/"
},
{
"key": "transactioncurrencyid",
"value": {
"__type": "EntityReference:http://schemas.microsoft.com/xrm/2011/Contracts",
"Id": "785af3f0-d976-ea11-a812-000d3a86a586",
"KeyAttributes": [],
"LogicalName": "transactioncurrency",
"Name": null,
"RowVersion": null
}
},
{
"key": "contactid",
"value": "a2b1d88b-90ea-ea11-a815-000d3a86a3ce"
},
{
"key": "haschildrencode",
"value": {
"__type": "OptionSetValue:http://schemas.microsoft.com/xrm/2011/Contracts",
"Value": 1
}
},
{
"key": "modifiedby",
"value": {
"__type": "EntityReference:http://schemas.microsoft.com/xrm/2011/Contracts",
"Id": "c164a5aa-765e-4181-8771-537e8b1ebf3b",
"KeyAttributes": [],
"LogicalName": "systemuser",
"Name": null,
"RowVersion": null
}
},
{
"key": "leadsourcecode",
"value": {
"__type": "OptionSetValue:http://schemas.microsoft.com/xrm/2011/Contracts",
"Value": 1
}
},
{
"key": "statuscode",
"value": {
"__type": "OptionSetValue:http://schemas.microsoft.com/xrm/2011/Contracts",
"Value": 1
}
},
{
"key": "modifiedonbehalfby",
"value": null
},
{
"key": "preferredcontactmethodcode",
"value": {
"__type": "OptionSetValue:http://schemas.microsoft.com/xrm/2011/Contracts",
"Value": 1
}
},
{
"key": "lastname",
"value": "Doe"
},
{
"key": "firstname",
"value": "John"
},
{
"key": "createdby",
"value": {
"__type": "EntityReference:http://schemas.microsoft.com/xrm/2011/Contracts",
"Id": "c164a5aa-765e-4181-8771-537e8b1ebf3b",
"KeyAttributes": [],
"LogicalName": "systemuser",
"Name": null,
"RowVersion": null
}
},
{
"key": "educationcode",
"value": {
"__type": "OptionSetValue:http://schemas.microsoft.com/xrm/2011/Contracts",
"Value": 1
}
},
{
"key": "yomifullname",
"value": "John Doe"
},
{
"key": "donotfax",
"value": false
},
{
"key": "merged",
"value": false
},
{
"key": "customersizecode",
"value": {
"__type": "OptionSetValue:http://schemas.microsoft.com/xrm/2011/Contracts",
"Value": 1
}
},
{
"key": "marketingonly",
"value": false
},
{
"key": "owningbusinessunit",
"value": {
"__type": "EntityReference:http://schemas.microsoft.com/xrm/2011/Contracts",
"Id": "f64c9d1f-d076-ea11-a812-000d3a86a586",
"KeyAttributes": [],
"LogicalName": "businessunit",
"Name": null,
"RowVersion": null
}
},
{
"key": "shippingmethodcode",
"value": {
"__type": "OptionSetValue:http://schemas.microsoft.com/xrm/2011/Contracts",
"Value": 1
}
},
{
"key": "creditonhold",
"value": false
},
{
"key": "modifiedon",
"value": "/Date(1598771723000)/"
},
{
"key": "participatesinworkflow",
"value": false
},
{
"key": "preferredappointmenttimecode",
"value": {
"__type": "OptionSetValue:http://schemas.microsoft.com/xrm/2011/Contracts",
"Value": 1
}
},
{
"key": "exchangerate",
"value": 1.0
}
],
"EntityState": null,
"FormattedValues": [
{
"key": "territorycode",
"value": "Default Value"
},
{
"key": "address2_freighttermscode",
"value": "Default Value"
},
{
"key": "address2_shippingmethodcode",
"value": "Default Value"
},
{
"key": "isprivate",
"value": "No"
},
{
"key": "followemail",
"value": "Allow"
},
{
"key": "donotbulkemail",
"value": "Allow"
},
{
"key": "donotsendmm",
"value": "Send"
},
{
"key": "customertypecode",
"value": "Default Value"
},
{
"key": "isautocreate",
"value": "No"
},
{
"key": "isbackofficecustomer",
"value": "No"
},
{
"key": "donotbulkpostalmail",
"value": "No"
},
{
"key": "donotpostalmail",
"value": "Allow"
},
{
"key": "donotemail",
"value": "Allow"
},
{
"key": "statecode",
"value": "Active"
},
{
"key": "address2_addresstypecode",
"value": "Default Value"
},
{
"key": "donotphone",
"value": "Allow"
},
{
"key": "createdon",
"value": "2020-08-30T07:15:23-00:00"
},
{
"key": "haschildrencode",
"value": "Default Value"
},
{
"key": "leadsourcecode",
"value": "Default Value"
},
{
"key": "statuscode",
"value": "Active"
},
{
"key": "preferredcontactmethodcode",
"value": "Any"
},
{
"key": "educationcode",
"value": "Default Value"
},
{
"key": "donotfax",
"value": "Allow"
},
{
"key": "merged",
"value": "No"
},
{
"key": "customersizecode",
"value": "Default Value"
},
{
"key": "marketingonly",
"value": "No"
},
{
"key": "shippingmethodcode",
"value": "Default Value"
},
{
"key": "creditonhold",
"value": "No"
},
{
"key": "modifiedon",
"value": "2020-08-30T07:15:23-00:00"
},
{
"key": "participatesinworkflow",
"value": "No"
},
{
"key": "preferredappointmenttimecode",
"value": "Morning"
}
],
"Id": "a2b1d88b-90ea-ea11-a815-000d3a86a3ce",
"KeyAttributes": [],
"LogicalName": "contact",
"RelatedEntities": [],
"RowVersion": "4198516"
}
}
],
"IsExecutingOffline": false,
"IsInTransaction": false,
"IsOfflinePlayback": false,
"IsolationMode": 1,
"MessageName": "Create",
"Mode": 1,
"OperationCreatedOn": "/Date(1598771723000+0000)/",
"OperationId": "b1b1d88b-90ea-ea11-a815-000d3a86a3ce",
"OrganizationId": "9c5d5db0-47c7-4741-aa25-08eb4cdf59a3",
"OrganizationName": "orgad623a9e",
"OutputParameters": [
{
"key": "id",
"value": "a2b1d88b-90ea-ea11-a815-000d3a86a3ce"
}
],
"OwningExtension": {
"Id": "aa45df79-90ea-ea11-a815-000d3a86a3ce",
"KeyAttributes": [],
"LogicalName": "sdkmessageprocessingstep",
"Name": null,
"RowVersion": null
},
"ParentContext": {
"BusinessUnitId": "f64c9d1f-d076-ea11-a812-000d3a86a586",
"CorrelationId": "dfe79039-5a08-4d0a-b4ee-9534625c8654",
"Depth": 1,
"InitiatingUserAzureActiveDirectoryObjectId": "00000000-0000-0000-0000-000000000000",
"InitiatingUserId": "c164a5aa-765e-4181-8771-537e8b1ebf3b",
"InputParameters": [
{
"key": "Target",
"value": {
"__type": "Entity:http://schemas.microsoft.com/xrm/2011/Contracts",
"Attributes": [
{
"key": "emailaddress1",
"value": "[email protected]"
},
{
"key": "jobtitle",
"value": "Manager"
},
{
"key": "lastname",
"value": "Doe"
},
{
"key": "firstname",
"value": "John"
},
{
"key": "creditonhold",
"value": false
},
{
"key": "donotpostalmail",
"value": false
},
{
"key": "donotfax",
"value": false
},
{
"key": "donotphone",
"value": false
},
{
"key": "donotbulkemail",
"value": false
},
{
"key": "followemail",
"value": true
},
{
"key": "donotemail",
"value": false
},
{
"key": "preferredcontactmethodcode",
"value": {
"__type": "OptionSetValue:http://schemas.microsoft.com/xrm/2011/Contracts",
"Value": 1
}
},
{
"key": "statuscode",
"value": {
"__type": "OptionSetValue:http://schemas.microsoft.com/xrm/2011/Contracts",
"Value": 1
}
},
{
"key": "donotbulkpostalmail",
"value": false
},
{
"key": "transactioncurrencyid",
"value": {
"__type": "EntityReference:http://schemas.microsoft.com/xrm/2011/Contracts",
"Id": "785af3f0-d976-ea11-a812-000d3a86a586",
"KeyAttributes": [],
"LogicalName": "transactioncurrency",
"Name": null,
"RowVersion": null
}
},
{
"key": "ownerid",
"value": {
"__type": "EntityReference:http://schemas.microsoft.com/xrm/2011/Contracts",
"Id": "c164a5aa-765e-4181-8771-537e8b1ebf3b",
"KeyAttributes": [],
"LogicalName": "systemuser",
"Name": null,
"RowVersion": null
}
},
{
"key": "preferredappointmenttimecode",
"value": {
"__type": "OptionSetValue:http://schemas.microsoft.com/xrm/2011/Contracts",
"Value": 1
}
},
{
"key": "customersizecode",
"value": {
"__type": "OptionSetValue:http://schemas.microsoft.com/xrm/2011/Contracts",
"Value": 1
}
},
{
"key": "address2_shippingmethodcode",
"value": {
"__type": "OptionSetValue:http://schemas.microsoft.com/xrm/2011/Contracts",
"Value": 1
}
},
{
"key": "address2_freighttermscode",
"value": {
"__type": "OptionSetValue:http://schemas.microsoft.com/xrm/2011/Contracts",
"Value": 1
}
},
{
"key": "educationcode",
"value": {
"__type": "OptionSetValue:http://schemas.microsoft.com/xrm/2011/Contracts",
"Value": 1
}
},
{
"key": "isautocreate",
"value": false
},
{
"key": "leadsourcecode",
"value": {
"__type": "OptionSetValue:http://schemas.microsoft.com/xrm/2011/Contracts",
"Value": 1
}
},
{
"key": "shippingmethodcode",
"value": {
"__type": "OptionSetValue:http://schemas.microsoft.com/xrm/2011/Contracts",
"Value": 1
}
},
{
"key": "participatesinworkflow",
"value": false
},
{
"key": "marketingonly",
"value": false
},
{
"key": "territorycode",
"value": {
"__type": "OptionSetValue:http://schemas.microsoft.com/xrm/2011/Contracts",
"Value": 1
}
},
{
"key": "isprivate",
"value": false
},
{
"key": "isbackofficecustomer",
"value": false
},
{
"key": "merged",
"value": false
},
{
"key": "donotsendmm",
"value": false
},
{
"key": "address2_addresstypecode",
"value": {
"__type": "OptionSetValue:http://schemas.microsoft.com/xrm/2011/Contracts",
"Value": 1
}
},
{
"key": "customertypecode",
"value": {
"__type": "OptionSetValue:http://schemas.microsoft.com/xrm/2011/Contracts",
"Value": 1
}
},
{
"key": "haschildrencode",
"value": {
"__type": "OptionSetValue:http://schemas.microsoft.com/xrm/2011/Contracts",
"Value": 1
}
},
{
"key": "contactid",
"value": "a2b1d88b-90ea-ea11-a815-000d3a86a3ce"
}
],
"EntityState": null,
"FormattedValues": [],
"Id": "a2b1d88b-90ea-ea11-a815-000d3a86a3ce",
"KeyAttributes": [],
"LogicalName": "contact",
"RelatedEntities": [],
"RowVersion": null
}
},
{
"key": "SuppressDuplicateDetection",
"value": false
}
],
"IsExecutingOffline": false,
"IsInTransaction": false,
"IsOfflinePlayback": false,
"IsolationMode": 1,
"MessageName": "Create",
"Mode": 1,
"OperationCreatedOn": "/Date(1598771723000+0000)/",
"OperationId": "b1b1d88b-90ea-ea11-a815-000d3a86a3ce",
"OrganizationId": "9c5d5db0-47c7-4741-aa25-08eb4cdf59a3",
"OrganizationName": "orgad623a9e",
"OutputParameters": [],
"OwningExtension": {
"Id": "aa45df79-90ea-ea11-a815-000d3a86a3ce",
"KeyAttributes": [],
"LogicalName": "sdkmessageprocessingstep",
"Name": null,
"RowVersion": null
},
"ParentContext": null,
"PostEntityImages": [],
"PreEntityImages": [],
"PrimaryEntityId": "a2b1d88b-90ea-ea11-a815-000d3a86a3ce",
"PrimaryEntityName": "contact",
"RequestId": "bda24fe5-1f23-4c96-a4ab-34739a2f5628",
"SecondaryEntityName": "none",
"SharedVariables": [
{
"key": "IsAutoTransact",
"value": true
},
{
"key": "DefaultsAddedFlag",
"value": true
},
{
"key": "ChangedEntityTypes",
"value": [
{
"__type": "KeyValuePairOfstringstring:#System.Collections.Generic",
"key": "contact",
"value": "Update"
}
]
}
],
"Stage": 30,
"UserAzureActiveDirectoryObjectId": "00000000-0000-0000-0000-000000000000",
"UserId": "c164a5aa-765e-4181-8771-537e8b1ebf3b"
},
"PostEntityImages": [
{
"key": "AsynchronousStepPrimaryName",
"value": {
"Attributes": [
{
"key": "fullname",
"value": "John Doe"
},
{
"key": "contactid",
"value": "a2b1d88b-90ea-ea11-a815-000d3a86a3ce"
}
],
"EntityState": null,
"FormattedValues": [],
"Id": "a2b1d88b-90ea-ea11-a815-000d3a86a3ce",
"KeyAttributes": [],
"LogicalName": "contact",
"RelatedEntities": [],
"RowVersion": null
}
}
],
"PreEntityImages": [],
"PrimaryEntityId": "a2b1d88b-90ea-ea11-a815-000d3a86a3ce",
"PrimaryEntityName": "contact",
"RequestId": "bda24fe5-1f23-4c96-a4ab-34739a2f5628",
"SecondaryEntityName": "none",
"SharedVariables": [
{
"key": "IsAutoTransact",
"value": true
},
{
"key": "DefaultsAddedFlag",
"value": true
}
],
"Stage": 40,
"UserAzureActiveDirectoryObjectId": "00000000-0000-0000-0000-000000000000",
"UserId": "c164a5aa-765e-4181-8771-537e8b1ebf3b"
}
The main difference with events is how we consume them; that is, outside of the application, as opposed to inside. There are a variety of reasons why it may be desirable to do this:
- As we saw when discussing plug-ins, there are some particular limitations that sandbox processing can impose on your custom code - such as the restricted use of third party libraries and the 2-minute maximum execution time. However, by processing these operations external from Microsoft Dataverse, developers can circumvent these restrictions while still benefitting from working with the same type of information typically available as part of a standard plug-in.
- For situations where your environment is processing hundreds or even thousands of record changes each hour, that then need to be processed asynchronously, events provide the most streamlined mechanism for achieving this. Also, it helps to reduce the reliance on the platform and the applications Asynchronous service in performing complex processing of these requests; instead, they can be straightforwardly “passed on” to a dedicated service responsible for this.
- It may be necessary to immediately trigger an external endpoint or API as soon as a user creates, updates or deletes a table row. By using events alongside Webhooks (more on this shortly), developers have a streamlined mechanism to meet this requirement.
- With the recent changes announced around API request and service protection limits at the platform level, developers may struggle to use the traditional plug-in assembly route to process complex operations. As such, we can realise benefits by moving this processing outside of the application and, as part of this, avoid hitting any nasty error messages resulting from an overage in the number of processed platform requests.
In most cases, you will typically use one of several different Microsoft Azure services when processing events received from the application. However, there are options available to integrate alongside other cloud platforms or systems. For the exam, focusing and having an awareness of the Azure options will be essential.
Understanding Service Endpoints & the Azure Service Bus
There are two core concepts to understand alongside events - service endpoints and the Azure Service Bus:
- Service Endpoints: This defines the location you wish to write your events out to for further processing. In pretty much all cases, you will want to use the Plug-in Registration Tool to create your service endpoint, but you can also deploy one programmatically via the web API. You can also use a service endpoint to receive incoming requests back into Microsoft Dataverse for processing. Once defined, you must then register the appropriate steps that will trigger your endpoint request, much in the same way as defining a plug-in step (e.g. Update on Contact, Delete on Lead, etc.). An advantage with this is that we can include pre/post table images as part of each event payload. However, the critical thing to remember is that we must specify these as asynchronous operations; synchronous calls are not allowed. In most cases, your service endpoint will target an Azure Service Bus queue, but you can also configure an Azure Event Hub endpoint as well.
- Azure Service Bus: Depending on the type of integration you are attempting to perform, the synchronous (i.e. all at once) processing of information may not be needed or desirable. This could be down to the single fact that the number of potential requests to process will be too high. For when this is relevant, Azure Service Bus comes into the equation by offering a decoupled, asynchronous mechanism to receive, analyse and route multiple requests to an intended destination - whether this is a database, another endpoint or a storage location. The service bus will process all events it receives in order, holding onto each request for as long as is necessary before handing it off. As well as allowing for a predictable flow of information, it can also provide assurances from a failure standpoint; if, for example, the backend endpoint goes offline for whatever reason, messages will remain in the queue and resume processing as soon as the endpoint comes back online. Developers have flexibility over the type of Azure Service Bus listener service to implement, which Microsoft outlines in this article, but the most common scenario is to use a queue. We can also define the format of events that get written out by the Azure Service Bus listener but, in most cases, outputting this as JSON will be the recommended option.
Demo: Posting Microsoft Dataverse Events to Azure Service Bus
In this video, we’ll walk through how to deploy out an Azure Service Bus resource and configure a service endpoint to receive requests from Microsoft Dataverse:
Webhooks Overview
A webhook is a lightweight mechanism for integrating multiple Web API’s. It operates on a publish and subscribe model; namely, we publish an event out, and an external endpoint subscribes to each event, processing it as it sees fit. As semi-automated, standard HTTP requests in their simplest form, they conform broadly to this pattern and - if they are a new concept - you should experience little difficulty in understanding them if you’ve previously worked with RESTful Web API’s. Webhooks are commonplace these days, both in the Microsoft world and across other vendors as well. For example, Azure supports the ability to write out any log alert event as a webhook to an endpoint of your choosing.
From a Microsoft Dataverse perspective, developers can register custom webhooks to any endpoint of their choosing. We can handle authentication into these endpoints in one of four ways:
- HttpHeader: Here, we must declare the appropriate header key/value pair values that will allow us to authenticate into the endpoint. HttpHeader is the option you will need to go for if you use Basic or OAuth 2.0 authentication via an authorization bearer token.
- WebhookKey: For this option, the platform will append a query string parameter called code onto the URL, whose value then allows you to authenticate into the API. If your endpoint is an Azure Function, then this is a possible candidate option for you to consider using, as all requests targeting your function will expect this by default.
- HttpQueryString: Best suited for endpoints that support shared access signature (SAS) authentication, developers specify the appropriate key/value pairs for this, in the same manner as the HttpHeader option. The main difference here is that the platform will instead add these values onto the endpoint URL as query parameters.
- Anonymous: Finally, it is possible to call any endpoint that does not enforce authentication. To do this, ensure you select the HttpHeader authentication type and supply a single key/value pair containing anything you like; otherwise, you may get errors when registering the Webhook.
Then, similar to working with service endpoints, we define the appropriate steps and Pre/Post images that will trigger the webhook call. The resulting operation will then initiate an HTTP POST request to your endpoint, containing the event data illustrated in the example earlier.
An important question arises around the usage of Webhooks in comparison to the Azure Service Bus. Certainly, Webhooks are the most attractive option to consider if you are integrating alongside non-Azure based services, your expected volume of API calls are low, or you need your request to execute synchronously. However, they will ultimately only be as reliable as the endpoint that you are contacting. Also, for high volume requests where we can tolerate a delay in processing, Webhooks will likely fall over pretty quickly. When this occurs, the Service Endpoint and Azure Service Bus option represent your optimal choice instead.
For more information on how to work with Webhooks, consult this Microsoft Docs article, which also provides some examples for you to work through.
Demo: Registering & Consuming a Microsoft Dataverse Webhook from an Azure Function
A common mechanism to work with Webhooks is via an Azure Function. With this in mind, check out the below video, which will show you how to consume webhook events in this manner:
We’re on the home stretch now, with only one more topic to look at before we wrap up the series. Next time around, we’ll look at how you can enable some specific capabilities on your Microsoft Dataverse to support data synchronisation scenarios.