Designing Your ServiceNow Script Includes

Posted by admin on October 10, 2024

An effective way to implement your ServiceNow Script Includes

ServiceNow is a powerful platform for IT service management, offering extensive customization through Script Includes. Implementing design patterns can significantly enhance the maintainability and scalability of your ServiceNow applications. In this blog post, we'll delve into the Repository Pattern tailored for ServiceNow, leveraging inheritance to create a robust and flexible structure for interacting with ServiceNow tables.

Provisional Data vs. Non-Provisional Data in ServiceNow and Their Relation to GlideRecord vs. GlideRecordSecure

In ServiceNow, provisional data and non-provisional data serve distinct roles within the platform. These two data types are treated differently, especially when handling CRUD (Create, Read, Update, Delete) operations. Understanding these differences is crucial for ensuring proper data management, security, and access control when developing applications on the platform.

What is Provisional Data? Provisional data refers to the data related to the configuration and development of an application. This data is system-oriented and is typically tracked using Update Sets. Provisional data includes:

  • Application configuration: Custom tables, fields, client scripts, business rules, and UI policies.
  • Customization metadata: Changes made to forms, lists, views, and widgets.
  • Update sets: These data tend to be tracked and allow for migrations of those changes between different instances.

In essence, provisional data is related to the structure and design of the application itself, not the actual business or user data. It supports how the system functions and evolves but is not part of the transactional data in the system.

What is Non-Provisional Data?

Non-provisional data refers to transactional or user data that resides in the system and is directly related to business processes or user activities. This data is often created by end users or automated processes and is not tracked via update sets. Non-provisional data is permanent (until explicitly deleted) and includes information that helps drive the operational aspects of the platform.

Relation to GlideRecord and GlideRecordSecure

The distinction between provisional and non-provisional data directly impacts how developers should approach data access, especially when it comes to security and governance. This is where GlideRecord and GlideRecordSecure come into play.

GlideRecord – Unrestricted Access to Data (Potential ACL Bypass)

  • Purpose: GlideRecord allows you to interact with any table in ServiceNow without enforcing Access Control Lists (ACLs). It provides full access to the underlying data regardless of user permissions.
  • Behavior: Since GlideRecord bypasses ACLs, developers can access or manipulate non-provisional data without adhering to security policies that protect sensitive information.
  • Use Case: GlideRecord is commonly used in admin-level operations or background scripts where unrestricted access is necessary. However, it should be used cautiously when dealing with non-provisional, user-specific data (like user profiles or incidents) because it may expose sensitive data.

GlideRecordSecure – Enforced ACLs (Default for Non-Provisional Data)

  • Purpose: GlideRecordSecure is the more secure alternative to GlideRecord, as it enforces ACLs for all CRUD operations, ensuring that only authorized users or processes can access or modify data.
  • Behavior: GlideRecordSecure respects the security policies set by ServiceNow, allowing operations only if the user or script has the necessary permissions. This is critical when handling non-provisional data, such as user information, cases, or incidents.
  • Use Case: GlideRecordSecure should be the default method when dealing with non-provisional data to protect sensitive records and comply with access control policies.
Introduction to the Repository Pattern

The Repository Pattern is a design pattern that mediates between the domain and data mapping layers, providing a collection-like interface for accessing domain objects. In the context of ServiceNow, this pattern can abstract the complexities of interacting with different tables, promoting cleaner and more maintainable code.

By implementing the Repository Pattern through Script Includes, developers can encapsulate data access logic, making it reusable and easier to manage, especially in large-scale ServiceNow applications.

Designing the Base Repository Class

The foundation of our Repository Pattern is the Repository base class. This class encapsulates common CRUD (Create, Read, Update, Delete) operations and query mechanisms that can be shared across various ServiceNow tables.

The Importance of Using GlideRecordSecure

In the base Repository class, GlideRecordSecure is used by default. This is an essential design choice because GlideRecordSecure respects the Access Control Rules (ACLs) defined in ServiceNow. ACLs play a crucial role in controlling access to sensitive data, ensuring that only authorized users can view or manipulate certain records.

If your application uses GlideRecord instead of GlideRecordSecure, you're effectively bypassing these ACLs, which can lead to unintended security vulnerabilities. For example, sensitive data in HR, Finance, or other confidential records could be exposed to users who shouldn't have access.

By defaulting to GlideRecordSecure, we ensure that our repository pattern is compliant with ServiceNow's security best practices. Developers must explicitly override this behavior if they are confident that GlideRecord is needed, and they must document the reasons why this is necessary.

Repository Script Include
/**
 * @class
 * Repository for interacting with ServiceNow tables.
 */
var Repository = Class.create();
Repository.prototype = {

    /**
     * Initializes the Repository for a given table.
     * @param {string} tableName - The name of the ServiceNow table.
     * @param {boolean} [overrideACL=false] - Determines whether to use GlideRecord (true) or GlideRecordSecure (false).
     */
    initialize: function(tableName, overrideACL) {
        this.overrideACL = overrideACL;
        this.tableName = tableName;
        if (!tableName) {
            throw new Error("A table name must be provided to the Repository.");
        }
        if (overrideACL) {
            this.record = new GlideRecord(this.tableName);
        } else {
            this.record = new GlideRecordSecure(this.tableName);
        }
    },
		
		/**
     * Adds an encoded query to the Repository's record.
     * @param {string} query - Encoded query string.
     * @returns {Repository} Returns the Repository instance for chaining.
     */
    addEncodedQuery: function(query) {
        this.record.addEncodedQuery(query);
        return this;
    },
		
		/**
     * Gets a single record based on the provided query.
     * @param {string} sysId - The sysId of the record to fetch.
     * @param {boolean} [isSerialized=false] - Determines if the record should be serialized.
     * @returns {(GlideRecord|null|Object)} Returns the GlideRecord, serialized record, or null.
     */
    getSingle: function(isSerialized) {
        this.record.setLimit(1);
        this.record.query();
        if (this.record.next()) {
            if (isSerialized) {
                return this._serializeRecord(this.record);
            } else {
                return this.record;
            }
        } else {
            return undefined;
        }


    },

    /**
     * Gets multiple records based on the current query.
     * @param {boolean} [isSerialized=false] - Determines if the records should be serialized.
     * @returns {(Array.<Object>|GlideRecord)} Returns an array of serialized records or a GlideRecord.
     */
    getMultiple: function(isSerialized) {
        this.record.query();

        if (isSerialized) {
            var results = [];
            while (this.record.next()) {
                results.push(this._serializeRecord(this.record));
            }

            return results;
        } else {
            return this.record;
        }
    },

    // ... [Other CRUD and query methods as defined in the base Repository class] ...

    /** 
     * Type definition for easier identification of this script include.
     * @type {string}
     */
    type: 'Repository'
};

Example:

Get single record

 taskGr = new Repository('task', false).addEncodedQuery('active=true').getSingle();

Get multiple records

var taskGr = new Repository('task', false).addEncodedQuery('active=true').getMultiple() ;

Key Components

  • Initialization: Sets up the GlideRecord or GlideRecordSecure based on the overrideACL parameter.
  • CRUD Operations: Methods like getSingle, and getMultiple, update, insert handle data manipulation.
  • Query Building: Methods like addEncodedQueryQuery, orderBy, and paginate facilitate building complex queries.
Creating Specialized Repositories

Building upon the base Repository class, we can create specialized repositories for specific tables. This is achieved through inheritance, allowing each specialized repository to extend the base functionality with table-specific logic.

Task Repository

/**
 * @class
 * Repository for interacting with the Task table in ServiceNow.
 * @extends Repository
 */
var TaskRepository = Class.create();
TaskRepository.prototype = Object.extendsObject(Repository, {

    /**
     * Initializes the TaskRepository for the Task table.
     * @param {boolean} [overrideACL=false] - Determines whether to use GlideRecord (true) or GlideRecordSecure (false).
     */
    initialize: function(tableName, overrideACL) {
        // Calling the parent's initialize method with the Task table.
        var table = tableName || 'task';
        Repository.prototype.initialize.call(this, table, overrideACL);
    },

    /**
     * Retrieves tasks assigned to current user.
     * @param {boolean} [isSerialized=false] - Determines if the records should be serialized.
     * @returns {(Array.<Object>|GlideRecord)} Returns an array of serialized records or a GlideRecord.
     */
    getByAssignedToMe: function(isSerialized) {
        this.addQuery('assigned_to=' + gs.getUserID());
        return this.getMultiple(isSerialized);
    },

    // ... [Other Task-specific methods] ...

    /** 
     * Type definition for easier identification of this script include.
     * @type {string}
     */
    type: 'TaskRepository'
});

HRTaskRepository Extending further, the HRTaskRepository is specialized for the HR Task table, which inherits from the Task table.

/** 
 * @class 
 * Repository for interacting with the HR Task table in ServiceNow.
 * @extends TaskRepository
 */
var HRTaskRepository = Class.create();
HRTaskRepository.prototype = Object.extendsObject(TaskRepository, {

    /**
     * Initializes the HRTaskRepository for the HR Task table.
     * @param {boolean} [overrideACL=false] - Determines whether to use GlideRecord (true) or GlideRecordSecure (false).
     */
    initialize: function(overrideACL) {
        TaskRepository.prototype.initialize.call(this, 'sn_hr_core_task', overrideACL);
    },

    /**
     * Retrieve HR tasks associated with parent case specific HR Service.
     * @param {string} hrServiceId - The sys_id of the HR Service.
     * @param {boolean} [isSerialized=false] - Determines if the records should be serialized.
     * @returns {(Array.<Object>|GlideRecord)} Returns an array of serialized records or a GlideRecord.
     */
    getByHRService: function(hrServiceId, isSerialized) {
        this.addQuery('parent.ref_sn_hr_core_case.hr_service=' + hrServiceId);
        return this.getMultiple(isSerialized);
    },

    // ... [Other HR Task-specific methods] ...

    /** 
     * Type definition for easier identification of this script include.
     * @type {string}
     */
    type: 'HRTaskRepository'
});

Benefits of Inheritance

  • Code Reusability: Common functionalities are defined once in the base class and inherited by specialized repositories.
  • Maintainability: Updates to shared logic need to be made only in the base class.
  • Extensibility: New repositories for other tables can be easily created by extending existing ones.
Advantages of This Approach

Implementing the Repository Pattern with inheritance in ServiceNow offers several advantages:

  1. Separation of Concerns: Data access logic is decoupled from business logic, enhancing code organization.
  2. Consistency: Standardized methods across repositories ensure uniform interaction with different tables.
  3. Scalability: Easily extendable to accommodate new tables and complex business requirements.
  4. Enhanced Security: Utilizing GlideRecordSecure by default ensures adherence to ACLs unless overridden.
  5. Simplified Testing: Encapsulated data access logic facilitates easier unit testing and mocking.
Visualizing the Inheritance Structure

Figure 1: Inheritance structure of Repository, TaskRepository, and HRTaskRepository.

Description: The diagram illustrates the inheritance hierarchy where TaskRepository extends Repository, and HRTaskRepository extends TaskRepository. This hierarchical structure allows each repository to inherit and augment functionalities as needed.

Conclusion

Adopting the Repository Pattern in ServiceNow through Script Includes and leveraging inheritance provides a robust framework for managing data interactions across various tables. This approach promotes cleaner code, enhances maintainability, and scales effectively with the growing complexities of ServiceNow applications.

By structuring your repositories in this manner, you ensure a consistent and efficient way to handle data operations, paving the way for more manageable and scalable ServiceNow solutions.

Copyright © Formcloud LLC 2024