Business rules best practices

This article is based on the ServiceNow documentation article. See the original article on the ServiceNow doc site: Business Rules Technical Best Practices Overview

A business rule is JavaScript code which run when a record is displayed, inserted, updated, or deleted, or when a table is queried. Follow these guidelines to ensure that business rules work efficiently and to prevent unpredictable results and performance issues.

Quality Clouds automatically checks that all the best practices defined below are followed in any Business Rule which you create or modify on your ServiceNow instance. Below is a snapshot from the Quality Clouds Best Practice analysis dashboard which shows the issues detected in Business Rules on a ServiceNow instance.

Know when to run business rules

The When field on the business rule form indicates whether the business rule script runs before or after the current object is saved to the database. The most commonly used business rules are before and after rules. 

You can use an async business rule in place of an after business rule. Async business rules are similar to after rules in that they run after the database commits a change. Unlike after rules, async rules run in the background simultaneously with other processes. Async business rules allow ServiceNow to return control to the user sooner but may take longer to update related objects. 

Follow these guidelines to determine which value to choose for the When field.

ValueUse case
displayUse to provide client-side scripts access to server-side data.
beforeUse to update information on the current object. For example, a business rule containing current.state=3; would set the State field on the current record to the state with a value of 3.
afterUse to update information on related objects that need to be displayed immediately, such as GlideRecord queries.
asyncUse to update information on related objects that do not need to be displayed immediately, such as calculating metrics and SLAs.

Use conditions in business rules

Since business rules are evaluated whenever an insert, update, delete or query action is made to a record, it is important to ensure you are using conditions. Conditions are evaluated before the rule is executed, if the condition is met, the script is evaluated and executed. If there is no condition, the system assumes that the business rule should be evaluated and executed for every action to a record on the specified table of the business rule. It is easier to debug business rules when you can see which one meet a particular condition and which do not.

→ In the business rule form, use the Filter Conditions field in the When to Run section to specify the condition.

→ In the advanced business rule form, use the Advanced section to specify the condition. Specify the condition using the Condition field.

Keep code in functions

By default, an advanced business rule will wrap your code in a function, and it is important that this guideline is followed. When code is not enclosed in a function, variables and other objects are available to all other server-side scripts. This availability can lead to unexpected consequences that are difficult to troubleshoot. Consider this example:

var gr = new GlideRecord('incident');

gr.addQuery('active', true);
gr.query();

while (gr.next()) {
   // do some processing here
}


Because the gr object is not enclosed in a function, all server-side scripts, including script includes and other business rules, have access to it. Other scripts may also use the common GlideRecord variable name gr. The duplicate names can conflict and lead to unexpected results. These issues are very difficult to isolate and resolve because typical debugging methods identify the business rule giving erroneous results rather than the business rule containing global variables. To avoid this issue, keep the default function with the same code snippet:

(function executeRule(current, previous /*null when async*/) {
    var grInc = new GlideRecord('incident');
 
    grInc.addQuery('active', true);
    grInc.query();
 
    while (grInc.next()) {
        // do some processing here
    }
})(current, previous);

This solution is safer because the scope of the variable grInc is limited to the Business Rule since it is wrapped in the default function. If every script was wrapped, it would not matter what the variables were called. But it is good defensive coding to minimize the possibility of collisions even further, and avoid using a variable called gr at all. This makes the possibility of namespace conflicts even more remote.

Prevent recursive business rules

Do not use current.update() in a business rule script. The update() method triggers business rules to run on the same table for insert and update operations, potentially leading to a business rule calling itself over and over. Changes made in before business rules are automatically saved when all before business rules are complete, and after business rules are best used for updating related, not current, objects. When a recursive business rule is detected, ServiceNow stops it and logs the error in the system log. However, this behavior may cause system performance issues and is never necessary.

You can prevent recursive business rules by using the setWorkflow() method with the false parameter, current.setWorkFlow(false). This will stop business rules and other related functions from running on this database access. The combination of the update() and setWorkflow() methods is only recommended in special circumstances where the normal before and after guidelines mentioned above do not meet your requirements.

Use business rules to double-check critical input

If you use a Client Script to validate data or a reference qualifier to present a filtered list, data may change between the time the user fills in the fields and the time the user submits the form. This mismatch can cause a conflict or error. business rules are an effective way to double-check critical input.

For example, a request allows users to reserve items. When users fill out the request form, they can only select currently available items. If two people fill out a business rule form at once and select the same item, the item appears to be available to both people because neither of them has submitted the form yet. If there is no business rule to double-check availability, ServiceNow assigns the same item to both requests. By using a business rule to re-verify item availability when the form is submitted, the second person receives a warning or other notification indicating the item has already been taken.

In this example, the Condition is: current.cmdb_ci.changes()

The script is:

(function executeRule(current, previous /*null when async*/) {

/************************
 *
 * Double-check to ensure that no one else has selected and
 * submitted a request for the same configuration item (CI)
 *
 ************************/
doubleCheckCiAvailability();

function doubleCheckCiAvailability() {

    var lu = new LoanerUtils();

    if (!lu.isAvailable(current.cmdb_ci, current.start_date, current.end_date)) {
        gs.addErrorMessage(gs.getMessage('Sorry, that item has already been allocated'));
        current.cmdb_ci = 'NULL';
    }
}

})(current, previous);


Use script includes instead of global business rules

A global business rule is any business rule where the selected Table is Global. Any other script can call global business rules. Global business rules have no condition or table restrictions and load on every page in the system. Most functions defined in global business rules are fairly specific, such as an advanced reference qualifier on one field of one form. There is no benefit to loading this kind of script on every page.

Script includes only load when called. If you have already written a global business rule, move the function definition to a Script Include. The name of the Script Include must match the name of the function for the Script Include to work properly. There is no need to modify any calls to the named function.

Consider this global Business Rule:

function BackfillAssignmentGroup() {
		var gp = ' ';
		var a = current.assigned_to;
 
		//return everything if the assigned_to value is empty
		if(!a)
			return;
		//sys_user_grmember has the user to group relationship
		var grp = new GlideRecord('sys_user_grmember');
		grp.addQuery('user',a);
		grp.query();
		while(grp.next()) {
			if (gp.length > 0) {
				//build a comma separated string of groups if there is more than one
				gp += (',' + grp.group);
			}
			else {
				gp = grp.group;
			}
		}
		// return Groups where assigned to is in those groups we use IN for lists
		return 'sys_idIN' + gp;
	}

This Script Include is a better alternative:

var BackfillAssignmentGroup = Class.create();
BackfillAssignmentGroup.prototype = {
	initialize: function() {
	},
 
	BackfillAssignmentGroup:function() {
		var gp = ' ';
		var a = current.assigned_to;
 
		//return everything if the assigned_to value is empty
		if(!a)
			return;
		//sys_user_grmember has the user to group relationship
		var grp = new GlideRecord('sys_user_grmember');
		grp.addQuery('user',a);
		grp.query();
		while(grp.next()) {
			if (gp.length > 0) {
				//build a comma separated string of groups if there is more than one
				gp += (',' + grp.group);
			}
			else {
				gp = grp.group;
			}
		}
		// return Groups where assigned to is in those groups we use IN for lists
		return 'sys_idIN' + gp;
	},
	type: 'BackfillAssignmentGroup'
}

To call the function, use a script like this:

var ref = new BackfillAssignmentGroup().BackfillAssignmentGroup();


Use glideAggregate instead of glideRecord to count the records in a table 

The GlideAggregate class is an extension of GlideRecord and allows database aggregation (COUNT, SUM, MIN, MAX, AVG) queries to be done. This is the most efficient way to obtain the total number of records on a table. Filters can also be added to the glideAggregate query, as is done with GlideRecord. Running a GlideRecord query (filtered or unfiltered) with the sole purpose of using the getRowCount property to count the number of returned rows is inefficient. Glide Aggregate should be used instead.




Last modified on Jun 23, 2020