When I was migrating a system I had to export and import SQL Gateway Connection, so in the source system I exported to a tab delimited file and in the target system I imported the definitions.

The code to create the imported definition is:

Set SQLConnection=##class(%SQLConnection).%New()
	Set SQLConnection.DSN=$p(line,tab,1)
	Set SQLConnection.Name=$p(line,tab,2)
	Set SQLConnection.ReverseOJ=$p(line,tab,3)
	Set SQLConnection.URL=$p(line,tab,4)
	Set SQLConnection.Usr=$p(line,tab,5)
	Set SQLConnection.bUnicodeStream=$p(line,tab,6)
	Set SQLConnection.classpath=$p(line,tab,7)
	Set SQLConnection.driver=$p(line,tab,8)
	Set SQLConnection.isJDBC=$p(line,tab,9)
	Set SQLConnection.needlongdatalen=$p(line,tab,10)
	Set SQLConnection.noconcat=$p(line,tab,11)
	Set SQLConnection.nodefq=$p(line,tab,12)
	Set SQLConnection.nofnconv=$p(line,tab,13)
	Set SQLConnection.nvl=$p(line,tab,14)
	Set SQLConnection.properties=$p(line,tab,15)
	Set SQLConnection.pwd=$p(line,tab,16)
	Set SQLConnection.useCAST=$p(line,tab,17)
	Set SQLConnection.useCASTCHAR=$p(line,tab,18)
	Set SQLConnection.useCOALESCE=$p(line,tab,19)
	Set SQLConnection.xadriver=$p(line,tab,20)
	Set sc=SQLConnection.%Save()

If I recall correctly I had a similar issue in some old Ensemble system but I'm not 100% sure the global was really that.

If you can stop the production, then I think it's safe to kill ^Ens.AppData, with the production running I don't think killing it it's a good idea.

A curiosity, do you use EnsLib.SQL.Snapshot class in some SQL host (BO)?
Is the content of ^Ens.AppData somewhat related to EnsLib.SQL.Snapshot?
Looking at the content of ^Ens.AppData, what type of adapter/operation is likely using it?

In the...good old days, we used to do that in one line 😂

s node="array" f  s node=$q(@node) q:node=""  w node,"=",@node,!

array(1)=1
array(1,1)=1,1
array(1,1,1)=1,1,1
array(1,1,1,1)=1,1,1,1
array(1,1,2)=1,1,2
array(1,1,3)=1,1,3
array(2)=2
array(2,1)=2,1
array(2,1,1)=2,1,1

$query argument can be a global or a public variable, so when using local variable array change the method signature:

ClassMethod test(array) [ public= array ]

Or use a temp global or, even better, a Process Private Global instead.

You can extend EnsLib.SOAP.OutboundAdapter and add support for LocalInterface and use the "new" adapter in your BO.

With "inspiration" 😁 from EnsLib.HTTP.OutboundAdapter, something like:

Class Community.adapter.SoapOutbound Extends EnsLib.SOAP.OutboundAdapter
{

/// In a multi-homed system, specify which network interface the TCP connection should go through.  An empty value means to use any interface. <br/>/// To be able to bind to IPv6 interfaces you may need to enable IPv6 in your instance.  This is done in the System Management Portal under /// System Administration > Configuration > Additional Settings > Startup, by editing the IPv6 setting.Property LocalInterface As%String(MAXLEN = 250);Parameter SETTINGS = "LocalInterface:Connection:selector?context={Ens.ContextSearch/TCPLocalInterfaces}";
Method OnInit() As%Status
{
	Set ..%Client.HttpRequest=##class(%Net.HttpRequest).%New()
	Set ..%Client.HttpRequest.LocalInterface=$ZStrip($P(..LocalInterface,"("),"*W")
	Return##super()
}

}

I did not tested it, try it and let us know.

Nete, if for any reason you reinstantiate ..%Client.HttpRequest, then you need to set the LocalInterface  property.

Having LocalInterface support in EnsLib.SOAP.OutboundAdapter "out of the box" can be worth an entry in the ideas portal.

That won't work, you are just setting a %Net.HttpRequest object in a (private) variable called HttpRequest that is destroyed when the method ends.

You need to set/change the %Net.HttpRequest instance contained in the ..%Client property of the adapted AFTER the web service client class is set/instantiated in ..%Client.

Did you had a chance to test the modified adapter I've posted? Maybe tomorrow I'll have time to test it.

Another option, in your BO add:

Property LocalInterface As%String(MAXLEN = 255);Parameter SETTINGS = "LocalInterface:Connection:selector?context={Ens.ContextSearch/TCPLocalInterfaces}";

Then before calling the service (..Adaper.Invoke*()) add this:

If 'IsObject(..Adapter.%Client.HttpRequest) {
		Set..Adapter.%Client.HttpRequest=##class(%Net.HttpRequest).%New()
	}
	Set..Adapter.%Client.HttpRequest.LocalInterface=$ZStrip($P(..LocalInterface,"("),"*W")
	

Edit to add: Why are you extending EnsLib.SOAP.GenericOperation?

For a SOAP BO using EnsLib.SOAP.OutboundAdapter the class signature is supposed to be:

Class osuwmc.Nutrition.OSU.CBOARDNetMenuOperation.CBORDHL7Port Extends Ens.BusinessOperation
 

Sorry, my bad, a typo, please replace IsObject with $IsObject

If '$IsObject(..Adapter.%Client.HttpRequest) {

Using Process Private Global:

ClassMethod setLayout(layout) As%Status
{
	MERGE ^||layout = layout

	Set node="^||layout"For  {
		Set node=$query(@node) Q:node=""Write node,"=",@node,!
	}
	Quit$$$OK
}

Using local variable array:

ClassMethod arrayQuery(myArrayLocalName) [ PublicList = myArrayLocalName ]
{
	Set node="myArrayLocalName"For  {
		Set node=$query(@node) Q:node=""Write node,"=",@node,!
	}
}

Note: the idea of using Process Private Global was INSTEAD of local variable array all around, not merging etc.

If the "internal HTTP" is using the PWS (Private Web Server), then NO, NIET, it's a bad, very bad idea.

DO NOT USE THE PWS for anything apart testing and local PC playing.

For ANYTHING serious, install and use a "real" web server (Apache, NGINX or IIS).

It has been so since EVER and has been documented since ever, but it seems that many people still used the PWS.

I guess that was (one of?) the reason that drove InterSystems to completely remove PWS since.....2024.1? (or was 2?).

From Ensemble 2018..3 documentation (the oldest available online):

Note:
When installing Caché and Ensemble, this private version of Apache is installed to ensure that:

The Management Portal runs out of the box.

An out-of-the-box testing capability is provided for development environments.

The private Apache web server is not supported for any other purpose.

For deployments of http-based applications, including CSP, Zen, and SOAP over http or https, you should not use the private web server for any application other than the Management Portal; instead, you must install and deploy one of the supported web servers (for information, see the section “Supported Web Servers” in the online InterSystems Supported PlatformsOpens in a new tab document for this release).

Additional info:

Discontinue Apache web server installations - FAQ

Effective with the first EM release in 2026, the private web server will be discontinued; at that point, upgrades of existing InterSystems IRIS instances will remove the private web server.

My BIO can be found in my DC profile, so I just copy/pasted it from there.

I invite everyone who posted here to add/copy this info in their profile 😀

The first two systems I worked with using InterSystems technology were a PDP-11 running M11+ and a VAX 11/750 running M/VX. Too many years ago to count! 😊
Since then I've used most, if not all, InterSystems products up to IRIS and HealthShare today.
I'm Italian living in Switzerland and I work as Senior Consultant at GAIVOTA consultin SA, we provide professional services for InterSystems and other technologies.
Fun Fact: apart from DC, I don't have ANY social account! 😁
Hobbies: technology and, when I can, I like diving (PADI Advanced) in nice tropical seas
Linkedin: see Fun Fact 😂

I've attended most of the InterSystems conferences in the last decades and I'll be in Orlando @ READY 2025.

I assume that your interoperability is using Application ACK, so the response ACK in traced (there is an Ens.MessageHeader and HL7 Message reponse with ACK).

select req.TargetConfigName as BusinessOperation,
DATEDIFF('ms',req.TimeCreated, res.TimeCreated) as ResponseTime
from %PARALLEL Ens.MessageHeader req, Ens.MessageHeader res
where req.SessionId=res.SessionId
and req.TargetConfigName = ?
and %internal(res.SourceBusinessType)=3and %internal(req.TargetBusinessType)=3and req.TimeCreated between ? and ?


First placeholder/parameter (?) is the BO name.

To narrow the scope (and runtime) you can use the second and third placeholder/parameter to limit the date/time range.

Note that the returned ResponseTime will include the time the request remains in queue (if any).

If you have many messages, %PARALLEL will help to speed up.

Set ..Adapter.WebServiceClientClass = "CBORDHL7WSService.CBORDHL7Por.....

If you reset the WebServiceClientClass the ..Adapter.%Client property is recreated and the ..Adapter.%Client.HttpRequest.LocalInterface property you modified in OnInit() in lost.

Set the WebServiceClientClass  in the BO settings from the portal and don't change it from code.

If you REALLY need (do you?) to change the WebServiceClientClass  from code (unlikely since you use a constant string), then when you change it:

Set ..Adapter.WebServiceClientClass = "CBORDHL7WSService.CBORDHL7Por.....
If '$IsObject(..Adapter.%Client.HttpRequest) {
    Set ..Adapter.%Client.HttpRequest = ##class(%Net.HttpRequest).%New()
}
Set ..Adapter.%Client.HttpRequest.LocalInterface = $ZStrip($P(..LocalInterface,"("),"*W")
 

...whenever you call code outside of the routine as opposed to calling code in the same routine, some execution speed is lost. For reports churning through millions of transactions this lost speed might be noticeable.

Can you reproduce this with a small/simple self contained example using code in same class/routine and same code calling different classes/routines where the difference is noticeable?

Ciao Robert,

I'm not sure your test address the original question, that is:

"whenever you call code outside of the routine as opposed to calling code in the same routine, some execution speed is lost"

In your test1 you do:

[call class method] -> [call class method in other class] -> [call a function in same class]

In your test2 you do:

[call class method] -> [call a function in same class]

This way you are not comparing "call code outside of the routine as opposed to calling code in the same routine", you are ADDING an additional call/level.

In addition, to measure the time penalty for calling inside/outside "routine" (or class), adding 100M global access does not help in getting a reliable measure because too many factors may change the time measured between different runs.
Finally a doubt, are we sure that calling a function and calling a class method is a fair comparison? May be or may be not, I don't know.

To mesure the difference I've a differente approach then Robert and concentrate on same/different class calls without any additional overhead.
Please note that is my no means a demonstration of best/worst approach, IMHO there is a better solution, see my next post.

I've created two classes and simulated a big number of calls of a class method within the same class and same code calling a class method in a different class.

Class Community.perf.Class1
{

ClassMethod Compare(NumCalls As%Integer)
{
	Set SingleStart=$zhDo..SingleClassCalls(NumCalls)
	Set SingleEnd=$zhSet MultiStart=$zhDo..MultiClassCalls(NumCalls)
	Set MultiEnd=$zhSet Difference=(MultiEnd-MultiStart)-(SingleEnd-SingleStart)
	Set Percent=1-((SingleEnd-SingleStart)/(MultiEnd-MultiStart))*100Write"Same class calls: ",SingleEnd-SingleStart,!
	Write"Diff class calls: ",MultiEnd-MultiStart,!
	Write"Difference: ",Difference," ",Percent,"%",!
}

ClassMethod SingleClassCalls(NumCalls As%Integer)
{
	For i=1:1:NumCalls {
		Setx=..Compute(NumCalls)
	}
}

ClassMethod MultiClassCalls(NumCalls As%Integer)
{
	For i=1:1:NumCalls {
		Setx=##class(Community.perf.Class2).Compute(NumCalls)
	}
}

ClassMethod Compute(Num As%Integer)
{
	;Quit NumSet ret=Num
	Quit ret
}

}
Class Community.perf.Class2
{

ClassMethod Compute(Num As%Integer)
{
	;Quit NumSet ret=Num
	Quit ret
}

}

Calling the Compare() method with 100M iterations the result is:

EPTEST>do##class(Community.perf.Class1).Compare(100000000)
Same class calls: 20.992929
Diff class calls: 31.460201
Difference: 10.467272 33.27147210534351%

Please note that changing the Compute() method in both classes to:

ClassMethod Compute(Num As %Integer)
{
    Quit Num
}

Makes a BIG difference:

EPTEST>do##class(Community.perf.Class1).Compare(100000000)
Same class calls: 4.606181
Diff class calls: 5.52639
Difference: .920209 16.6511773508565266%

Using the first method code it adds the handling of the stack, therefore it takes longer and, for 100M calls, the difference is noticeable.
I think the first with "some" stack handling is a more realistic use case.

In conclusion, there is a difference and is measurable. Is it noticeable? Not much, IMHO in a computation that probably takes many minutes duplicating codes is not worth the gain.

There is however a better approach without duplicating the code, see my next post.

IMHO the best approach is to take advantage of the  object-oriented development environment that IRIS provide and have the common functions/methods in a single (or multiple) classes, possible abstract classes, and inherit them in the "main" class.

Class Community.perf.ClassMain Extends Community.perf.ClassAbs
{

ClassMethod Compare(NumCalls As%Integer)
{
	Set Start=$zhDo..ClassCalls(NumCalls)
	Set End=$zhWrite"Class calls: ",End-Start,!
}

ClassMethod ClassCalls(NumCalls As%Integer)
{
	For i=1:1:NumCalls {
		Setx=..Compute(NumCalls)
	}
}

}
Class Community.perf.ClassAbs [ Abstract ]
{

ClassMethod Compute(Num As%Integer)
{
	;Quit NumSet ret=Num
	Quit ret
}

}

How about performance?

EPTEST>do##class(Community.perf.ClassMain).Compare(100000000)
Class calls: 31.675438

In latest version of IRIS (and Cachè?) inherited members method code is no longer duplicated, so there is no difference then using separate classes but I think this approach is more modern, elegant and, depending on situations, MUCH more flexible,

Do you need to decode a Base64 stream or you just need to read a stream to a string?

If so, then:

Set string=stream.Read($$$MaxStringLength)

What product? IRIS, IRIS fir Health, HealthShare HC or HealthShare?

How did you created in the Management Portal the new database/namespace?

From the ISCSOAP log it's evident that the the content of encodedMessage property/tag is not the encoded HL7 raw message but it was assigned with an oref (object reference, an object instance) of the osuwmc.Nutrition.OSU.CBOARDNetMenuRequest.SendMessageRequest class.

This is not what the code you posted is doing, so I guess the actual code is different or the encodedMessage property is set to an oref after the code you posted.

It looks like somewhere after that code there is a:

Set CBORDRequest.encodedMessage=CBORDRequest

Beside that, my curiosity:

set a = $SYSTEM.Encryption.Base64Encode((pMsgOut.RawContent))
Why two parenthesis?

set CBORDRequest.encodedMessage = $Get(a)
Why using $Get for a variable that is indeed/for sure defined?

Fixed for you:

/// Returns Age in years from DOB in YYYYMMDD format/// Make sure DOB is not null ("") before calling the function.ClassMethod GetAge(DateOfBirth As%String) As%Integer
{
    Set today=$zd($h,8)	
    Set age=$e(today,1,4)-$e(DateOfBirth,1,4)
    If$e(today,5,8) < $e(DateOfBirth,5,8) Set age=age-1Quit age
}

Not that complex after all.

The log from ^ISCLOG you posted does not reflect the code you posted.

Something MUST happen to the CBORDRequest.encodedMessage property AFTER the code you posted before the request is sent.

Please post as much code you can up until InvokeClient().

How did you installed set up your FHIR Server?

When installed using the IRIS portal the FHIR Web Application is automatically created and configured with the following Application Roles :
%DB_HSCUSTOM
%DB_HSLIB
%DB_HSSYS
%DB_<YourNamespace>X0001R
%DB_<YourNamespace>X0001V
%HS_DB_<YourNamespace>
%HS_ImpersonateUser

What's the datatype of the HL7 message in SendMessageRespose?

I assume that the HL7 message in SendMessageRespose is a %String.

String variable contains the HL7 message, then:

Set HL7MessageResponse=##class(EnsLib.HL7.Message).ImportFromString(String,.sc)
    ; handle sc error hereSet DocType=##class(EnsLib.HL7.Schema).ResolveSchemaTypeToDocType(HL7MessageResponse.TypeVersion,HL7MessageResponse.Name)
    Set sc=HL7MessageResponse.PokeDocType(DocType)
    ; handle sc error here

No, it doesn't.

If the variable String contains a Base 64 encoded HL7 message, then:

Set HL7MessageResponse=##class(EnsLib.HL7.Message).ImportFromString($system.Encryption.Base64Decode(String),.sc)
    ; handle sc error hereSet DocType=##class(EnsLib.HL7.Schema).ResolveSchemaTypeToDocType(HL7MessageResponse.TypeVersion,HL7MessageResponse.Name)
    Set sc=HL7MessageResponse.PokeDocType(DocType)
    ; handle sc error here

After that, the variable HL7MessageResponse is an instance (OREF, object reference) of an Ens.HL7.Message with proper DocType, Name, etc. that you can use as HL7 response back to your HL7 BP Router.