Maik Skoddow
Tera Patron
Tera Patron

MaikSkoddow_1-1715512704038.png

 

 

Idea

 

In one of my previous articles Caching Approaches for higher Performance I have described how to cache an information within a single transaction. I remembered that approach when thinking about a certain challenge regarding field styles for the sys_metadata_link table. That table holds artifacts that act as a container for pure data records to ship them along with an application (see documentation). This can be very useful, for example, if you want to ensure that a particular user group on which many implementation artifacts depend is always available. On the other hand, such metadata snapshots can also be problematic. Since there is no direct link to the original record with automatic synchronization, ghost records can occur that are repeatedly deployed on the target instance even though the original record has long since been deleted on the DEV. The original record may also have been updated in the meantime, but this does not result in the metadata snapshot being updated, and can therefore cause problems that are difficult to identify.

 

I wanted to use field styles in the list view of the sys_metadata_link table to give a better indication of the synchronization between the original record and the metadata snapshots created from it:

  • green when the metadata snapshot represents the most recent version of the original record,
  • yellow when the original record is more recent than the corresponding metadata snapshot record and
  • red if no original record exists for the metadata snapshot any longer (ghost record).

 

MaikSkoddow_0-1715491096316.png

 

 

 

Challenge

 

Field Styles in ServiceNow refer to the visual representation of fields on forms or list views within the platform's user interface. They are used to differentiating and highlighting specific types of fields based on their characteristics or values. Administrators can define conditional field styles to dynamically change the appearance of fields based on specified conditions. For example, a field can be highlighted in red if its value exceeds a certain threshold or displayed in a different color if it meets specific criteria. The desired visual effects, such as font color, background color, border styles, and text formatting can be achieved via CSS formatting. The problem is that you can only specify one CSS definition per field style configuration as the result of a matching condition. In my case with three different colors, three different field style configurations are required and each of them has to perform the comparison between the original data record and the metadata snapshot from the very beginning. From a performance point of view, however, it would be more elegant to perform the check only once, and then reuse the status determined for the new check in all other field-style calculations. However, this would require the status to be stored in a central location that can be accessed by all field styles - for example in the transaction cache, which can be written to and read using the GlideController API.

 

 

 

Solution

 

Since the transaction cache is a pure key-value store, not much is actually required to implement it: A simple Script Include with a single static method for checking the status would suffice. In this method, the cache would first be looked up using a Sys-ID-based key to determine if a comparison results already exists, and if not, it would be determined and written to the cache.

 

However, I had long wanted to write an article about the so-called singleton pattern and was just looking for a good use case, which has now arisen.

 

The singleton pattern is a creational design pattern used in object-oriented programming languages to ensure that a class has only one instance in the runtime environment and provides a global point of access to that instance.  Here's how it typically works:

  • The class implementing the singleton pattern typically has a private constructor, preventing external instantiation of the class.
  • Instead, the class contains a static member variable that holds the single instance of the class. This instance is typically created the first time it is requested and then reused for subsequent requests.
  • A static method is provided to access the single instance of the class. This method is responsible for creating the instance if it doesn't already exist and returning it to the caller. The instance creation can be lazily initialized, meaning it is created only when it is first requested. This approach conserves resources by deferring instantiation until necessary.

 

The singleton pattern provides a global access point to the single instance of the class, allowing clients to access it from anywhere in the program. It is commonly used in scenarios where there should be exactly one instance of a class, such as logging, configuration settings, database connections, and resource management.

 

Now let's take a look at the implementation details.

 

 

Field styles

 

The table sys_metadata_link contains three field styles - one for each of the colors.

 

The configuration for the yellow style looks as follows:

 

MaikSkoddow_0-1715509300545.png

 

The important part is the invocation of the singleton class MetadataSnapshot to retrieve an object instance for the current record, and then to check whether the original data record was updated after capturing.

 

 

Script Include MetadataSnapshot

 

The Script Include starts as follows:

 

var MetadataSnapshot = Class.create();
var _MetadataSnapshot = Class.create();

MetadataSnapshot.STATUS_UPTODATE     = 1;
MetadataSnapshot.STATUS_OUTDATED     = 2;
MetadataSnapshot.STATUS_NOTAVAILABLE = 3;

//prevent object instantiation via "new" operator
MetadataSnapshot.prototype = {
    initialize: function() {
		throw new SyntaxError('Creating instances of the "MetadataSnapshot" class is not allowed!');
    },

    type: 'MetadataSnapshot'
};

 

 

There are two interesting topics I want to highlight:

  1. Non-instantiable class
    Via throwing an error within the initialize() method (which acts as the constructor) we can prevent intantiating new objects
  2. Private class
    Within the same Script Include record another class _MetadataSnapshot with a leading underscore is defined (and later implemented) that cannot be instantiated via the new operator outside the Script Include. Therefore, it represents a real private class.

 

The most important part of the implementation - the getInstance() method, is realized as follows:

 

MetadataSnapshot.getInstance = function(grMetadataSnapshot) {

	var _strKey = 'grMetadataSnapshot' + grMetadataSnapshot.getUniqueValue();

	//check if there is already a cached version for the requested snaphot 
	if (GlideController.exists(_strKey)) {
		return GlideController.getGlobal(_strKey);
	}
	
	//create a new object
	var _objMetadataSnapshot = new _MetadataSnapshot(grMetadataSnapshot);

	//cache the object inside the transaction cache
	GlideController.putGlobal(_strKey, _objMetadataSnapshot);

	return _objMetadataSnapshot;
}

 

 

A key consisting of a prefix and the Sys ID of the MetadataSnapshot record is used to check whether an item already exists in the transaction cache. If so that item is returned. If not an object of the private class is instantiated, added to the cache and then returned.

 

That way only one object for a certain MetadataSnapshot record is created and reused for all field styles.

 

However what is done within the _MetadataSnapshot class?

 

 

_MetadataSnapshot.prototype = {
    initialize: function(grMetadataSnapshot) {
      this._grMetadataSnapshot = grMetadataSnapshot;
    },

  getStatus: function() {
    this._loadStatus();

    return this._status;
  },

  isOriginalRecordUpToDate: function() {
    this._loadStatus();

    return this._status === MetadataSnapshot.STATUS_UPTODATE;
  },

  isOriginalRecordUpdated: function() {
    this._loadStatus();

    return this._status === MetadataSnapshot.STATUS_OUTDATED;
  },

  isOriginalRecordNotAvailable: function() {
    this._loadStatus();

    return this._status === MetadataSnapshot.STATUS_NOTAVAILABLE;
  },

  _loadStatus: function() {
    if (typeof this._status === 'undefined') {
      var _grOriginalRecord = new GlideRecord(this._grMetadataSnapshot.tablename);

      if (_grOriginalRecord.get(this._grMetadataSnapshot.documentkey)) {
        var _objPayloadXml         = new global.XMLDocument(this._grMetadataSnapshot.payload);
        var _numCapturedModCounter = parseInt(_objPayloadXml.getNodeText('//sys_mod_count'), 10);
        var _numOriginalModCounter = parseInt(_grOriginalRecord.getValue('sys_mod_count'), 10);

        this._status = 
          _numCapturedModCounter === _numOriginalModCounter ?
            MetadataSnapshot.STATUS_UPTODATE :
            MetadataSnapshot.STATUS_OUTDATED;

        _objPayloadXml = null;
      }
      else {
        this._status = MetadataSnapshot.STATUS_NOTAVAILABLE;
      }

      _grOriginalRecord = null;
      this._grMetadataSnapshot = null;
    }
  },

    type: '_MetadataSnapshot'
};

 

 

The private method _loadStatus() contains the logic for loading the original data record and comparing with the payload stored within the MetadatSnapshot record.  

 

As the payload value within the MetadatSnapshot record is just unstructured text, we need to convert it to a structured XML document. That way, all values of the containing XML tags can be accessed.

 

And to avoid cumbersome date comparisons, I decided to compare the numbers in sys_mod_count with each other to find out whether the MetadatSnapshot record is outdated or not.

 

At the end of the method, the references in _objPayloadXml and _grOriginalRecord and _grMetadataSnapshot are nullified to help the garbage collector identify and purge unnecessary object instances. That way, the object stored in the cache is only holding minimal information (the integer value in _status).

Version history
Last update:
‎05-12-2024 04:39 AM
Updated by:
Contributors