Updating records with AFTER business rules
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
‎09-15-2020 04:12 AM
Hi, all
For a while now, I have been going through some of my company's SN configuration and coding to review some of the practices that have been in place up to now. This has been particularly relevant because of some performance issues overall and ''interesting'' bugs coming up from time to time.
So one thing I've found, the team seems to be using current.update() call in a LOT of business rules across the system, but in particular for some tables. I know that this has been covered in many places and topics already and is considered very bad practice - https://hi.service-now.com/kb_view.do?sysparm_article=KB0715782 or https://community.servicenow.com/community?id=community_blog&sys_id=f12d66e5dbd0dbc01dcaf3231f961988
Now, for the before business rule, everything is clear and that is a childish mistake that we can easily fix. My dilemma is with the after business rules, what would be the correct way of substituting current functionality and allow us to avoid the recursive calls that hinder performance and is very bad practice? I am also aware of setWorkflow(false) function. I would like to know how to get rid of these for good, not putting in the workflow function, since that is supposed to be last measure in exceptional cases anyway.
So let's model a situation - we have a table of records, and some BR associated with it. When the record is updated or inserted, it triggers the BR, which changes, updates some fields of that current record. This can only be done after the DB action, though, because we want to make sure the initial change has been allowed and successfully posted to the DB.
For example - Document A is created, document approver field is filled in and you click on submit button. This would trigger a business rule after the insert/update is made and set the document stage from ''draft'' to ''in progress''. How would you go about this? Would appreciate if you could also shed some light on async type BR, when that would be best applied.
- Labels:
-
Script Debugger
-
Scripting and Coding
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
‎09-15-2020 05:00 AM
Thanks, the situation could be improved a lot by using correct conditions on the BR, I agree. But I am still confused by SN saying that current.update() should NEVER be used in any business rules. Just wondering if there is more to it, than just conditions.
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
‎09-15-2020 05:11 AM
Sometimes ServiceNow states it very harsh. It should be viewed as a last resort, but sometimes it can't be prevented. Then the conditions will help you. As you said it is a good practice by itself setting the conditions as 'tight' as possible.
ServiceNow uses it themselves as well:

- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
‎09-15-2020 05:18 AM
yeah, let me give you a practical example of current.update() in After business rule.
We have an integration with the third-party system. When we push data it returns a response. From the response body, we need to fetch values and store them inside the current form.
After BR run after data inserted into the database and as our data is coming after the database operation has already been performed so here need to push the new data using current.update().
Muhammad
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
‎09-15-2020 04:58 AM
Long article from external source, but very useful:
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.
Value | Use case |
---|---|
display | Use to provide client-side scripts access to server-side data. |
before | Use 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. |
after | Use to update information on related objects that need to be displayed immediately, such as GlideRecord queries. |
async | Use 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.
Reference:https://docs.qualityclouds.com/qcd/business-rules-best-practices-3997741.html
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
‎09-18-2020 03:48 PM
Hi Kris,
Hope you are doing well.
Is your question resolved? Or do we need to follow-up on this?
Please mark the answer as correct if it solves your question. This will help others who are looking for a similar solution. Also marking this answer as correct takes the post of the unsolved list.
Thanks.
Kind regards,
Willem