Monday, June 10, 2019

Sitecore JSS - Real Time Personalization

Sitecore JSS is really powerfull framework which brings a lot of advantages to your websites. After a few days of digging into this technology, I fell in love with this headless approach which is great and I can see a bright future ahead of this. Once I’ve started playing with it, I noticed that only JSON data is coming from a server and we’re building whole HTML on client side. Based on that, I decided to put some challenges for myself to find a way how we can use it for a real time personalization - it means that content will be changed automatically once provided rule will be fulfilled and all will be done without any page reloading. Long story short - it is enough to get newest data from API and replace it in the store. Let’s take a look at what I found out.


Let’s start

In this article I’ll use JSS with Vue JS in connected mode and I’ll base my mechanism on RouteHandler.vue from JSS offitial github. As I am mainly back end developer, please forgive me any potenial lacks in JS code, and if you find something to improve, please let me know.


Monitoring changes

To get information when personalized content is changed, we have to keep updated information and refresh it from time to time. For this purpose we need to create worker which will make calls to get up to date route data using sitecore API. Also, to avoid unnecessary refereshing components that contain any changes, it would be good to check if old and new object values are deeply equal (Just to clarify, == and === will not fly here, because they compare instances of the objects). For this purpose I’ll use lodash.isequal.

...
import isEqual from 'lodash.isequal';
import EventBus from '../../Foundation/Events/EventBus';

export default {
  name: 'Route-Handler',
  data() {
    return {
        ...,
        interval: null,
        };
  },
  created(param) {
    ...
    this.enableWorker();
  },
  beforeDestroy() {
    ...
    this.disableWorker();
  },
  methods: {
    ...,
    updateComponents() {
      getRouteData(this.sitecoreRoutePath, this.language).then((routeData) => {
        if (routeData !== null && routeData.sitecore.route) {
            // here we're checking if route data returned from api is different than current one
          if (!isEqual(this.appState.routeData, routeData.sitecore.route)) {
            //in case if it is, we have to change data in actual jss store
            this.$jss.store.setSitecoreData(routeData);
          }
        }
      });
    },
    enableWorker() {
      if (!this.context.pageEditing && !this.interval) {
        this.interval = setInterval(() => this.updateComponents(), 5000);
      }
    },
    disableWorker() {
      if (!this.context.pageEditing && this.interval) {
        clearInterval(this.interval);
        this.interval = null;
      }
    },
  },
};

Refreshing components

Once we’ve updated route data object in JSS store, we can use Vue watchers to handle moment when we should make our component rendered again. Let’s imagine that I would like to rerender my section component with all children in case if any data within section or children is changed. For this purpose we need to watch rendering property and make a deep comparision between old one and the new one. In case if changes are detected, we’ll trigger code responsible for rerender Section component.

<template>
  <section :class="backgroundClass" :id="sectionId" v-if="renderComponent">
    <div class="container">
      <placeholder name="jss-section" :rendering="rendering" />
    </div>
  </section>
</template>

<script>
 import { Placeholder } from "@sitecore-jss/sitecore-jss-vue";
 import isEqual from "lodash.isequal";

 export default {
   data() {
  return {
    renderComponent: true
  };
   },
   props: {
  rendering: {
    type: Object
  }
   },
   components: {
  Placeholder
   },
   watch: {
  rendering(val, oldVal) {
    // here we're checking if route data returned from api is different than current one
    if (!isEqual(val, oldVal)) {
   this.refresh();
    }
  }
   },
   methods: {
  // renderers component
  refresh() {
    this.renderComponent = false;
    this.$nextTick(() => {
   this.renderComponent = true;
    });
  }
   }
 };
</script>

The nice thing about this solution is that we don’t render whole layout again, but only those places what are handled by our watchers and only those that were changed. Besides real time personalization, usage of this solution can be helpfull in many other situations like:

  • dynamically changed content/datasource without reloading page
  • handling content for singed in/signed off users
  • synchronizing page content opened in few tabs/windows

That’s how it works in practice

On our demo page, I’ve created Section component where I’ve put minor components. For one of them I’ve set personalization rule to use different datasource once specific timestamp will be reached. You will notice that component will be changed automatically without reloading the page. It is good to know that only components from one Section were rerendered, because only there data were changed.


Few words on the end

This soultion can be really helpfull, but we should be aware that traffic on the server will increase, because every active instance will made some requests in background to get up to date data. Once we want to use it on production site, we must observe statistics and choose right interval time for our worker or get rid of worker and make hooks for some events, but it is up to the situation.

Thank you for reading and I hope you enjoyed it.

Tuesday, April 16, 2019

Updating items in Sitecore indexes

Sometimes, when we work on indexes in Sitecore, it is not enough to use just out of the box fields that are stored in indexes. When we want to reduce or avoid DB calls during running a search query, we need to store more information in index. In such situations, it is a common thing to use computed fields. It helps us to add information to index in easily way. When we extend data stored in computed fields, it is more than sure that we’ll use some information from another Sitecore items. It is nothing wrong, because we want to speed up our search as much as it is possible, but in such situations we should be aware of potential issues.


What kind of danger can we face?

When computed field stores information from another Sitecore items, we need to be careful to keep data up to date in our indexes. Once we change something in the Sitecore item, based on index update strategy, index document for this one will be updated as well. In such situations we should also remember about updating index documents that are using changed item in computed fields. Otherwise we’ll keep depracated data in our index what can cause inconsistent results presented for end user.


Example of issue

Tree structure presented above shows the potential issue. We have particular types of items:
- Car store - location where the store is. It contains vary car brands inside.
- Car brand - item responsible for keeping data about brand.
- Car model - item what contains information about model and price.

Let’s imagine that we want to find all stores that offering cars within specified price range. Based on structure from example, price field exists only in car models items. In this situation we could go through all cars in index, choose those which contain proper price for our range and take the related car stores to results. It sounds good, but in case of large amount of items, it can take some time.


To improve it, we can prepare a simple computed field to keep price range for each car store item. Our custom computed field will check all cars within car store and select min and max values. Then our search will go only through store items instead of all cars that we have. For this purpose we’re creating computed field to keeping price range and we’re rebuilding index. For now it looks good, because our index was rebuilt recently. Please notice that once we update single car price in Sitecore, index document for this item will be updated with a new value, but store will still keep the old price range in its index document. To avoid this issue we can create custom indexing strategy, but it would be pretty big piece of code. Let’s see some simple tricks that you will be able to use in similar situation.


Updating Index Item

To make things simpler it will be fine to handle some sitecore events like item:saved or publish:end and update indexes only for particular items.

Inside an event handler, we should refresh index documents related to changed one. After finding out all required items, let’s update them by using one of the proposed snippets of code:

public static void Update(string indexName, ICollection<Item> items)
{
    var index = ContentSearchManager.GetIndex(indexName);
    foreach (var item in items)
    {
        var uniqueId = new SitecoreItemUniqueId(item.Uri);
        IndexCustodian.UpdateItem(index, uniqueId);
    }
}

Code presented above will update index documents for items passed in parameters. It will work in asynchronous way, because IndexCustodian creates updating process as Sitecore job and puts it in the job queue. I would recommend this way instead of synchronous way. Because if we would use it in Sitecore event handlers, we have to be aware that they work in synchronous way too, so in case a large amount of items, it can freeze our instance for a while.

But if you still want to do it in synchronous way, example presented below shows how to achieve it without IndexCustodian:

public static void Update(string indexName, ICollection<Item> items)
{
    var index = ContentSearchManager.GetIndex(indexName);
    foreach (var item in items)
    {
        var uniqueId = new SitecoreItemUniqueId(item.Uri);
        index.Update(uniqueId);
    }
}


Refreshing Index Item

Beyond index updating, we are able also to refresh index item. Main difference is that refreshing process updates indexes for pointed item and its descendants. Please take a look at the code below:

public static void Refresh(string indexName, Item item)
{
    var index = ContentSearchManager.GetIndex(indexName);
    var indexableItem = (SitecoreIndexableItem) item;
    IndexCustodian.Refresh(index, indexableItem);
}

In the code presented above we used IndexCustodian like in updating example, so this process will be handled asynchronously.

If we want do it in synchronous way, just take a look at the following example:

public static void Refresh(string indexName, Item item)
{
    var index = ContentSearchManager.GetIndex(indexName);
    var indexableItem = (SitecoreIndexableItem)item;
    index.Refresh(indexableItem);
}


Conclusion

Computed fields are really helpful in scope of indexes customising. They bring us possibilities to store additional information to speed up search and other features like faceting, pagination and so on. It is important to use them wisely by remembering about all relations to other items and keeping index documents up to date. For this purpose we could rebuild index, but it will cost too much time and effort in case of single item changes. The better solution is updating or refreshing single index items, but we need to be aware of differences between those two processes and use them in proper situations.

Thank you for reading this post, I hope it’ll help some of you.