by Zdravko Verguilov, ServiceNow Platform Developer, Do IT Wise
In this article, we’ll see how to tame the mischievous nature of ‘var’ variables, and more importantly, why recursions are just as fun as a bad ‘stuck-in-a-time-loop’ Tom Cruise movie.
Business Rules, or BRs, are incredibly useful scripts. They cover almost every kind of scenario, due to being multi-typed, they are incredibly flexible with their trigger conditions, due to a script condition field being present alongside the actual BR conditional construct, and they can also be fast, being server-side dwellers.
If that’s not enough, they have enough reach to affect the client-side as well. So we obviously have an incredible amount of power concentrated in small scripts which at first seem easy enough to handle. But we all know from the wise words of Spider-Man, with great power comes great responsibility. An incorrectly set up Business Rule could render a whole instance useless for any period of time before the built-in defences trigger and cut the corrupt process. And building such Business Rules is relatively easy, so let’s dive in for some actual examples.
Official and not-so-official documentation states that current.update() is not, by any means, your ally. It is boldly claimed in multiple documents across the community that “there’s always a way to avoid it”.
The thing is, we live in an imperfect world, and yes, there will be cases where you’ll have to use current.update() in a BR. Even though it’s a bad practice and leads to some issues. And because of the said imperfect world, ServiceNow provided us with a nice little safety net for those cases that, according to documentation, do not exist.
Let’s shed some light on why using current.update() is not the greatest idea in a BR (although in other types of scripts it’s not a very different picture either). The main issue comes with the ‘update’ type. Here’s an oversimplified setup with two of those:
“Incident Test A” triggers after any incident update, with an order of 100. It adds “test” to the description and then updates the current record.
“Incident Test B” runs after the update, with an order of 200. In real life, you probably won’t have a whole BR for the purpose of a single log, but we’ll only use that as an indication of what’s going on.
Next, we go to any incident and change any field we want, to trigger the BRs. Here is what we get in the log:
What we just witnessed is the direct consequence of current.update(). First, we updated the record, which triggered “Incident Test A”. Then “Incident Test B” ran as the next update-typed BR in order. But, as “A” updates the record once more, “B” also triggers once more. Should this process be left to run unrestricted, “A” would have also triggered itself once more, and then again, and again, entering in an endless loop. Luckily, there’s a logic that watches over the order of execution of after-typed Business Rules and prevents that from happening. Nevertheless, it’s not hard to realize that these are failsafe mechanisms, and any code designed to directly rely on them to run properly is far from any good practice.
What is also important in this case is that preventing the endless loop is not the only concern here. In a real environment, we might have more BRs operating on the same table, so the same logic might actually run more than twice. Apart from the obvious performance loss in uselessly running the same logic multiple times, that can also result in sending the same notifications multiple times, and many other potential problems.
The most obvious solution would be to switch the “after” type of the BR to “before”. Then we simply get rid of the current.update(). We can alter anything we like before a record is updated, and not worry about saving the changes. If we are running our logic BEFORE an UPDATE, that means the UPDATE is IMMINENT. It will save the record that it received, the way it received it:
If we, for some reason, ABSOLUTELY NEED to run that BR after the update, we’ll need the following:
This piece of code will give us the same result as the “before” one. The problem with it is that it effectively prevents any following BRs from triggering on the table. So basically you can have only one piece of BR logic containing current.set Workflow(false) on a table. Any subsequent Business Rules simply wouldn’t run at all.
So, now that we’ve seen the possibilities for update BRs, what about insert and display?
For the insert type, things are similar to the update one. We can’t really access and use the values of the fields before insert, but we can easily set them, and a current.update() action is not needed.
Running current.update() after insert would mess with the update BRs in the same manner described above.
A current.update() in a display BR would simply result in flickering and saving of the form that a user is interacting with.
Some of you might have noticed a little thing we missed at this point. That’s because we kept the best for last…
So, what about asynchronous Business Rules?
Pay attention to that “not time-sensitive” part above, though. That’s the thing with the async Business Rules – you have no guarantees when exactly they would execute, and how much time they’ll need to complete what they’re doing. Remember that little failsafe up there, preventing the “after update” BR from triggering itself endlessly? Here is how it would affect a script if it doesn’t know when it would trigger:
If you wonder how many times it would run before the next stage of the built-in failsafe (yes, there is a next stage, and again, it is not a good idea to rely on those) would notice a recursive BR, it’s about 1024.
So that’s about it for the execution part of things. Here is a best practice bonus:
Nope, not that football thing everyone has a different opinion about.
The last thing we’ll touch on in this article is a simple trick you might want to follow when scripting in a BR. VARs have no block scope, only function scope. That means they are only ever encapsulated (i.e., truly protected from accidental resetting in a script) when defined in the body of a function. So, just a friendly suggestion, if your business logic requires defining some variables (and it probably will), it’s a good idea to do so in a function. Then maybe return some value from it, or leave it void, whatever the logic needs you to do.
Do you find this article helpful? For more insights about ServiceNow, check out our latest piece on “How to clone and configure Service Portal Header?“