Article
· 9 hr ago 8m read

Customizing Message Routers

Introduction

In this article, we will explore various approaches to extending and customizing the behavior of InterSystems IRIS (and IRIS Health) built-in interoperability message routers.

Message routers perform one of the core functions of Enterprise Application Integration (EAI) and are among the most frequently used business processes in interoperability productions.

After a brief overview of the built-in message router classes in InterSystems IRIS and IRIS for Health, the article will demonstrate how to enhance their capabilities to achieve specific outcomes—without the need to develop a business process from scratch..

A word of warning, most of these techniques involve overriding the methods of the current message router classes implementation in IRIS Data Platform and IRIS for Health 2025.x. They may not apply to other past or future versions. 

The GitHub repository in support of this article contains a collection of simple, minimalist, and intentionally abstract examples that illustrate discussed techniques.

We welcome you to leave reviews, comments, and constructive feedback!

Router Classes

IRIS comes with several built-in business process message router classes. The base class for all of them is EnsLib.MsgRouter.RoutingEngine

In the IRIS Data Platform, several specialized subclasses extend this base:

  • EnsLib.MsgRouter.RoutingEngineST: Augments routing by populating a search table (see Ens.VDoc.SearchTable) for each incoming message.
  • EnsLib.MsgRouter.VDocRoutingEngine: Manages the routing of messages, implementing Ens.VDoc.Interface.
  • EnsLib.EDI.MsgRouter.SegmentedRoutingEngine: Routes segmented EDI messages.
  • EnsLib.EDI.X12.MsgRouter.RoutingEngine: Directs X12 messages.

IRIS for Health has an additional subclass:

  • EnsLib.HL7.MsgRouter.RoutingEngine that extends EnsLib.EDI.MsgRouter.SegmentedRoutingEngine to support the routing of HL7 v2.x messages, which are instances of EnsLib.HL7.Message.

All router classes share the business process lifecycle inherited from Ens.BusinessProcess and have a common execution flow implemented by EnsLib.MsgRouter.RoutingEngine OnRequest() method, which executes the following sequence: 

  • If the Validation property is set, validate the message by calling OnValidate().
  • If validation fails, handle malformed message.
  • Evaluate rules and perform the resulting action(s) by calling doOneAction().

The default implementation of OnValidate() does not do anything. Specialized subclasses implement this message to validate incoming messages. For example, EnsLib.HL7.MsgRouter.RoutingEngine OnValidate() uses EnsLib.HL7.Util.Validator to verify the incoming HL7 message.

If validation fails, malformed messages are sent asynchronously to BadMessageHandler target configuration name, and OnError() is called, passing the error status and “0_!_Validation” as completion key.

Rule evaluation is implemented recursively by doOneAction().

You can tailor routing behavior by overriding the following key methods in your custom message router class (which extends a built-in router class):

  • OnRequest() and other business process lifecycle callback methods: To add pre- or post-processing, or set router properties that can be used during rule evaluation.
  • OnValidate(): To customize incoming message validation.
  • doOneAction(): To modify the behavior of actions.
  • OnError(): To implement custom error handling.
  • OnResponse(): To implement custom response handling.
  • OnTimeout(): To implement custom timeout handling.

Implementing Pre- or Post-processing

Since the router is a business process, you can override its callback methods to perform pre- or post-processing, or custom error handling:

  • OnRequest()
  • OnResponse()
  • OnComplete()
  • OnTimeout()

Adding properties to the Router by Overriding OnRequest()

During rule evaluation, the router's properties are available both in business rule logic—such as within boolean expressions in "when" conditions—and during data transformation execution. This is enabled by the router passing a reference to itself through the auxiliary argument aux.

Built-in router classes only expose a limited context: the incoming message and rule set-related properties, such as RuleActionData or temporary @ variables defined within the rule (see Working with rules).

To incorporate additional context data during message routing, you can subclass a built-in router, add custom properties, and override its OnRequest() method to set properties before the actual router logic is executed. It permits you to enrich the routing logic by injecting custom properties or metadata into the rule evaluation and data transformation process. 

You can, for instance, fetch supplementary data by querying the database, or send a synchronous request to another business process or business operation, using the response to augment the routing context.

Let’s override OnRequest() to demonstrate adding a context property that is available during rule evaluation:

Class dc.routing.examples.VariableTargetRouter Extends EnsLib.MsgRouter.RoutingEngine
{

Method doOneAction(pRequest As %Persistent, pOneAction As %String, Output pQuitProcessing As %Boolean, pLevel As %Integer = 1, Output pSyncResponse As %Persistent) As %Status
{
  #Dim sc as %Status
  #Dim ex as %Exception.AbstractException
  s sc = $$$OK
  try {
    if $piece(pOneAction,":",1)="send" {
      s $piece(pOneAction,":",2) = ##class(Ens.Rule.ExpressionParser).Evaluate($piece(pOneAction,":",2),$this,.errorMsg)
    }
    $$$TOE(sc,##super(pRequest,pOneAction,.pQuitProcessing,pLevel,.pSyncResponse))
  } catch (ex) {
    s sc = ex.AsStatus()
  }
  return sc
}

Storage Default
{
<Type>%Storage.Persistent</Type>
}

}

I have applied this technique in field scenarios on multiple occasions. One example involves retrieving detailed medical product information from a pharmacy management system to inform routing decisions and modify outgoing requests. The router overrides the OnRequest() method, initiating a call to an operation that interfaces with the pharmacy system's API. The operation returns enriched product data, which is then exposed to routing rules and data transformations as an object property of the router.

Performing Custom Message Validation

When executing the OnRequest() method during incoming message processing, the router invokes OnValidate(). While the default implementation does not perform any action in EnsLib.MsgRouter.RoutingEngine, such classes as EnsLib.HL7.MsgRouter.RoutingEngine override OnValidate() to enable HL7 v2.x message verification.

Let’s customize HL7 message validation by overriding OnValidate()and using different verification specifications, depending on the message source:

Class dc.routing.examples.CustomValidationRouter Extends EnsLib.HL7.MsgRouter.RoutingEngine
{

Parameter SETTINGS = "CustomValidation";
Property CustomValidation As %String(MAXLEN = 240);
Method OnValidate(pDoc As EnsLib.HL7.Message, pValSpec As %String, Output pStatus As %Status = {$$$OK}) As %Boolean
{
  #Dim ex as %Exception.AbstractException
  #Dim result as %Boolean
  s pStatus = $$$OK
  s result = 0
  try {    
   s result = ##super(pDoc,..GetCustomValidationSpec(pDoc,pValSpec),.pStatus)
  } catch (ex) {
    s pStatus = ex.AsStatus()
  }
  return result
}

Method GetCustomValidationSpec(request As EnsLib.HL7.Message, defaultSpec As %String) As %String
{
  s result = defaultSpec
  s specList = $listfromstring(..CustomValidation)
  s ptr = 0
  while $listnext(specList,ptr,spec) {
    if $piece(spec,"=",1)=..%PrimaryRequestHeader.SourceConfigName {
      s result = $piece(spec,"=",2)
      quit
    }
  }
  return result
}

}

In practice, when working with HL7 routing, overriding the existing, non-empty OnValidate() allows you, for instance, to accept messages having custom (Zxx) non-trailing segments as valid ones, without having to resort to a custom HL7 schema.

Modifying Action Behavior

During rule evaluation, the router calls the doOneAction() method recursively to execute actions such as send, delete, delegate, and rule—where the 'rule' action itself may trigger another recursive invocation of doOneAction().

The method has the following signature:

Method doOneAction(pRequest As %Persistent, pOneAction As %String, Output pQuitProcessing As %Boolean, pLevel As %Integer = 1, Output pSyncResponse As %Persistent) As %Status

The arguments are passed during OnRequest() execution, and have the following values:

  • pRequest is the incoming message.
  • pOneAction is a “:” delimited list of tokens describing the action to take: 
    • $piece(pOneAction,”:”,1) is the action type (send, delete, rule, or delegate).
    • $piece(pOneAction,”:”,2) is the string argument of the ‘send’ action.
    • $piece(pOneAction,”:”,3) is the transform argument of the ‘send’ action.
    • $piece(pOneAction,”:”,4) is the action reason text.

To customize how actions behave, we can override this method. For instance, we can make the 'send' action dynamically evaluate an expression and leverage any properties available during routing rule evaluation:

Any alterations in how the router interprets the actions must, of course, be accounted for in the routing rule.

In this example, string literals should now be enclosed in double quotes, and since the entire argument is considered a string by the rule code generator, the quotes must be escaped by doubling them.

In the routing rule, the send argument is an expression that is evaluated as the target of the send action.

 

Class dc.routing.examples.VariableTargetRouter Extends EnsLib.MsgRouter.RoutingEngine
{

Method doOneAction(pRequest As %Persistent, pOneAction As %String, Output pQuitProcessing As %Boolean, pLevel As %Integer = 1, Output pSyncResponse As %Persistent) As %Status
{
  #Dim sc as %Status
  #Dim ex as %Exception.AbstractException
  s sc = $$$OK
  try {
    if $piece(pOneAction,":",1)="send" {
      s $piece(pOneAction,":",2) = ##class(Ens.Rule.ExpressionParser).Evaluate($piece(pOneAction,":",2),$this,.errorMsg)
    }
    $$$TOE(sc,##super(pRequest,pOneAction,.pQuitProcessing,pLevel,.pSyncResponse))
  } catch (ex) {
    s sc = ex.AsStatus()
  }
  return sc
}

Storage Default
{
<Type>%Storage.Persistent</Type>
}

}

@Thomas Haig ,this technique answers your question about having a routing rule with a variable target.

Another use for this technique occurs when the decision to route the resulting message can only be made after the data transformation has been completed. 

In practice, I applied this technique to incoming messages from a medication ordering system that included a mix of medical product restocking requests.

Some requests were intended for the central pharmacy system, while others targeted the autonomous cabinet management system.

Rather than deploying two separate routers with distinct rule sets, I used conditional logic to route the transformed message based on an expression evaluated from the data transformation resulting message. 

Discussion (0)1
Log in or sign up to continue