官术网_书友最值得收藏!

Business rules

The home of server-side scripting in ServiceNow is business rules. They are so called because they allow you to apply business or process logic to applications hosted on ServiceNow. Do you want to update a remote system through an integration? Perhaps check a date? Or populate some fields automatically? Business Rules are a great way to accomplish this.

Some like to think of business rules as database triggers since they perform a very similar function. business rules run whenever a record is inserted, updated, or deleted or when a query is run against the table. Since ServiceNow is a data-driven platform, this enables great flexibility in manipulating and working with the information in the system.

To create a Business Rule, navigate to System Definition > Business Rules, and click New.

Setting the table

One of the thoughts to have when creating a business rule is to set which table it will run on. Most of the time, it is obvious: if you want to affect the Check-in records, you need to place it on the Check-in table.

However, what makes business rules pretty special is that you can run them on any level of the table hierarchy.

In the previous chapter the Guest table was created, extending the User table. This means it inherits the fields that User provides. Business rules are inherited too. So, if a business rule is created for the Guest table, it will run just for Guest records, but if it is placed against the User table, it will run for all User records, including Guests.

Being advanced

There are two sides to business rules. Some common actions can be performed without any code. You can use the options to display a message, set fields, or stop execution when a certain condition is met. So this means that whenever a room is created or a Guest record updated, you can easily inform the user. This is an example of the point and click or code option that ServiceNow gives you. It's a good idea to use this if possible.

However, ticking the Advanced checkbox enables the script and many other fields. Since we are Mastering ServiceNow, we'll concentrate on this side!

Note

For information on the simpler functions, check out the product documentation: https://docs.servicenow.com/bundle/helsinki-servicenow-platform/page/script/business-rules/concept/c_BusinessRules.html.

Knowing the predefined variables

Whenever a Business Rule runs (and the Advanced checkbox is ticked!) the code in the script field is run. The script has access to several global variables provided by the platform:

  • current: This is a GlideRecord object that represents the record in question. Any changes that the user (or a script) has performed on it are available for inspection.
  • previous: This represents the record before any changes have been made.
  • g_scratchpad: This is used as a temporary storage area for data; however, it's also available to client-side scripts. We'll explore this in more detail in the next chapter.
  • gs: We've already met this; it contains several helpful functions for use in our scripts.

Even though these variables are global, the platform provides a pre-defined function that passes in current and previous. You should use this as your starting point.

Displaying the right table

For a very simple example of what a Business Rule can do, follow these steps:

  1. Navigate to System Definition > Business Rules. Click on New.
  2. Use the following information:
    • Name: Display table on record creation
    • Table: User[sys_user]
    • Insert: <ticked>
    • Advanced: <ticked>
    • Script:
gs.addInfoMessage ('You have created a ' + current.getTableName() + ' record'); 

Add the script inside the function that was created for you so that the script field looks like this:

Tip

Always insert the code inside the executeRule script. This stop Business Rules from colliding and affecting each other. If you did not, there is the increased possibility that one Business Rule could affect the variables of another.

This code uses another of the GlideSystem functions to output a message. Instead of it being visible in the logfile, it is presented to the user who carried out the action. In the message, the table name of the current record is shown using the getTableName function of the current object of GlideRecord.

Tip

It is always a good idea to give the Business Rule a meaningful name so you can easily identify it later. There is also a description field that you can add to the form to allow you to better describe what the script does.

From now on, whenever you create a User or Guest record, you will receive a message telling you which table has been used.

For Guest records, you get the following message:

And this is the one you get for User records:

Conditioning your scripts

When records in a table are worked with, Business Rules are run. Each Business Rule has a condition that determines whether the script should run or not. The condition can be a JavaScript statement that is executed. If the result is true, then the main script is parsed and run. In addition, the Filter conditions and Role conditions fields can be used to specify when to execute.

It is good practice to always provide some sort of condition. Here's why:

  • Code should only be run when appropriate. Since the Script field is only parsed if the statement passes, code that is not appropriate at that time is ignored, speeding up the execution.
  • It provides easier debugging. Business Rule debugging tells you which scripts were run and when and whether the condition was met or not. Chapter 8, Securing Applications and Data, discusses this in more detail.

The condition has access to the current object of GlideRecord and the other predefined variables, where appropriate. This lets you check what is happening to the record, ensuring the Business Rule is running only at the right time. For example, for a Business Rule on the Check-in table, a condition can be added to run the script only if the value of the Guest field has been altered:

current.guest.changes() 

This line of code uses several elements we've already seen: current, our GlideRecord object representing the record we are dealing with; guest, the GlideElement object for the field; and a function called changes, which returns true if someone (or something) has altered the value of that field.

Tip

In addition to always having a condition, it a good idea to always have a condition that includes a changes function call on a GlideElement object. It is common to see code like this: current.guest == 'Bob'

This means that whenever guest is set to Bob, even if it hasn't changed, this Business Rule will run. For the majority of the time, you only want to run your rule when the value is changed: current.guest.changesTo('Bob')

You may also want to use some of the other functions available in GlideSystem to limit Business Rules, such as the hasRole function. Roles and platform security is explored in Chapter 7, Exchanging Data – Import Sets, Web Services, and other Integrations.

Having good conditions

When writing conditions, be careful with using the right JavaScript operators. Since the Condition field is a short line, the code can get quite compressed. If you have a very complex condition, consider the following:

  • Split one Business Rule into many. This is always good practice anyway. Instead of having lots of OR conditions and writing one big Business Rule, have several, more targeted Business Rules with smaller conditions.
  • Put the most important statements in the Condition field and place the rest in an if block in the Script field.
  • Write a function and call it from the condition (the ideal place to store the function is in a Script Include, described later in this chapter).
  • Expand the Condition field. It is possible to make the Condition field bigger by changing the dictionary settings. However, consider that this may just encourage you to write longer conditions, and longer conditions tend to go wrong!

Tip

Finally, lay out your conditions carefully, and test thoroughly. The most common confusion I see is around negative or conditions. If you want to test that several values aren't true, you must use and. In English, we often say "I don't want a, b, or c", which is short for "I don't want a, I don't want b, and I don't want c." We need the long format! In JavaScript, it would be this:

current.field != 'a' && current.field != 'b' && current.field != 'c'

Controlling the database

Business Rules run when there is database access. So, if you try to delete 20 records, a delete Business Rule will run 20 times. It is most common for Business Rules to run on insert and update, but you can select a combination of the options relatively freely.

As this diagram shows, when the instance does something with the database, it triggers the Business Rules to run against whatever record was worked with.

Controlling database queries with Business Rules

Query Business Rules are slightly different. These run before the user has a chance to interact with a particular record, and even before the database has received the results. In fact, they are designed to control exactly what the database returns. When a query Business Rules runs, the current object is still provided, but it is before the query function has been called. This gives you an opportunity to add conditions, such as when we manipulated our first GlideRecord object.

A great example is the baseline Business Rule called user query that makes inactive users invisible to everyone bar System Administrators. Let's examine its contents:

The first thing to look at is the following condition:

gs.getSession().isInteractive() && !gs.hasRole("admin") 

The condition is split into two parts. Firstly, it checks to see how the user is logged in. GlideSystem provides session details through the getSession function, and the isInteractive function returns true if the user is using the web interface (other alternatives include accessing the platform via web services, as discussed in Chapter 7, Exchanging Data - Import Sets, Web Services, and Other Integrations).

Secondly, it determines whether the user has the admin role. So, if the user is accessing through the web interface and isn't an admin, the script will run.

If the condition matches, this one-line script does the work:

current.addActiveQuery(); 

This is equivalent to running the addQuery function you may be more familiar with:

current.addQuery('active', true); 

It uses slightly fewer keystrokes!

This Business Rule will always ensure that (subject to the condition) only active users are returned when accessing the User table. Once a user is marked as inactive, they will effectively "disappear" from reference fields and lists alike. This technique is very useful for enforcing particular security rules, but it is worth remembering if you wonder why you can't access some records. The following diagram shows that the Query Business Rule runs before the database access.

Choosing when to execute - before, after, and really after

In addition to the Insert, Update, Delete, and Query checkboxes, the When to run section in the business-rules form also contains the When and Order fields. These control exactly when your rule runs.

The When dropdown field controls the point during the database operation the Business Rule runs: before the data is sent to the database, or after. When inserting a record, you may want to programmatically set or alter default values. Any before Business Rules that run against this record can alter or check what data will be sent to the database. An after Business Rule is used when you want to update another table or system, since at that point you know the record has been successfully altered. This diagram shows this graphically, as well as the aysnc option, discussed in a moment.

Defaulting data

Let's create a Business Rule that will automatically populate the floor number when you create or update a Room record. In most hotels, the first digit of the room number is the floor number. Follow these steps:

  1. Create a new Business Rule (navigate to System Definition > Business Rules, and click New), and set the following information:
    • Name: Set default floor number
    • Table: Room [x_hotel_room]
    • Advanced: <ticked>
    • Insert: <ticked>
    • Update: <ticked>
    • Condition:
    (current.number.changes() || current.floor.changes()) && current.floor.nil() && current.number.toString().length > 1 
    

    This fairly long condition checks several items. It'll only run when the Number or the Room field changes, and check that the floor field is not populated and that the number field, when converted to a string, has a length of at least one character.

    Tip

    This condition could also be specified using the Condition builder.

    Script:

    current.floor = current.number.substring(0, 1); 
    

    The script will take the first digit of the number value and assign it to the floor field. Remember to keep the executeRule method in place.

    Tip

    Notice that current.update() was not necessary. Since this is a before Business Rule, we are able to make changes before the record writes to the database. In other words, the reason why Business Rules are running is because something triggered the record to save. (Probably someone pressing the Submit or Save button in the UI.) We don't need to try to save it again, and in fact, you may cause loops by doing so. Wherever possible, the platform will detect and stop this from happening.

  2. Save the business rule.
  3. To test, try creating a room without the Floor field populated, but ensure Number is. When you save the record, the system will automatically provide a value for the Floor field. This will also happen if you try to clear out the floor, effectively providing a rule using which the system will ensure that a room number is generally always available.

Note

When you test, you'll receive a message saying that access to ScopedGlideElement was granted. This is the first time you've accessed fields whilst in the Hotel scope.

As done here, if you are providing default values or calculating new ones, it is a good idea to ensure you have the data and that the user hasn't overridden the system. When possible, you should trust the users of your system to know what they are doing. There are usually exceptions to policies, and if the system is too locked down and only allows for foreseen circumstances, frustration will arise!

Note

There are many other options for validation and checking data. In the next chapter, we'll explore client-side options, while later on in this chapter, we'll look at Data Policy.

Validating information

Another use of before business rules is to validate data. If you find information that is wrong or missing, you can tell the platform not to commit the action to the database.

One of the big issues with our hotel application is that you can check in to a room multiple times. There is nothing to prevent the allocation of the same room to several different people!

Let's expand the Check-in table slightly to include more information:

  1. Navigate to Hotel > Check-ins, and click New to open the form.
  2. Click the additional action menu icon and choose Configure, then Form Design.
  3. Click the Field Types tab on the left, and drag in a new Date field. This represents the date on which the check-in happened. Click the cog next to it, and set the label and name as below:
    • Label: Date
    • Name: Date
  4. Click Save. If you navigate to Hotel > Check-ins, and click New again, the form should look like this:
  5. Now, let's create a Business Rule that uses this date and the Room field to ensure collisions don't occur. Navigate to System Definition > Business Rules and click New. Fill in these fields, and Save.
    • Name: Stop duplicate check-ins
    • Table: Check-in [x_hotel_check_in]
    • Advanced:<ticked>
    • Insert: <ticked>
    • Update: <ticked>
    • Condition:
    current.room.changes() ||  current.date.changes() || current.operation() ==  'insert'

    This condition is shorter. This rule will fire if either the Room or Date fields change. Also, there is an additional check to see whether the record is being inserted. If you start with a completely blank record and insert it, then the Date and Room fields will both change (from nothing to something), but it is possible to "copy" a record (using Insert and Stay). In this edge case, you may end up with a record being inserted without any fields changing.

    • Script: (Remember to insert this code inside the provided function)
    var dup = new GlideRecord('x_hotel_check_in'); 
    dup.addQuery('room', current.room); 
    dup.addQuery('date', current.date); 
    dup.setLimit(1); 
    dup.query(); 
    if (dup.hasNext()) { 
      gs.addErrorMessage('This room is already checked in on this date.'); 
      current.setAbortAction(true); 
    } 
    

This script is longer than what we've seen before, but it reuses a lot of the functionality we've already seen. The bulk of the code sets up a GlideRecord query to find any Check-in records that have the same room and date as the record we are dealing with. We are only interested if there is at least one record, so a limit is added-a simple optimization.

Then, we query the database. Instead of looping round the results, we just ask the database whether there was a result. The hasNext function of GlideRecord will return true if there is.

If so, the script then call two new functions: the addErrorMessage function of GlideSystem gives the user a red error message, and, as the name suggests, setAbortAction(true) will stop the system in its tracks. This means the record is not inserted or updated.

Once the Business Rules has been saved, test it by creating some Check-in records. If you choose a date and a room that is the same as another record, when you click on Submit, you will be faced with the following error message:

Tip

GlideAggregate could have been used instead of GlideRecord. This lets the database do more of the counting.

Working with dates

A common requirement is ensuring that dates make sense. Let's validate that the Departure field on the Reservations table has a value that's after the Arrival date. There are several ways to compare dates: ensure you check out all the functions available in GlideDateTime to find the date from a few days ago or the beginning of the next quarter:

  1. Create a new Business Rule (System Definition | Business Rules and click New), and set the following information. Once done, Save.
    • Name: Sanity check dates
    • Table: Reservation [x_hotel_reservation]
    • Advanced:<ticked>
    • Insert: <ticked>
    • Update: <ticked>
    • Condition: current.arrival > current.departure
    • This very simple condition uses coercion to compare two dates against each other. The script runs if the arrival date is greater than (meaning after) departure. Most of the time, you can work with the GlideElement objects as you expect them to, but ensure you always test thoroughly.
    • Script:
      gs.addErrorMessage('Departure date must be after arrival'); 
      current.setAbortAction(true); 
      
    • Both these two function calls have already been use: they add an error message and stop the record from being saved.
  2. Try out your script by creating or editing an Arrival date that is after the Departure date. You should get the error message, and the record should not be saved.

Updating information

The opposite to before is after. Rules with When set to after will run after the record has been committed to the database. This means that you cannot use them to set field values or stop the database write, since it has already happened. Indeed, knowing that the record has successfully been saved means that it is the perfect place to update other records.

We can use this to enforce a rule we have within Gardiner Hotels-ensuring there is only one lead passenger in a reservation:

  1. Go to System Definition > Business Rules and click New. Set the following information, and Save.
    • Name: Force single lead passenger
    • Table: Guest Reservations [x_hotel_m2m_guests_reservations]
    • Advanced: <ticked>
    • When: After
    • Insert: <ticked>
    • Update: <ticked>
    • Condition:
      current.lead.changesTo(true) || (current.lead && current.operation() == 'insert')
      

      This condition checks whether Lead has just been selected as true or whether it is a new record being inserted and the Lead field is true.

      Tip

      A true/false field in ServiceNow will always be either true or false; it cannot be null. And don't forget that the field will be a GlideElement object. This means that you shouldn't use current.lead === true to test for strict equality-it'll be false.

    • Script:
      var lead = new GlideRecord('x_hotel_m2m_guests_reservations'); 
      lead.addQuery('reservation', current.reservation); 
      lead.addQuery('lead', true); 
      lead.addQuery('sys_id', '!=', current.sys_id); 
      lead.query(); 
      while(lead.next()) { 
        lead.lead = false; 
        lead.update(); 
      } 
      

      This is a fairly typical loop based on GlideRecord. It queries the many-to-many relationship table that relates guests to their reservations. It looks for the set of records that are for this reservation, and it finds every one that has the Lead field set to true. Of course, it should avoid working with the current record, so this is excluded by filtering out using sys_id.

      The result set is looped around, with the script setting the Lead field of each to false, and finally, it saves the record.

  2. Try creating a new Reservation record, and set more than one of the guests to have Lead marked as true. You will find that the script will change the records so that only the last one to be updated keeps it. All the others will have Lead set to false.

Running things later with system scheduling

Another way to execute a Business Rule is to run it async, meaning asynchronous. This is similar to an after Business Rule, but instead of running the script immediately after the record is saved and before the results are presented to the user, it runs it at some later time. The platform does this by creating a scheduled job.

Tip

If you wish to see these jobs, navigate to System Scheduler > Scheduled Jobs. Chapter 5, Getting Things Done with Tasks, explains these concepts in much more detail.

A scheduled job has a Next Action date, which tells the platform when the record can be run. Jobs will be run after this date, but in order and only when the system has the capacity. If the instance is busy, perhaps serving interactive (user) requests, then the scheduled jobs may queue up. An asynchronous Business Rule will be scheduled to run immediately, but it is not guaranteed to run within a specific timeframe.

Tip

Asynchronous Business Rules are not run within the user's current session. When a scheduled job is picked up and is assigned to a specific user account, the platform creates a new session and impersonates the user. This does have an impact on some session-specific functionality, such as Field Encryption plugin. However, most of the time, you won't notice it.

Asynchronous Business Rules are therefore perfect for jobs that may take some time to run and don't need immediate feedback to be sent to the end user. A good example are integrations such as eBonding, where ServiceNow connects to a remote system using web services and exchanges information after an update in ServiceNow. E-mail notification are another. In general, you don't want to force the user to wait, blocking their browser session while these activites takes place.

Tip

Apps using the scoped API are subject to restrictions on what they can do within Business Rules. For example, you must perform a REST call in an async Business Rule. The platform will not allow it in a before or after script. This is to protect system performance.

Display business rules

We've discussed Business Rules that control queries, manipulate data on the way to the database, and react to events such as deletion, but there is a final, less common option available: display Business Rules. Scripts marked to run on display will run after the record has been pulled from the database, but before it is displayed on a form. (Display Business Rules are not run for lists.)

Display Business Rules don't actually change the data that is stored, but only what is presented. So a script could, on every display, populate a field with a random number, and it'd be different every time. The database changes to the most recently generated number only if the record is saved.

However, the most important aspect of display Business Rules is the ability to pass data to the client. A special variable called g_scratchpad is available, allowing calculated information to be passed to the browser, making it is instantly available. We'll discuss this capability in the next chapter.

Global business rules

Sometimes, it is desirable to have code that runs all the time. You may have noticed that some business rules, have the Table field set Global. You may think of this as "all tables" instead, since a script written in a global Business Rule is executed regardless of which table is being updated, queried, inserted, or deleted.

As they stand today, global business rules have limited use due to the bluntness of their nature. If you want to run a script every time any record is deleted, then this is the way to achieve it. But this use case is not very frequent! In the past, a global business rule was the only way to define code that was always available, such as a utility function. However, Script Includes perform this job far more efficiently.

Instead, the rather large downside of a global business rule is that the code is parsed and executed very frequently-on every transaction or interaction with the platform. A function defined in a global Business Rule is parsed and stored in memory, cluttering the namespace, even if it is not used. This therefore means that global business rules are generally only used in very specific circumstances.

Tip

Business rules created in an application cannot create global Business Rules. They can only be created in the global scope; and even then it's a bad idea.

You may see lots of legacy global Business Rules in the out-of-the-box platform. Over time, these will be removed and replaced as Script Includes.

主站蜘蛛池模板: 成武县| 尖扎县| 屏南县| 和政县| 临清市| 苍梧县| 峨眉山市| 鸡东县| 山丹县| 靖边县| 乐都县| 雅江县| 郁南县| 鱼台县| 嘉义市| 天津市| 罗山县| 阜南县| 台南市| 莫力| 泰安市| 静宁县| 宝丰县| 张家口市| 巨鹿县| 旺苍县| 德州市| 奇台县| 望谟县| 马龙县| 巴林右旗| 鹤庆县| 敖汉旗| 溆浦县| 巩留县| 兴和县| 九江市| 贞丰县| 雷山县| 堆龙德庆县| 溧水县|