Article
· 4 hr ago 9m read

Implement a REST Dispatch Class with optional number of arguments to multiple endpoints

My main goal of this article was to prove the use of InterSystems IRIS for Health for REST FHIR interoperability between multiple applications. In this use case, some initiating application makes a REST call to IRIS for Health (which is merely a passthrough for REST calls) to retrieve FHIR data from an Oracle Health R4 FHIR repository. Ideally, it simplifies the syntax for calling the Oracle Health APIs. 

In this article, I will demonstrate a means for implementing a REST Dispatch Class in InterSystems IRIS, where multiple endpoints can have an optional number of arguments, and all endpoints reference a single class method. In this article, I am using IRIS for Health 2024.1.2, and making REST calls to a public FHIR sandbox for an Oracle Health Millennium Platform (aka Cerner Ignite FHIR), all with sample data (No real PII or PHI).

Note that I am doing everything Unauthenticated, as the public sandbox for Cerner Ignite FHIR does not require authentication, although it does require HTTPS.

Documentation Links To Oracle Health

Sample Oracle Health FHIR Data

https://docs.google.com/document/d/e/2PACX-1vQwyX3px4qi5t1O6_El6022zYt4y...

FHIR R4 APIs for Oracle Health Millennium Platform

https://docs.oracle.com/en/industries/health/millennium-platform-apis/mf...

Approach

The overall goal is to have multiple Url Routes in the <Routes> definition, with an optional number of parameters, and implement a single Class Method for all Url Routes. There are two approaches to this, and I will describe both, provide documentation links, and sample code. The two examples are 1) Specifying Parameters, or 2) Regular Expressions in the Route Map. The example code I use in the REST Dispatch class uses the first approach.

Specifying Parameters

https://docs.intersystems.com/iris20243/csp/docbook/DocBook.UI.Page.cls?...

XData UrlMap [ XMLNamespace = "http://www.intersystems.com/urlmap" ]
 {
  <Routes>
   <Route Url="/Patient" Method="GET" Call="GetPatient" Cors="true"/> <!-- 0 parameters, returns an error --> 
   <Route Url="/Patient/:Param1" Method="GET" Call="GetPatient" Cors="true"/> <!-- 1 parameter --> 
   <Route Url="/Patient/:Param1/:Param2" Method="GET" Call="GetPatient" Cors="true"/> <!-- 2 parameters -->
   <Route Url="/Patient/:Param1/:Param2/:Param3" Method="GET" Call="GetPatient" Cors="true"/> <!-- 3 parameters -->
  </Routes>
 }

Regular Expressions in the Route Map

https://docs.intersystems.com/iris20243/csp/docbook/DocBook.UI.Page.cls?...

XData UrlMap [ XMLNamespace = "http://www.intersystems.com/urlmap" ]
 {
  <Routes>
   <Route Url="/Patient" Method="GET" Call="GetPatient" Cors="true"/> <!-- 0 parameters, returns an error --> 
   <Route Url="/Patient/([^/]*)" Method="GET" Call="GetPatient" Cors="true"/> <!-- 1 parameter -->
   <Route Url="/Patient/([^/]*)/([^/]*)" Method="GET" Call="GetPatient" Cors="true"/> <!-- 2 parameters -->
   <Route Url="/Patient/([^/]*)/([^/]*)/([^/]*)" Method="GET" Call="GetPatient" Cors="true"/> <!-- 3 parameters -->
  </Routes>
 }

System Setup

I started with a Windows 11 Virtual Machine on my MacBookPro using VM Ware, with hostname = "vmirishealth-dlf". I enabled the IIS server to provide the Web Server needed for the IRIS installation.  I then installed IRIS for Health 2024.1.2, activated with a license key, then created a Namespace and corresponding database called CernerEMR. I then created a SSL/TLS Configuration called "ISC.Demo.Cerner.SSL".

I then created a REST Web Application called "/api/cerneremr" that implements a REST Dispatch Class called "ISC.Demo.REST.CernerServiceDispatch" that will be created below.

 ObjectScript Code

In my CernerEMR namespace, I created a class called ISC.Demo.REST.CernerServiceDispatch which extends %CSP.REST, added a Parameter defining portions of the URL for the Cerner APIs that could be used within the class, and defined the UrlMap with my REST Endpoints. Note I also defined Parameter HandleCorsRequest = 1; and Cors="true" for each route in my UrlMap.

Class ISC.Demo.REST.CernerServiceDispatch Extends %CSP.REST
{

Parameter HandleCorsRequest = 1;
Parameter FHIRURL = "/r4/ec2458f2-1e24-41c8-b71b-0e701af7583d";
XData UrlMap [ XMLNamespace = "http://www.intersystems.com/urlmap" ]
 {
  <Routes>
   <Route Url="/Patient" Method="GET" Call="GetPatient" Cors="true"/> <!-- 0 parameters, returns an error -->
   <Route Url="/Patient/:Param1" Method="GET" Call="GetPatient" Cors="true"/> <!-- 1 parameter -->
   <Route Url="/Patient/:Param1/:Param2" Method="GET" Call="GetPatient" Cors="true"/> <!-- 2 parameters -->
   <Route Url="/Patient/:Param1/:Param2/:Param3" Method="GET" Call="GetPatient" Cors="true"/> <!-- 3 parameters -->
  </Routes>
 }
}

I then added a ClassMethod called MakeHttpRequest that I could reuse for additional calls to Cerner for resources beyond the Patient (Practitioner, Locations, Appointments, etc which are not shown in this article, but would follow the same pattern used for the Patient resource). As part of this method, I added two headers required by the Oracle Health R4 APIs.

ClassMethod MakeHttpRequest() As %Net.HttpRequest
{
    // generate the HTTP REST request	
    set httprequest=##class(%Net.HttpRequest).%New()
    set httprequest.Https=1 ; to ensure HTTPS is used
    set httprequest.SSLConfiguration = "ISC.Demo.Cerner.SSL" ; required if using https
    set httprequest.Server="fhir-open.cerner.com"
    do httprequest.SetHeader("Accept","application/fhir+json")	
    do httprequest.SetHeader("Accept-Encoding","gzip,deflate")
    Quit httprequest
}

I then created a ClassMethod called GetPatient (as that is the method called for each REST Endpoint in my UrlMap). Note that I used an example from the documentation for using a variable number of arguments. This allowed me to use one single method for multiple endpoints, each with a variable number of arguments. The class method determines how many arguments have been passed in, and then formulates the correct parameter list that is suffixed on the REST URL.

I created the REST request, added my dynamic parameter list, sent that as a GET Request to the Oracle Health R4 FHIR Endpoint, and then forwarded the response payload from Oracle Health as JSON in my method's response.

Specifying a Variable Number of Arguments for a Method in InterSystems IRIS

https://docs.intersystems.com/iris20242/csp/docbook/DocBook.UI.Page.cls?...

ClassMethod GetPatient(param1... As %String) As %Status
{
    set httprequest = ..MakeHttpRequest()
    set tURL = $PARAMETER($THIS,"FHIRURL") 
    
    // dynamically build the parameter list based on unknown number of arguments
    set params = $GET(param1,0)
    // ensure we have a least 1 parameter, if not error
    if params > 0 {
        set paramList = "?"_$GET(param1(1))
        For i = 2 : 1 : params {
            set paramList = paramList _"&"_$GET(param1(i))
        }

        set status=httprequest.Get(tURL_"/Patient/"_paramList)
        if $$$ISERR(status) {
             do $system.OBJ.DisplayError()
             Quit $$$ERROR(status)
        } else {
             set response=httprequest.HttpResponse
        }

        set jsonObject = {}.%FromJSON(response.Data)
    } else {
        set jsonObject = {"errorMessage":"No Parameters passed in"}
    }
    do ##class(%JSON.Formatter).%New().Format(jsonObject)
    Quit $$$OK
}

To summarize with all of the code created, here is the full class:

Class ISC.Demo.REST.CernerServiceDispatch Extends %CSP.REST
{

Parameter HandleCorsRequest = 1;
Parameter FHIRURL = "/r4/ec2458f2-1e24-41c8-b71b-0e701af7583d";
XData UrlMap [ XMLNamespace = "http://www.intersystems.com/urlmap" ]
{
 <Routes>
  <Route Url="/Patient" Method="GET" Call="GetPatient" Cors="true"/>  <!-- 0 parameters which should error out -->
  <Route Url="/Patient/:Param1" Method="GET" Call="GetPatient" Cors="true"/> <!-- 1 parameter -->
  <Route Url="/Patient/:Param1/:Param2" Method="GET" Call="GetPatient" Cors="true"/> <!-- 2 parameters -->
  <Route Url="/Patient/:Param1/:Param2/:Param3" Method="GET" Call="GetPatient" Cors="true"/> <!-- 3 parameters -->
 </Routes>
}

ClassMethod MakeHttpRequest() As %Net.HttpRequest
{
    // generate the HTTP REST request	
    set httprequest=##class(%Net.HttpRequest).%New()
    set httprequest.Https=1
    set httprequest.SSLConfiguration = "ISC.Demo.Cerner.SSL" ; required if using https
    set httprequest.Server="fhir-open.cerner.com"
    do httprequest.SetHeader("Accept","application/fhir+json")	
    do httprequest.SetHeader("Accept-Encoding","gzip,deflate")
    Quit httprequest
}

ClassMethod GetPatient(param1... As %String) As %Status
 {
    set httprequest = ..MakeHttpRequest()
    set tURL = $PARAMETER($THIS,"FHIRURL") 
    
    // dynamically build the parameter list based on unknown number of arguments
    set params = $GET(param1,0)
    // ensure we have a least 1 parameter, if not error
    if params > 0 {
        set paramList = "?"_$GET(param1(1))
        For i = 2 : 1 : params {
            set paramList = paramList _"&"_$GET(param1(i))
        }

        set status=httprequest.Get(tURL_"/Patient/"_paramList)
        if $$$ISERR(status) {
             do $system.OBJ.DisplayError()
             Quit $$$ERROR(status)
        } else {
             set response=httprequest.HttpResponse
        }

        set jsonObject = {}.%FromJSON(response.Data)
    } else {
        set jsonObject = {"errorMessage":"No Parameters passed in"}
    }
    do ##class(%JSON.Formatter).%New().Format(jsonObject)
    Quit $$$OK
 }
}

Then an example REST call from my favorite REST Client (I used Insomnia 10.2.0) to retrieve Patient data (using 2 parameters) from Oracle Health R4 FHIR repository.

IRIS REST Call
GET http://vmirishealth-dlf/api/cerneremr/Patient/family=SMART/given=Nancy

which gets transformed into

Cerner REST Call
GET https://fhir-open.cerner.com/r4/ec2458f2-1e24-41c8-b71b-0e701af7583d/Patient?family=SMART&given=Nancy

This request returns 4 patients that satisfies the search request. The response below is truncated to show only the total number of resources, and the id of the first patient, which is 12724066

{
	"resourceType": "Bundle",
	"id": "6227ca9f-30da-4e49-80c1-902d34b420a7",
	"type": "searchset",
	"total": 4,
	"link": [
		{
			"relation": "self",
			"url": "https://fhir-open.cerner.com/r4/ec2458f2-1e24-41c8-b71b-0e701af7583d/Patient?family=SMART&given=Nancy"
		}
	],
	"entry": [
		{
			"fullUrl": "https://fhir-open.cerner.com/r4/ec2458f2-1e24-41c8-b71b-0e701af7583d/Patient/12724066",

This is an example REST call that asks for Patient by id (12724066), using 1 parameter.

IRIS REST Call
GET http://vmirishealth-dlf/api/cerneremr/Patient/_id=12724066

which gets transformed into

Cerner REST Call
GET https://fhir-open.cerner.com/r4/ec2458f2-1e24-41c8-b71b-0e701af7583d/Patient?_id=12724066

and the truncated response that shows 1 resource returned, and the name of the patient.

{
	"resourceType": "Bundle",
	"id": "916f95b6-5f95-4722-9425-3b5019f6c45c",
	"type": "searchset",
	"total": 1,
	"link": [
		{
			"relation": "self",
			"url": "https://fhir-open.cerner.com/r4/ec2458f2-1e24-41c8-b71b-0e701af7583d/Patient?_id=12724066"
		}
	],
	"entry": [
		{
			"fullUrl": "https://fhir-open.cerner.com/r4/ec2458f2-1e24-41c8-b71b-0e701af7583d/Patient/12724066",
			"resource": {
				"resourceType": "Patient",
				"id": "12724066",
				"meta": {
					"versionId": "1465",
					"lastUpdated": "2024-10-07T05:02:43.000Z"
				},
				"text": {
					"status": "extensions",
					"div": "<div xmlns=\"http://www.w3.org/1999/xhtml\"><p><b>Patient</b></p><p><b>Name</b>: SMARTS II, NANCYS II</p><p><b>Status</b>: Active</p><p><b>DOB</b>: Sep 15, 1990</p><p><b>Birth Sex</b>: Female</p><p><b>Administrative Gender</b>: Female</p><p><b>Marital Status</b>: Divorced</p></div>"

Hopefully this technique comes in handy.

TBD - I plan to record a YouTube video and add the code to https://openexchange.intersystems.com.

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