Managing Instance-Specific System Properties for Dev/Test/Prod in ServiceNow

While watching this video by the venerable Jace Benson, I was inspired by an idea presented around the 4-minute mark, which he picked up from Maik Skoddow's article, here: a more effective way to manage instance-specific System Properties. So inspired was I, that I decided to not only adopt his suggestion, but to write a little tool to handle it for me as well! I call this tool "Local Properties". 'Local', here meaning "instance-specific"; properties which should have one value in one instance (such as your dev environment), and a different value in a different instance (such as production).

As many developers will know all too well, this is a problem that we face often: How do you use one property value in one instance and another value in another instance, without risking poisoning the data in the higher instance just by modifying the value in a lower instance (and perhaps not even realizing that it's been captured in your update set)?

In this article, we're going to discuss the idea that Jace presented to solve this problem in a robust and elegant way, as well as a free tool that I've built to handle this for you!

The idea presented in Jace's video is to use a specific naming convention for "local" System Properties wherein you prefix your instance-specific (“local”) system properties with the exact name of the instance to which they belong.
For example, if you have a system property like “my_rest_endpoint”, you would actually create a separate version of that property for each of your ServiceNow instances. Say you have three instances: Dev (acme-dev), Test (acme-test), and Prod (acme). In this case, you would create three separate versions of this system property like so:

  • acme-dev.my_rest_endpoint
  • acme-test.my_rest_endpoint
  • acme.my_rest_endpoint


This sounds simple enough -- but it still requires your code to "know" which instance it's in, in order to retrieve the correct property. Luckily, there's another system property in every ServiceNow instance called instance_name which we can use to make our code fancy and dynamic; and that's what I leveraged in order to build a little Script Include to handle this for us automatically!

All you have to do to use this functionality is copy the below code into a new Script Include in your instance. The Script Include should have the name LocalProperty, and should be accessible from all scopes.

View Script Include code (expand)
/**
* Local (instance-specific) System Property Script Include
* More info available at https://localprop.snc.guru
*
* A ServiceNow JavaScript ES5 class, LocalProperty, for getting and setting instance-specific
* system properties.
* See method documentation for more information.
* @param {string} [instanceNameOverride=] - Override the name of the instance that's currently
* executing this code.
* For example, if you have a system property such as "my_prod_instance.some_property" which
* you need to specifically retrieve while executing code in your development environment
* for some reason, you would call this constructor like so: <br />
*
* var localPropUtil = new LocalProperty('my_prod_instance');
* //...
*
* This is most commonly useful for testing, but this argument should generally NOT be specified.
*
* @constructor
*/
function LocalProperty(instanceNameOverride) {
//If the instance name override is not provided, use the instance name system property.
var instanceNamePropVal = gs.getProperty('instance_name', '');
//Set the local-scope instance_name property for later use, depending on whether the override argument has been specified.
this.instance_name = (typeof instanceNameOverride === 'undefined') ?
(instanceNamePropVal) : (instanceNameOverride);
//If the instance name is empty, throw an error.
//This should never happen, and would indicate that something has gone horribly wrong.
if (!this.instance_name) {
throw new Error(
'Whoa there! The "instance_name" system property was not found in the sys_properties ' +
'table. Please set the instance_name property. This is very important, not just ' +
'for this specific error!'
);
}
/* Methods below */
/**
* Get the value of an instance-specific ("local") system property.
* Local system properties should follow the pattern <instance_name>.<property_name>. For
* example, if your instance is named "my_instance" and you want to retrieve the value of a
* property named "my_http_endpoint", you would need to have a system property with the
* name "my_instance.my_http_endpoint", and call this method with the propVal argument
* "my_http_endpoint".
*
* @param {string} propName - The name suffix of the system property to retrieve.
* This should be the portion of the system property name that comes after the instance name.
* For example, if your instance name is "dev1234", and you want to retrieve the value of a
* system property named "dev1234.my_http_endpoint", you would pass "my_http_endpoint" as
* the propName argument and not include the instance name in this argument.
* If there is no system property with the name <instance_name>.<propName>, then the default value you specify in the defaultVal argument is returned. If you do not specify a default value, then an empty string is returned instead.
* The instance name is retrieved automatically from the instance_name system property, but can be overridden when calling the constructor for this class.
*
* @param {string} [defaultVal=''] - The default value to return if the system property
* is not found.
*
* @returns {string} - The value of the system property with the name
* <instance_name>.<propName>, or the default value if the system property is not found.
*
* @example Get the value of a local system property "my_instance.my_http_endpoint":
* var propName = 'my_http_endpoint';
* var localProp = new LocalProperty();
* var propVal = localProp.getProperty(propName);
*
* @example Get the value of a local system property "my_instance.my_http_endpoint", but with a default value of "http://localhost:8080":
* var propName = 'my_http_endpoint';
* var defaultVal = 'http://localhost:8080';
* var localProp = new LocalProperty();
* var propVal = localProp.getProperty(propName, defaultVal);
*
* @example Get the value of a local system property "my_other_instance.my_http_endpoint", but with a default value of "http://localhost:8080", and override the instance name (in this case, to "my_other_instance") - which will cause the system property "my_other_instance.my_http_endpoint" to be retrieved no matter which instance this code is executed in:
* var propName = 'my_http_endpoint';
* var defaultVal = 'http://localhost:8080';
* var instanceNameOverride = 'my_other_instance';
* var localProp = new LocalProperty(instanceNameOverride);
* var propVal = localProp.getProperty(propName, defaultVal);
*/
this.getProperty = function(propName, defaultVal) {
var propVal;
var fullPropName = this.instance_name + '.' + propName;
//Set the default value ('') of the defaultVal argument if it is not provided.
defaultVal = (typeof defaultVal === 'undefined') ? ('') : (defaultVal);
//If the propName argument is not provided, throw an error.
if (!propName) {
throw new Error(
'Whoa there! The "propName" argument was not provided when attempting to get a ' +
'property with the LocalProperty class. You must provide a value for the propName ' +
'argument.'
);
}
//Get the value of the system property with the naming pattern <instance_name>.<propName>.
propVal = gs.getProperty(
fullPropName,
defaultVal
);
gs.debug(
'Got the value "' + propVal + '" from system property with name "' +
fullPropName + '".'
);
return propVal;
};
/**
* Set the value of an instance-specific ("local") system property to the specified new value.
* Local system properties should follow the pattern <instance_name>.<property_name>. For
* example, if your instance is named "my_instance" and you want to set the value of a
* property named "my_http_endpoint", you would need to have a system property with the
* name "my_instance.my_http_endpoint", and call this method with the propVal argument
* "my_http_endpoint".
*
* Note: Care should be taken when setting system properties (sys_properties), as it causes
* a system-wide cache flush. Each flush can cause system degradation while the caches
* rebuild. If a value must be updated often, it should not be stored as a system property.
* In general, you should only place values in the sys_properties table that do not
* frequently change.
*
* Note: Just as with using the OOB gs.setProperty() method, if the specified system property does not exist, then it will be created. If it is created, it will always be created as a STRING type -- regardless of what data-type is passed into it. Therefore,
*
* @param {string} propName - The name suffix of the system property to set.
* This should be the portion of the system property name that comes after the instance name.
* For example, if your instance name is "dev1234", and you want to set the value of a
* system property named "dev1234.my_http_endpoint", you would pass "my_http_endpoint" as
* the propName argument and not include the instance name in this argument.
* If there is no system property with the name <instance_name>.<propName>, then the default
* value you specify in the defaultVal argument is returned. If you do not specify a default
* value, then an empty string is returned instead.
* The instance name is retrieved automatically from the instance_name system property, but
* can be overridden when calling the constructor for this class.
*
* @param {string} [newVal=''] - The new value to which the specified system property should
* be set.
* If this is not specified, then the system property will be set to a blank string.
* If the system property does not exist, then it will be created.
*
* @param {string} [description=] - A description of the property.
* This can normally be left blank unless you're intending to create a new system property,
* but is not mandatory in either case.
*
* @returns {string} the new value to which the specified system property has been set.
*
* @example Set the value of a local system property "my_instance.my_http_endpoint" to the new value http://localhost:8080 (if the property doesn't exist, it will be created):
* var propName = 'my_http_endpoint';
* var newPropVal = 'http://localhost:8080';
* var localProp = new LocalProperty();
*
* localProp.setProperty(propName, newPropVal);
*
* @example Set the value of a local system property "my_other_instance.my_http_endpoint" to "http://localhost:8080", but override the instance name (in this case, to "my_other_instance") - which will cause the system property "my_other_instance.my_http_endpoint" to be set no matter which instance this code is executed in:
* var propName = 'my_http_endpoint';
* var newPropVal = 'http://localhost:8080';
* var instanceNameOverride = 'my_other_instance';
* var localProp = new LocalProperty(instanceNameOverride);
* localProp.setProperty(propName, defaultVal);
*/
this.setProperty = function(propName, newVal, description) {
var fullPropName;
if (!propName) {
throw new Error(
'Whoa there! The "propName" argument was not provided when attempting to set a ' +
'property with the LocalProperty class. You must provide a value for the ' +
'propName argument.'
);
}
fullPropName = this.instance_name + '.' + propName;
if (typeof newVal === 'undefined') {
gs.warn(
'The "newVal" argument was not provided when attempting to set the value ' +
'of the property "' + fullPropName + '" with the LocalProperty class. The ' +
'property will be set to an empty string. \n' +
'It is best to explicitly pass an empty string to this method if that is the ' +
'desired outcome.'
);
newVal = '';
}
gs.debug(
'Setting the system property "' + fullPropName + '" to the new value "' +
newVal + '".'
);
if (description) {
gs.setProperty(
fullPropName,
newVal,
description
);
return newVal;
}
gs.setProperty(
fullPropName,
newVal
);
return newVal;
};
}


This above Script Include exposes two 'methods' (functions): .getProperty() and .setProperty(). These behave effectively identically to ServiceNow's own gs.getProperty() and gs.setProperty() APIs, except that it will prepend the system property name you specify with the instance name!

The Script Include above makes heavy use of JSDoc to document the methods - arguments that they accept, what they return, and example usage - but if you're having trouble picturing what I'm talking about, here are some examples of how you might interact with these methods in your own code:

Get the value of a local system property
Retrieve the value of the local system property named “my_instance.my_http_endpoint (where my_instance is the name of whatever instance the code is executing in, such as acme-dev)”.

var propName = 'my_http_endpoint';  
var localProp = new LocalProperty();  
var propVal = localProp.getProperty(propName);

Get local system property value (with default value if it doesn't exist)
Retrieve the value of the “my_instance.my_http_endpoint” system property, but specify a default value of “http://localhost:8080” to be returned if the property doesn't exist.

var propName = 'my_http_endpoint';  
var defaultVal = 'http://localhost:8080';  
var localProp = new LocalProperty();  
var propVal = localProp.getProperty(propName, defaultVal);

Get the value for a given system property, in a different instance ('my_other_instance')
Retrieve the value of the "my_http_endpoint" system property, as if you were in another instance (in this case, the "my_other_instance" instance).

var propName = 'my_http_endpoint';  
var defaultVal = 'http://localhost:8080';  
var instanceNameOverride = 'my_other_instance';  
var localProp = new LocalProperty(instanceNameOverride);  
var propVal = localProp.getProperty(propName, defaultVal);

Set the value of a local system property

var propName = 'my_http_endpoint';
var newPropVal = 'http://localhost:8080';
var localProp = new LocalProperty();
localProp.setProperty(propName, newPropVal);

Set the value of the local system property for a specific instance
Set the value of a given system property for a specific instance; not necessarily the instance in which the code is running.

var propName = 'my_http_endpoint';
var newPropVal = 'http://localhost:8080';
var instanceNameOverride = 'my_other_instance';
var localProp = new LocalProperty(instanceNameOverride);
localProp.setProperty(propName, defaultVal);

That's all there is to it!

After adding this Script Include to your instance, you should be able to get and set local (instance-specific) System Properties using the pattern new LocalProperty().getProperty('my_prop', 'default value here'); or new LocalProperty().setProperty('my_prop', 'new_val');.

Thanks for reading! If you find my content useful, please consider checking out my latest book: The ServiceNow Development Handbook, check out some of my other articles below, subscribe to this blog and if you like, connect with me on LinkedIn.
If you like Jace's content as much as I do, consider checking out his website and YouTube channel!



Share