Jake Gillespie
Mega Guru

In my last two posts we've reviewed Coding Best Practices as well as Client Script Best Practices in ServiceNow. Now I'd like to review some of the Best Practice tips for writing successful Server-side scripts. The ServiceNow platform is extremely flexible when it comes to enhancing functionality on the Server side. As an Administrator, you can add scripting to many system components, which drive the system processes under-the-hood. Some of the most common examples include Workflow Activities (such as dynamic approvals) and Reference Field filters (aka Reference Qualifiers), but there are actually many more. If you're not familiar with terms such as Business Rules, Script Includes or GlideRecord, I suggest reading about them first on the ServiceNow Documentation site before reading the rest of this article.

So where do the Server-side scripts execute and what language are they written in? Although the underlying code that the ServiceNow platform is built on is actually Java, most of the scripts we execute are actually written in JavaScript. ServiceNow uses a JavaScript engine called Rhino to interpret and execute such Server-side JavaScript. It is important to understand though that the JavaScript you execute on the Server is almost always not the same as JavaScript you execute on the Client. The syntax might be the same, but the Objects and Classes used will be quite different. For example, in my previous article we discussed GlideForm (g_form) and how it can be used to manipulate fields on a table form. On the Server, this API cannot be accessed at all as it is only available on the Client-side. On the Server-side, the GlideRecord Class is frequently used to manipulate field values in a table record. Another popular Server-side API is GlideSystem (or gs), and it includes many functions, which are not specific to table records but rather the current session and system configuration. Because these scripts are executed directly on the Server, they are very powerful and can seriously affect system performance and stability if implemented incorrectly. To avoid such issues, I recommend the following Best Practices:

Use Script Includes instead of Global Business Rules

If you define a function or Class in a Global Business Rule, this code will be loaded when the Business Rules execute on ANY and EVERY table in the system. This is usually highly undesirable since it adds additional processing load, which will affect the system performance. Script Includes can be used to serve the same purposes, however they are only loaded if their function call is detected. The most common example of this is a Reference Qualifier script. Since this script might only be needed on a couple of table fields in the system, it is far more efficient to have this script loaded as required instead of on EVERY table!

Wrap Business Rule scripts in functions

Because there are many Business Rules in the system, they are executed in a sequence (based on their Order value). When this happens, the contents of these scripts are compiled together into one big long sequence. If the same variable or function names are used in multiple Business Rules, they could conflict and change the flow of the program, causing unexpected behaviour. It is therefore recommended that scripts defined in Business Rules be wrapped into local functions. This protects variables from being changed unexpectedly. Local function names should also be reasonably specific to avoid conflicting with other Business Rule scripts. Note that this only affects older Business Rules prior to the introduction of the Immediately-Invoked Function Expression (IIFE) code stub. In recent releases, new Business Rules will automatically use the IIFE structure, thus negating the need to wrap code in functions.

Avoid updating other records in a Before Business Rule

Before Insert/Update Business Rules are nice because they allow us to modify the current (record) Object just before the Database operation is executed. However, you can hinder system performance if you are unnecessarily updating other records during this script. This is because an insert/update action on another table will likely invoke any applicable Business Rules configured on that table as well, and thus delay the current action being performed. If you need to trigger an action on another table record it is better to do this in an After or Async Business Rule, but knowing which one to use will depend on your circumstances. After Business Rules are triggered immediately after the current action has occurred (e.g. when a database record has been updated), and they still have access to the previous Object (as well as the changes() method etc.). Async Business Rules on the other hand, work like a once-off scheduled job, and are processed a short time after the current action has finished depending on system resources. For interactive sessions (e.g. User clicks a Save UI Action), the Before and After Business Rules will run before the User sees the Form reload. Long running Before/After Business Rules will increase this duration. Async Business Rules on the other hand, will not affect this duration as they are queued for the system to run in the background.

Use Conditions in Business Rules

When a database transaction occurs, all of the applicable Business Rules will be executed in the following sequence:

    • [User opens a List]
      • Query
    • [User opens a Form]
      • Display
    • [User inserts/updates/deletes a record]
      • Before
      • After
      • Async (scheduled)

Within each of the above groups, Business Rules are further sequenced by their Order number. Because it's possible (and likely) that multiple Business Rules (of each type) have been defined, the system will effectively combine all of them into one big long script for processing (where the order is determined by the Order field on each rule). If no Condition is configured on these rules, all of them will be executed every time. However, if you define a condition on the Condition field (or the Condition Builder), the system will first evaluate these conditions and only include the script contents/actions if the condition evaluates to true. This will improve system performance, as only the applicable Business Rules will be triggered. Note that the Condition field has a limited character length, so if your condition is quite long you will need to move the logic to a Script Include function and call it from the Condition field instead.

Use GlideAggregate instead of getRowCount()

At times we've all needed to check how many records were returned by a specific query, and we probably did this by using a function from the GlideRecord Class called getRowCount(). This will work however, under-the-hood it is not the most efficient means of obtaining such a count. This is because the GlideRecord Class will retrieve the values of each record as well as the number of records returned. It is therefore recommended that we use the GlideAggregate Class instead. This Class operates with some similar functions and parameters as the GlideRecord Class, however its purpose is not to retrieve all of the record data but simply to provide statistics based on the records returned by a query.

Avoid hardcoding values by using System Properties and Messages

As a Developer, it is very easy to hardcode values into scripts that we write. Often this might be to assign an approval or task to a specific group, or perhaps display an onscreen error message. In doing so we make it difficult to maintain and make changes to these values, since they could be buried deep within our code and changing them will often require an Update Set migration. In ServiceNow, a common practice is to hardcode sys_id values into scripts so our query will always find the same record, but this is against the ServiceNow Best Practice, and can easily be avoided by simply using a System Property. Why use a System Property you ask? Well if you store the sys_id in a System Property, an Administrator (or anyone with access to the specific System Property via Roles) can easily update the value later, and not necessarily via the Update Set migration process. System Properties allow us to categorise and describe the values we store in them. I recommend adding your System Properties to a Category (they can belong to more than one), and then adding a menu module (link) to display them. This will allow Administrators to easily find and maintain them. Likewise, if you need to provide specific error or information messages to the user, consider storing these in the Message table. These values can be easily retrieved on both the Client and Server using the standard APIs, and have the added advantage of being mapped against a specific language. This allows us to customise the messages for multi-language configurations. Remember, the key is to avoid hardcoding values, as it won't always be us making the changes, so please think about the next person working on your code!

In this article we've covered some of the principles behind Server Scripting Best Practices in ServiceNow. In my next article, we'll discuss GlideRecord Scripting Best Practices and how they apply specifically to ServiceNow. For further information on the topics covered in this article, please see the following pages:

NOTE: MY POSTINGS REFLECT MY OWN VIEWS AND DO NOT NECESSARILY REPRESENT THE VIEWS OF MY EMPLOYER.