查找

記事
· 2025年11月12日 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. 

ディスカッション (0)1
続けるにはログインするか新規登録を行ってください
質問
· 2025年11月12日

Regain Your Confidence with the Best Hair Transplant Clinic in Indore

Tired of hiding your hair loss? At RMD Hair Transplant, we bring back your natural look with advanced FUE and DHI hair transplant techniques performed by skilled surgeons.

✅ 100% Natural Results
✅ Minimal Downtime
✅ Experienced Doctors
✅ Advanced Technology
✅ Personalized Treatment Plans

Join hundreds of happy patients who chose RMD — the best hair transplant clinic in Indore — for safe, affordable, and long-lasting results.

📍 Visit us today or book your free consultation to start your hair restoration journey!

ディスカッション (0)1
続けるにはログインするか新規登録を行ってください
記事
· 2025年11月12日 9m read

IKO Plus: VIP in Kubernetes on IrisClusters

Power your IrisCluster serviceTemplate with kube-vip

If you're running IRIS in a mirrored IrisCluster for HA in Kubernetes, the question of providing a Mirror VIP (Virtual IP) becomes relevant. Virtual IP offers a way for downstream systems to interact with IRIS using one IP address. Even when a failover happens, downstream systems can reconnect to the same IP address and continue working.

The lead in above was stolen (gaffled, jacked, pilfered) from techniques shared to the community for vips across public clouds with IRIS by @Eduard Lebedyuk ...

Articles: ☁ vip-aws | vip-gcp | vip-azure

This version strives to solve the same challenges for IRIS on Kubernetes when being deployed via MAAS, on prem, and possibly yet to be realized using cloud mechanics with Manged Kubernetes Services.  

 

Distraction

This distraction will highlight kube-vip, where it fits into a Mirrored IrisCluster, and enabling "floating ip" for layers 2-4 with the serviceTemplate, or one of your own.  I'll walk through a quick install of the project, apply it to a Mirrored IrisCluster and attest to a failover of the mirror against the floating vip is timely and functional.

IP

Snag an available IPv4 address off your network and set it asside for use as the VIP for the IrisCluster (or a range of them).  For this distraction we value the predictability of a single ip address to support the workload.

192.168.1.152

This is the one address to rule them all, and in use for the remainder of the article.

Kubernetes Cluster

The Cluster itself is running Canonical Kubernetes on commodity hardware of 3 physical nodes on a flat 192.X network, home lab is the strictest definition of the term.

Nodes

You'll want to do this step through some slick hook to get some work done on the node during the scheduling for implementing the virutal interface/ip.  Hopefully your nodes will have some consistentcy with the NIC hardware, making the node prep easy.  However my cluster above had some varying network interfaces, as its purchase spanned over multiple prime days, so I virtualized all them by aliasing the active nic to vip0 as an interface.

I ran the following on the nodes before I get started to add a virtual nic to a physical interface and ensure it starts at boot on the nodes.

 
vip0.sh

You should see the system assign vip0 interface and tee it up for start on boot.

If your commodity network gear lets you know when something new has arrived on the network, you may get something like this after adding those interfaces on your cell.


 

 

💫 kube-vip

A descprition of kube-vip from the ether:

kube-vip provides a virtual IP (VIP) for Kubernetes workloads, giving them a stable, highly available network address that automatically fails over between nodes — enabling load balancer–like or control plane–style redundancy without an external balancer.

The commercial workload use case is prevelant for secure implementations where the IP address space is limited and DNS is a bit tricky, like HSCN connectivity for instance in England.  The less important, but thing to solve for most standing up clusters outside of the public cloud, is basically ALB/NLB like connectivity to the workloads... have solved this with Cilium, MetalLB, and now have added kube-vip to my list.

On each node, kube-vip runs as a container, via a Daemonset, that participates in a leader-election process using Kubernetes Lease objects to determine which node owns the virtual IP (VIP). The elected leader binds the VIP directly to a host network interface (for example, creating a virtual interface like eth0:vip0) and advertises it to the surrounding network. In ARP mode, kube-vip periodically sends gratuitous ARP messages so other hosts route traffic for the VIP to that node’s MAC address. When the leader fails or loses its lease, another node’s kube-vip instance immediately assumes leadership, binds the VIP locally, and begins advertising it, enabling failover. This approach effectively makes the VIP “float” across nodes, providing high-availability networking for control-plane endpoints or load-balanced workloads without relying on an external balancer.

Shorter version:

The kube-vip containers have an election to employ a leader to determine which node should own the Virtual IP, and then binds the IP to the interface accordingly on that node and advertises it to the network.  This enables the IP address to "float" across the nodes, bind only to healthy ones, and make services accessible via IP... all using Kubernetes native magic.

The install was dead simple, no immediate chart needed, but very easy to wrap up in one if desired, here we will just deploy the manifests that support its install as specified on the getting started md of the project.

 
kube-vip.yaml

Apply it! sa, rbac, daemonset.



Bask in the glory of the running pods of the Daemonset (hopefully)

IrisCluster

Nothing special here, but a vanilla mirrorMap of ( primary/failover ).

 
IrisCluster.yaml ( abbreviated )

Apply it, and make sure its running...

kubectl apply -f IrisCluster.yaml -n ikoplus

Its alive (and mirroring) !

Annotate

This binds the VirtualIP to the Service, and is the trigger for setting up the service to the vip.

Keep in mind here, we can specify a range of ip addresses too, which you can get creative with your use case, this skips the trigger and just pulls one from a range, recall we foregoed it in the install of kube-virt, but check the yaml for the commented example.


 

kubectl annotate service nginx-lb kube-vip.io/loadbalancerIPs="192.168.1.152" --overwrite -n ikoplus


Attestation

Lets launch a pod that continually polls against the smp url constructed with the vip and watch teh status codes during fail over, then we will send one of the mirrors members "casters up" and see how the vip takes over on the alternate node.

 
podviptest.yaml

 🎉

ディスカッション (0)1
続けるにはログインするか新規登録を行ってください
記事
· 2025年11月12日 3m read

Recursos relacionados con VS Code

Después de los dos webinars que realizamos centrados en VS Code ["Introducción" y "Más allá de lo básico"; en hebreo], un compañero de la comunidad inglesa preparó para los participantes algunos enlaces relacionados con recursos relevantes que enviamos como seguimiento. Los compartimos aquí también.
Por supuesto, todos estáis invitados a añadir más recursos útiles.

* No se avalan las extensiones de terceros

ディスカッション (0)1
続けるにはログインするか新規登録を行ってください
質問
· 2025年11月11日

HTTP post request rejected

Hi guys,

I'm looking to mimic this Post request URL where I'm sending a token :

So I created the below code but I'm getting "HTTP/1.1 405 Method Not Allowed" error 

Url="myurl/confirmed?id="_token
Set Httprequest1=##class(%Net.HttpRequest).%New()
Set Httprequest1.SSLConfiguration="LS2"
Set Httprequest1.Server="myserver.com" 
Set Httprequest1.Timeout=30
Set Httprequest1.Https=1
Set Httprequest1.Port=7711
set Httprequest1.ContentType="application/json"
Do Httprequest1.SetHeader("Accept","application/json")
Do Httprequest1.SetHeader("Accept-Language","en_US")
//D Httprequest1.EntityBody.Write(token)
Set tSc=Httprequest1.Post(Url)
Set StateLine=Httprequest1.HttpResponse.StatusLine
^Out2($zdt($h),1)=tSc_"|"_StateLine

So what am I doing wrong ?

Thanks

3件の新着コメント
ディスカッション (3)3
続けるにはログインするか新規登録を行ってください