VenniMakarainen
ServiceNow Employee
ServiceNow Employee

Foreword

 

This article expects basic level of knowledge of Domain Separation, its concepts and functional behaviour. Additionally, the basic understanding of Flow Designer and Decision Tables will help the reader to understand the contents of this article

 

Introduction

 

The primary use of Domain Separation is to separate data, processes, and administrative tasks into logically defined domains. Especially, Domain Separation is used to separate data. I.e., Domain a does not have visibility to data in Domain b and vice versa. This is when:

 

  1. A particular domain is not a parent/child to another domain
  2. A particular domain does not contain/is not contained by another domain

 

VenniMakarainen_0-1674128266964.png

 

Additionally, the standard behaviour prevents any customer to see any data of the service provider. Often, service providers have so called shared data to support their service delivery for multiple customers, but this data is only accessible by the service provider itself using the contains domains and/or visibility domains options.

 

Flow execution in domain separated instance

 

Enforced data separation applies to many background automations as well. For example, a flow triggered in Domain a for a Requested Item will execute in Domain a:

 

  1. A flow is configured in Global domain – used by various catalog items to streamline and standardize certain service delivery of the service provider
  2. Customer a submits a catalog item associated with the flow
  3. One of the created task records is a Requested Item in Domain a
  4. The flow then triggers against the Requested Item – and inherits Domain a as its execution context + inherits the Domain a as the scope of data visibility

What if it would be desired to have the flow now executing in Domain a to do some automation leveraging data in another domain? This could be facilitated for example by:

 

  • The contains domains feature – simple to setup but would allow all Domain a users to see the target domain data. Likely a no-go due to security, compliance and contractual reasons.
  • Duplicating data across domains and defining a master record – not simple and does not support manageability nor user experience to say the least.

I’ll outline an approach which requires neither of the above options. Let’s start with setting the scene with a sample scenario.

 

Across domains flow execution - sample scenario

 

  1. Customer a in Domain a still submits that specific catalog item
  2. Requested Item gets created in Domain a
  3. An agent of the service provider picks up the request and enriches it by populating a background reference variable x – some data residing in the Shared MSP domain
  4. We would now like to have the flow to run some automation in the Shared MSP domain with a flow using the variable x. But oops, we cannot. The flow is executing in Domain a which has absolutely zero visibility into the Shared MSP domain. Therefore, the flow cannot see the value of variable x either.

Let us next see an example approach to facilitate the desired job execution.

 

Across domains flow execution - example approach for the sample scenario

 

Step 1 - flow action configuration

We need ScriptableFlowRunner to be incorporated in a new flow action – configure action once, reuse across flows. As you can guess, the ScriptableFlowRunner can be used to trigger flows and subflows. More importantly, it enables defining the domain where the triggered flow executes.

 

We will use minimal mandatory inputs for our scenario:

  • Requested item record
  • The name of a subflow

These inputs are passed on to the ScriptableFlowRunner in order to:

  1. determine which subflow we want to trigger
  2. make the Requested item record available for the triggered subflow

The action could look like this:

 

VenniMakarainen_1-1674128266969.png

 

I’ll just have this example action always trigger a subflow in the Global domain. The preferred domain could be an additional input for the action.

 

Step 2 - subflow configuration

We need a subflow that does the magic job execution for us. This is the subflow that is triggered by the above action.

 

Step 3 - decision table configuration

We will leverage Decision Tables to determine:

 

(a) which of our subflows should execute when the main flow executes – let us avoid complex if-else logic within the main flow and configuring something which only works for one customer served by the service provider. Let’s have a very simple setup for this:

 

VenniMakarainen_2-1674128266975.png

        - E.g., should we give inputs Catalog Item and Customer a, we’d get Subflow x returned

 

(b) approvals – in this scenario we will facilitate customer specific approvals which are often needed... and again avoid complex if-else logic within the main flow. Let’s have a simple setup for this as well:

 

VenniMakarainen_1-1674132964879.pngVenniMakarainen_0-1674133357061.png

 


              - E.g., should we give inputs Catalog Item and Customer a, we’d get Customer a approvers returned

 

Step 4 - flow configuration

We need to put it all together - a flow which is something like this:

VenniMakarainen_4-1674128266984.png

 

I’ll briefly note what takes place per each action in the above sample flow:

  1. Interaction with our approvals decision table to determine which approvers we should engage
  2. Approvals facilitated by a subflow for which we will provide the step 1. decision result
  3. Check whether approval approved/rejected
  4. Do something if rejected – here execution just bluntly ended
  5. Some task(s) to facilitate proper request fulfilment – here we tell the agent to populate variable x and wait for the task completion
  6. Interaction with our subflows  decision table to determine which subflow should be triggered in the context of current Requested Item
  7. Our custom action execution using ScriptableFlowRunner. The action triggers the subflow from step 6. decision result in the Global domain. The subflow is aware of the original Requested Item while having access to the variable x. The triggered subflow is not restricted by a domain context and can execute that little magic automation across domains.

Ending words

 

I hope you found this piece of writing interesting and helpful. Often, combining various powerful platform features together can enable that little piece of magic – even without unnecessary customization.

 

 

 

 

Comments
Tom Hauri
ServiceNow Employee
ServiceNow Employee

Hi Venni,
Good idea to create an article about this!

We had a similar issue (we use session domain instead of record domain) and we found another solution. Actually support wrote a KB about it after our case: KB0998966. Same problem and solution exists also for legacy workflows: KB1197774.


For example you could identify your cross domain flows and insert a Business Rule on the Flow engine context [sys_flow_context].
Example for our use case with system:

 

var bRunAsSystem = false;
try {
	var oAttr = JSON.parse(current.getValue('attributes'));
	if ((oAttr.hasOwnProperty('run_as')) && (oAttr['run_as'] == 'system')) {
		bRunAsSystem = true;
	} // if run as system
} catch (ex) {
	// do nothing if JSON not parsable
} // catch invalid json
if (bRunAsSystem) {
	current.setValue('sys_domain', 'global');
}

 

 

VenniMakarainen
ServiceNow Employee
ServiceNow Employee

Cheers Tom! It is very good to highlight alternative options. The approach is what mainly differs here:

 

  • enforcing  specific domain in the background vs. allowing defining the execution domain within flow designer. (for a little piece of work)
  • segregating the cross-domain automation from any main process flow while allowing scenario specific execution based on the context of the request

The business rule based solution is nice and simple indeed! Then it is a matter that works best for  a real-life scenario.

Nathan Sanders
Tera Contributor

Thank you, Venni and Tom, for this post. This was very helpful in a recent situation that we had. However, we had to adjust the script above as neither fit our use cases. So I wanted to share with the community our solution. In case anyone else is facing a similar issue with ServiceNow Flows and Domain Separation, here's what we did to address it.

 

Fortunately, I found a solution in a post by Tom over at ServiceNow. However, the solution had a drawback: it made all flows set to run as system run in the global domain. While this may be fine for some users, it was too broad for our use cases. We wanted some flows to be universal but run on Mid servers for a particular domain, and we didn't want to specify a specific Mid server. We hoped to run these flows as a system and let ServiceNow find the first available Mid server for us. However, if we used Tom's script, it would see all our Mid servers instead of just the ones in the desired domain.

 

To address this issue, we took Tom's script and modified it to add a system property that allows us to maintain an allowlist of flows that can run in the global domain. If a flow is not on the allowlist, flows will not be allowed to run in the global domain and run in the domain the flow was triggered from. This solution works well for us and allows us to control which flows can run globally and which can't.

 

You might be wondering why we needed so much code to do this. The reason is that the sys_flow_context in ServiceNow does not store the flow sys_id until update 1, which is too late to change the domain of the flow execution. Therefore, we had to look for a way to get the flow id on insert (update 0).

 

I hope this solution helps someone else who has a similar need.

 

My blog post on this: https://nathanasanders.com/2023/05/09/flow-execution-across-domains-yes-you-can-part-2/ 

 

Business Rule:

  • Name: sys_flow_context – set domain
  • Table: Flow engine context (sys_flow_context)
  • Domain: Global
  • When: before
  • Order: 10,000
  • Insert: Yes

 

Script:

 

(function executeRule(current, previous /*null when async*/ ) {
    try {
        // Declare our variables, system property 'u_flow_domain_sep_exempt' is the AllowList
        var flowId;
        var oAttr = JSON.parse(current.getValue('attributes'));
        var pAttr = JSON.parse(current.getValue('plan'));
        var sysIdArr = [];
        var sysIds = gs.getProperty("u_flow_domain_sep_exempt");
        var onList = false;
        var bRunAsSystem = false;

        // sys_context_flow lacks the flow id on insert, so we need to walk into snapshot to get the sys_id of the flow
        if (pAttr.hasOwnProperty('persistor')) {
            var grFlowSnap = new GlideRecord('sys_hub_snapshot');
            grFlowSnap.addEncodedQuery('sys_id=' + pAttr['persistor']['snapshot']);
            grFlowSnap.query();
            while (grFlowSnap.next()) {
                flowId = grFlowSnap.parent;
            }
        }

        // Let's see if the flow id is on our allow list
        if (sysIds) {
            sysIdArr = sysIds.split(",");
            for (var i = 0; i < sysIdArr.length; i++) {
                if (sysIdArr[i] == flowId) {
                    onList = true;
                }
            }
        }

        // Refer to https://support.servicenow.com/kb?id=kb_article_view&sysparm_article=KB0998966
        if (gs.getProperty('glide.sys.domain.use_record_domain_for_data') == 'false') {
            if ((oAttr.hasOwnProperty('run_as')) && (oAttr['run_as'] == 'system') && (onList)) {
                bRunAsSystem = true;
            }
            if (bRunAsSystem) {
                gs.info("Flow ID: " + flowId + " Running in Global Domain as on Allow List.");
                current.setValue('sys_domain', 'global');
            } else {
                // Running in Record Domain that triggered the flow.
            }
        }
    } catch (ex) {}
})(current, previous);

 

 

 

paki
Tera Expert

the attribut "snapshot" doesn't exist anymore. Have you checked on this since?

Version history
Last update:
‎03-21-2025 05:15 AM
Updated by:
Contributors