logo

NJP

Maintain instance-specific configurations

Import · Jun 24, 2023 · article

macro-1452987_1280.jpg

Table of Contents
Challenge Solution Enhancements

As a former software architect for Java-based applications, I was used to maintaining application properties in individual properties files. And it was quite common to have separate properties files for different environments, in order to cope with environment-specific peculiarities in this way. These can be, for example, simple switches that (de)activate a certain feature or URLs to the various peripheral systems that need to be connected. The goal was to eliminate all conditional code blocks regarding an environment and to obtain the desired values via simple "getProperty()" method, that know which properties file has to be loaded accordingly.

Challenge

However, in ServiceNow I miss a corresponding approach, although each customer usually has at least one DEV, one TEST and one PROD instance and thus with high probability instance-specific values have to be maintained.

I'm sure you know the table sys_properties in ServiceNow and the respective method gs.getProperty() to get the value of a certain system property. It's the perfect place to store all the values for any app configurations.

But there are also two key drawbacks with this approach:

  1. If you have a lot of system properties to maintain, you can quickly lose track of them - especially if you don't have strict naming rules and and each developer gives free rein to his creativity.
  2. OOTB ServiceNow does not provide a way to differentiate and load system properties depending on the environment the code is executed in.

Solution

Combining all properties into a single JSON structure

Instead of having dozens of individual system properties, I prefer combining them within a single system property. The perfect data structure to store all these values is JSON. That means you have to store within the "Value" field a stringified version of a JavaScript object, for example:

MaikSkoddow_0-1687611066628.png

Separating instance-specific values in individual system properties

There is one OOTB system property "instance_name" which holds the unique name of a ServiceNow instance. It may or may not correspond to the subdomain name in the ServiceNow cloud. For example, for PDIs, the instance name can be "dev123456".

For the instances of my current customer project the on-premise provider has assigned the following independent & standalone names:

In the end, it doesn't matter what those names look like. The only important thing is that they can be used to distinguish the individual instances.

Now comes the crucial part: In order to be able to define independent values for certain properties of individual instances, a new system property is created with the same name as the base system property, but extended by the instance name.

For example, if the base system property for storing values that apply for all instances is "acme.config" the system property for the DEV instance could be (related to my above instance names) "acme.config.dttdev1".

In that new system property you also have to store a JSON-based data structure either with the same keys as in the base system property in case you want to override them or with individual keys which are only available for the DEV instance.

For a better understanding, please refer to the below example

system property "acme.config" system property"acme.config.dttdev1" resulting value on DEV resulting value on TEST
{ "contact.name": "Jane Doe" } { "contact.name": "John Smith" } John Smith Jane Doe

Now we only need a helper method which realizes the above example and is able to:

  • identify the current environment via system property "instance_name"
  • load the instance-specific system property of the given name (if available), convert its JSON data into an object and return the value if it contains the given key
  • load the base system property of the given name (if available), convert its JSON data into an object and return the value if it contains the given key

A simple but working solution could like this:

function getConfigValue(strSystemProperty, strKey) {

  if (typeof strSystemProperty !== 'string' || strSystemProperty.length === 0) {
    return '';
  }

  if (typeof strKey !== 'string' || strKey.length === 0) {
    return '';
  }

  var _objBaseData = {};
  var _objInstanceData = {};
  var _strInstanceName = gs.getProperty('instance_name');
  var _strBaseData = gs.getProperty(strSystemProperty, '');
  var _strInstanceData = gs.getProperty(strSystemProperty + '.' + _strInstanceName, '');

  if (_strBaseData.length > 0) {
    try {
      _objBaseData = JSON.parse(_strBaseData);
    } 
     catch (e) {
      gs.error(e);
    }
  }

  if (_strInstanceData.length > 0) {
    try {
      _objInstanceData = JSON.parse(_strInstanceData);
    } 
     catch (e) {
      gs.error(e);
    }
  }

  if (_objInstanceData[strKey]) {
    return _objInstanceData[strKey];
  }

  if (_objBaseData[strKey]) {
    return _objBaseData[strKey];
  }

  return '';
}

gs.info(getConfigValue('acme.config', 'contact.name'));

:warning: In real projects, you should store the above method in a utility Script Include.

Enhancements

Extending the system properties types

There are many different types for system properties, but unfortunately none for storing JSON-based values. By extending the choices of the "Type" field (table sys_properties) by a new value "JSON" you can react to this type accordingly. For example, with the help of an additional Business Rule, you can validate the value and prevent storing the system property in case the value does not represent a valid JSON structure.

Caching the objects within the user session

Basically ServiceNow caches all system properties in the memory so that the database does not have to be queried every time gs.getProperty() is called. However, parsing the string-based value into a JSON object must be done each time. If the data is very large and the getConfigValue() method is also called very often, a small performance improvement could be to store the JSON object in the user session during the first invocation and load it from the user session for all following invocations.

View original source

https://www.servicenow.com/community/developer-articles/maintain-instance-specific-configurations/ta-p/2597305