Article
· Aug 12, 2020 3m read

Calling production from a REST Broker

Productions often need to receive REST requests.

Here's how to do that.

1. Create proxy Service:

/// Empty BS we would use to send Produciton requests
Class production.ProxyService Extends Ens.BusinessService
{
}

2. Add it to production as RESTService (or any other name).

3. Write your rest broker (docs, more docs)

4. In your handler method create the message and call target BP/BO like this

Include Ensemble
Class test.REST Extends %CSP.REST
{

XData UrlMap [ XMLNamespace = "http://www.intersystems.com/urlmap" ]
{
<Routes>
<Route Url="/launchProcess/:processName" Method="POST" Call="launchProcess"/>
</Routes>
}

ClassMethod launchProcess(processName As %String) As %Status
{
    #dim sc As %Status = $$$OK
    
    // BS from which we would call BP
    #dim serviceName As %String = "RESTService"
    
    #dim message = ##class(Ens.StringContainer).%New(%request.Content.Read($$$MaxStringLength))
    
    set sc = ..invokeHostAsync(processName, message, serviceName)

    quit sc
}

/// Call production host in async mode
/// Thirs arg is BS caller, testing service by default - testing must be enabled in that case
ClassMethod invokeHostAsync(targetHostName As %String, message As %Persistent, serviceName As %String = "EnsLib.Testing.Service", sessionId As %Integer = "") As %Status
{
    #dim sc As %Status
    
    #dim prod As Ens.Config.Production = ..getCurrentProduction()
    if '$isObject(prod) quit $$$ERROR($$$GeneralError, "Produciton is not running")
        
    #dim item As Ens.Config.Item = ..findConfigItem(targetHostName)
    if '$isObject(item) quit $$$ERROR($$$GeneralError, "Host " _ targetHostName _ " not found")
    
    #dim service As Ens.BusinessService
    set sc = ##class(Ens.Director).CreateBusinessService(serviceName, .service)
    if $$$ISERR(sc) quit sc
    
    if (sessionId '= "") set service.%SessionId = sessionId
    
    set sc = service.SendRequestAsync(targetHostName, message)
    
    quit sc
}

/// Get current production
ClassMethod getCurrentProduction() As Ens.Config.Production
{
    #dim sc As %Status
    #dim prodName As %String
    #dim prodState As %Integer
    
    // Get produciton name
    if '##class(Ens.Director).IsProductionRunning(.prodName) quit ""
    
    
    // Open produciton by name
    #dim prod As Ens.Config.Production = ##class(Ens.Config.Production).%OpenId(prodName, , .sc)
    if $$$ISERR(sc)
    {
        $$$LOGERROR($System.Status.GetErrorText(sc))
        quit ""
    }
    
    quit prod
}

/// Find host by name
ClassMethod findConfigItem(name As %String, businessType As %String = "", enabledOnly As %Boolean = 0) As Ens.Config.Item
{
    #dim sc As %Status
    
    // Get current production
    #dim prod As Ens.Config.Production = ..getCurrentProduction()
    if '$isObject(prod) quit ""

    // Search for our target
    #dim item As Ens.Config.Item
    #dim result As Ens.Config.Item = ""
    for i = prod.Items.Count():-1:1
    {
        set item = prod.Items.GetAt(i)
        
        if '$isObject(item) continue
        if ((businessType '= "") && (item.BusinessType() '= businessType)) || (item.Name '= name) || (enabledOnly && 'item.Enabled) continue
                        
        set result = item
        quit
    }
    quit result
}

}

And now this call:

POST host:port/webapp/launchProcess/BPName
{ "prop":"val"}

Would send a Ens.StringContainer message to the BPName.

Additionally REST broker can convert json to a persistent object and send that (it would be preferable).

Discussion (8)2
Log in or sign up to continue

Hi Eduard,

Firstly, thank you for sharing this article. Great information !

I am trying to work out best way of Error Handling within a REST Business Operation and Business Process. My Business Operation makes a call to the third party API to do certain things. I need to return the response I get back from the third party to the clinet.  Possible repsonses are, 

- 200 in which case I return requested data e.g. Patient demographics 

- 404 Patient not found 

- 500 - Internal Server Error

This is what my Business Operation currently looks like,

 
Code

Now, when the BO gets any sort of error, the operation shows an error and so does the process and same error goes back to the clinet e.g. in event of 503 client sees,

 
JSON

I need to catch this exception and modify the json response that goes back to the client.  But I am not sure what is the best way to do that. Is it something that is best done in BO or BP ? 
 

Any help or guidance on this will be much appreciated!

Utsavi

Hi @Eduard Lebedyuk ,

I'm working on what I expected to be a fairly simple web service that would accept a JSON payload as the body. I started with your code and instructions here, but I can't seem to get past this error:

While the class appears to be dispatched properly, It appears that the %request object has no Data property, and I'm not understanding why.

I searched for other examples of the %request object in the InterSystems-supplied classes and found no reference to the Data member, so am unsure how to proceed. Thanks in advance for your help!

I'm seeing some interesting behavior in the Production view, and I think it has something to do with changes made to the way active items are colored when not actually running. The Proxy Service's status icon always displays pale green when enabled, while other items that are enabled display a dark green icon. This is behavior I first noticed in HealthConnect/IRIS4Health with 2020.1 in relation to scheduled hosts. Although they were enabled, they would only display in the dark green color when the schedule started them and would revert to pale green when the schedule stopped them.

I'm just curious to know if there's a way to force the proxy service to display a "normal" dark green icon, even though it's not actually doing anything.

Hi @Eduard Lebedyuk,

I recently implemented this mechanism for handling ad-hoc message selection queries to the Google Health HL7v2 Store, and wanted to share a modification needed to satisfy the customer.

Their concern was that the service name was hard-coded in the dispatch class, and that they would have to touch the code if they used the class with a differently named service. To resolve that, I added a TargetConfigName property to the otherwise empty proxy service along with a SETTINGS parameter to reveal it through the configuration panel and allow the user to configure it.

I then modified the methods in the dispatch class to expect the service name in the URL "launch" argument and interrogate the production for the specified service's TargetConfigName value.

As always, thank you for the informative articles and answers!