Maik Skoddow
Tera Patron
Tera Patron

macro-1452987_1280.jpg

 

Table of Contents

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:

  • dttdev1
  • dtttest1
  • dttprod

 

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'));

 

 

 

⚠️ 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.

 

Comments
surajp
Mega Guru

Hi @Maik Skoddow , 

This is good approach.

One other approach is also seen in many OOTB script include is to store the config data within class as object property, it can be extended by other script includes. This will avoid database queries. Only drawback I think is changing or maintaining the config will be not easy for people not familiar with code.

 

 

 

 

Maik Skoddow
Tera Patron
Tera Patron

Hi @surajp 

in my opinion this is not a good idea. The main reason is that you have to change code for changed configurations and thus requiring a new app deployment. Every professional devleoper/architec will try everything to prevent that.

And a second reason against it is that all configurations are scattered throughout the platform and thus the overview is lost.

You cannot control what ServiceNow does, however in your applications/projects you should demonstrate that it can be done in a better way.

gaurav_thakur
Tera Explorer

Good one!

Jace Benson
Mega Sage

This is so close to my favorite way to deal with this.

 

I don't like recreating logic so making a helper function sounds great until you need to get everyone to use them.  Now you have to thing "am I using gs.getProperty or getConfigValue?"  I know i know, you're is better but i just think it's another thing to juggle when coming up with a solution or debugging an existing one.

 

I'd be curious @Maik Skoddow what your opinion is of just building the property name when you need to call it.

 

so instead of the property name of 'acme.config' you need to just name it 'acme.config.<instance>' 

When calling the property just always write it out as gs.getProperty('acme.config.' + instanceName)?

 

In any case, love the post thanks for sharing!

 

Maik Skoddow
Tera Patron
Tera Patron

Hi @Jace Benson 

 

sorry for the late answer, I'm quite busy at the moment.

 

I'm not sure, whether I got your point, but as given in my example you only have to invoke

getConfigValue('acme.config', 'contact.name')

 

Within the method, a second system property is loaded automatically with the name:

acme.config.<instance_name>

 

And its values are merged with the values from the original property with the name

acme.config

 

That way you have two trade-offs:

  1. Aggregating many keys & values into one system properties with the help of a JSON-based structure
  2. Differentiating between instances for individual keys 
praestegaard
Tera Contributor

Hi @Maik Skoddow 

 

I have been thought-processing your content for some time, as a potential foundation for a sub-prod group assignment logic. Like all your other inspiring contributions it has been a good kickstart towards designing a solid an reliable solution to this need.

 

I agree with @Jace Benson that the likelyhood that developers remember to adhere to this approach (and numerous other smart approaches that we might invent) is questionable. Potentially we are creating a subtle technical dept.

 

My question:

When developing this approach, did you take the Private (is_private) field into consideration?

This field also allows for specifying per-instance values (albeit not allowing you to inspect all instance specific values form every instance) and would not require a script include and special calls potentially reducing the technical dept.

 

Thank you!

Anders

Version history
Last update:
‎06-26-2023 09:04 PM
Updated by:
Contributors