Replies

I agree with Robert. I have seldom if ever seen code that calls the ResultSet Close() method. There may be temprary globals left over in the CacheTemp database but if I am not mistaken there is a system Purge method that is run as part of the standard system tasks to clear-down these temporary structures. However if you want to complete the ResultSet functionality from Start to Finish then there is no harm in calling the Close() method.

I agree with Robert. I have seldom if ever seen code that calls the ResultSet Close() method. There may be temprary globals left over in the CacheTemp database but if I am not mistaken there is a system Purge method that is run as part of the standard system tasks to clear-down these temporary structures. However if you want to complete the ResultSet functionality from Start to Finish then there is no harm in calling the Close() method.

If the servers can 'see' each other . Lets call then Server A with Namespace A and Server B with Namespace B. You do the following:

1) From the Management Portal on Server A use the System->Configuration->ECP Settings->ECP Data Servers and Add a new Server connection to Server B

2) Then within the System Configuration use the Add Remote Database to create a Database definition of the Database or Databases that Namespace B uses (Typically a namespace will be linked to one database that contains both the Application Code (Classes and Routines) and the Application Data (Globals). However it is also possible that the Application Classes and Routines live in one database and the data in another. 

3) On Server A create a new Namespace that links to the Database(s) you have mapped in step (2)

4) In the Namespace Definition of Namespace A on Server A use the Package Mapping to Map the class, selection of classes or class package into Namespace A. If the class you want to interact with is linked to an underlying Global Definition (Data, Index and Stream) then use the Global Mapping to Map the Globals used by the class into Namespace A

5) The Class (call it {PackageB}.[ClassNameB} will now appear as a class in Namespace A and if you run the methods in {PackageB}.{ClassNameB} then the code will execute in the Cache Job on Server A but any data retrievals or saves will be executed in the global(s) in the Global Database of Namespace B on Server B

There may be licensing implications to use ECP but I am not an expert on current InterSystems licensing models

The other way would be to expose the methods of Class B in Namespace B on Server B in the form of a Web Service on Server B and then invoke the methods from Server A by invoking HTTP calls to the Web Service.

Nigel 

Hi

No, the OnInit() method is called when the Business Service Starts Up, the OnTearDown() is invoked when the Business Service stops. The OnInit() is not aware of the request message and therefore It is not aware of any Request messages at this point. The ProcessInput() and more specifically the OnProcessInput() method is the first time you get to see the incoming request message and it is in the OnProcessInput() method that you decide what you are going to do with the request HL& Message, whether you route it to a Business Process based on the Ensemble HL7 Passthrough Architecture or whether you pass it to a custom Business Process However I made the assumption that your Business Service is a conventional HL7 TCP Inbound Adapter based service. 

If however it is an HTTP REST service then that is another matter altogether. If it is an HTTP REST service then by default the request will be handled by the %CSP.REST Dispatch class. The basic functionality of the %CSP.REST class is to route HTTP requests to a Business Service. You can inherit the %CSP.REST class into your own REST dispatcher class. 

I have a REST Dispatcher class in Ensemble that emulates the IRIS FHIR End Point functionality.

I have 4 csp applications defined:

/csp/endpoint

/csp/endpoint/endpointA

/csp/endpoint/endpointB

/csp/endpoint/EndPointC

All 4 csp applications invoke my custom Rest.Dispatch class (which inherits from %CSP.REST) 

I have a Business Service Class named BusinessService.MyEndPointService

I create 4 Business Services in my production

End Point General (/csp/endpoint)

End Point A (/csp/endpoint/endpointA

and so on

In the Rest Dispatch class I look at the request URL and based on the URL I invoke the OnRequest() method of the Appropriate Business Service using the Production Service Name.

However as I am writing this there is something in the back of my mind that is telling me that if you are using the EnsLib.HL7.TCP Adapter that you can reroute an incoming message to another service but I would have to go back to the documentation to remind myself of what exactly you can do and how it works.

The most common way that developers normally use is the EnsLib.HL7.MsgRouter architecture where you create routine rules that test the MSH Message Structure and you can then route the message to another specific Business Process to process that specific Message Type. This is all handled through the Management Portal->Ensemble->Build set of functions which allow you to create Business Processes, Business Rules, Transformations and so on.

If you are using HTTP REST and want more information on how I have implemented that then I would send you a far more detailed description of my implementation.

Nigel

Historically Cache and Ensemble did not support WebSockets and so you could not have two processes using the same (incoming) port but if I remember correctly IRIS supports WebSockets and though I can't remember how these work something in the depths of my mind tells me that I think WebSockets were aimed at this specific requirement

Check out the IRIS documentation on WebSockets

Nigel

Hi

Business Services support an OnInit() method and in that method you could write some code like this:

In the Business service add the following Parameters:

Parameter StartPort = 51000

Parameter LastPort = 51100

Parameter SETTINGS = "StartPort:Basic,LastPort:Basic"

/// You will need a mapping table to global in the form ^BusinessServiceProductionNames({Bussiness_Service_ClassName})={Production_Item_Name}

method OnInit() as %Status

{

    set tSC=$$$OK

    try {

        set  tPort=..#StartPort,found=0

        while 'found {

              if $l($g(^BusinessServicePorts(tPort))) {

                     set tPort=tPort=tPort+1

                     if tPort>..#LastPort {set tSC=$$$ERROR(5001,"Cannot find available Port") $$$ThrowStatus(tSC)}

                     else {continue}

             Else {

                     set ^BusinessServicePorts(tPort)=$classname(),^BusinessServicePortsIndex($classname())=tPort

                     set ..Adapter.Port=tPort,found=1 quit

              }

         }

    }

    catch ex {set tSC=ex.AsStatus()}

    if 'tSC!('found) set ..Adapter.Port=""

    quit tSC

}

method OnTearDown () as %Status

{

    set tSC=$$$OK

    try {

         if ..Adapter.Port'="" {

              kill ^BusinessServicePorts(..Adapter.Port),^BusinessServicePortsIndex($classname())

       }

    }

    catch ex {set tSC=ex.AsStatus()}

    quit tSC

}

The only thing you would need to do now is somehow notify the Client appliction would need to be notified which Port they should use to access a particular Business Service. I would suggest a WebService that accepts a request consisting of a property "BusinessServiceName" and will return ^BuinessServiceNames($classname()) and a second property "BusinessServicePort" which will return ^BusinessServicePortsIndex($classname())

You would need to supply the client with a list of available Business Service Names and what the Business Service Does. Again you could do this with a WebService which the client invokes and the WebService would return a List of Business Services and Function Description

This solution does make certain assumptions such as the client being willing/able to introduce the code to invoke the WebServices to get the list of Available Services and the Port number for a specific Business Service

The reason you need a 'LastPort" parameter is to ensure that the Business Service will not get into a loop testing every port from 51000 through to whatever the last Possible port number is in TCP/Port architecture

Nigel

Ah, ok

I'm afraid I don't have an easy answer for this one. I just tried creating an abstract class with all of the comments indicating the contents and structure and then inherited it into another new persistent class but all of the /// comments did not load into the new class. I tried compiling the class and then used the View-Class Documentation in the hope that the comments would appear in the documentation but they did not :-( 

Back to the drawing board

Nigel

As with everything in VS code you can link the code snippets at a USER level or at a WORKSPACE level

Another trick is to write snippets of code as #defines in an INCLUDE file and then reference the snippet using the syntax $$${snippet_nanme}

Then Include the include file in the classes you write.

If you work your way through the InterSystems include files you will find many examples where the ISC developers have done exactly this.

Here is an example of a #define for a block of code

#define JSONError400(%ErrorResponseMessage) ##Continue
   Set %response.Status ..#HTTP400BADREQUEST  ##Continue
   Set:'$D(tErrorResp) tErrorResp=##class(PJH.HST.JSON.Proxy.ErrorStatus).%New() ##Continue
   Set tErrorResp.error %ErrorResponseMessage_" Contact system administrator" ##Continue
   $$$WriteJSONError
   ;Do ##class(%ZEN.Auxiliary.jsonProvider).%ObjectToJSON(tErrorResp,.tVisit,,"ltw")

The #continue at the end of each line tells the .inc processor to proceed to the next line

then in your code you just write your line of code as

     do $$$JSONError400(%ErrorResponseMessage)

      .......

Nigel

We tend to forget that %SYS contains a whole load of utilities that have been around for years. Some of the utilities were wrapped into classes that could be invoked through $system (i.e. the %SYSTEM Package).

Some of the utilities are still invoked by the Management Portal. 

I answered another issue that came up in the Dev Community about how you can determine which classes and routines in %SYS are InterSystems code and those that may have been written by a developer many years ago when it was commonplace for some applications to write %Routines or %Classes especially in the MUMPS and very early Cache days where things like Package Mapping didn't exist.

The global utilities ^%G, ^%GIF and %GOF I still use periodically bcause they have such useful masking features as pointed out in other messages in this thread. Likewise ^%RO, ^%RI, %RD, ^ROUTINE, ^%RDELETE can be useful paerticularly if you want to export the .obj compiled routine code rather than the .int version. 

The commands ZLOAD, ZPRINT, ZINSERT, ZREMOVE, ZSAVE are useful for writing small little bits of routine code and once i a while I will use zload and zprint to see what code is in the routine buffer especially if I am testing some code and I get an "<UNDEF> zMyClass.int.1 zMyMethod+10" error status. Normally you open the class in studio and then select View -> Other Code and then fine the label and offset which is fine but sometines it is just quicker to execute the commands ZLOAD MyRourine.int.1  ZPRINT zMyMethod+10

Utilities like ^LOCKTAB can be useful if you want to see whats in the Lock Table and maybe release a lock. I have used this when the Management Portal fails to load properly bcause there is a rougue cache process that is consuming all available resources

^RESJOB is useful for killing off unwanted processes e.g. Rogue Processes  

The $system.Security api's can be very useful if you want to manipulate Cache Users programatically. For example your application may maintain its own User/Role classes and UI which will be referenced by the UI of the application but you also want the application Roles and Users to be created or updated as Cache Roles and Users. 

Oe of the issues with the management portal is that it will comply with the CSP session timeout limit and the example of attempting to import a large global export file for two reasons

1) It only displays N number of global names in the GOF file so even though you have clicked the "select all" check box it seems to only select those globals that have been displayed and there is no way of increasing the number of rows that it displays

2) If the load takes too long the csp session will expire and the page becomes unresponsive and you will find that only the globals that were 'checked' have been loaded whereas if you select 'run in background' and then check the 'select all' globals when it creates the background task it passes in 'true' for the parameter 'select all globals' so gets around the issue of selecting only those that are visible in the select globals form and secondly the background task has no implicit timeout associated with it so it will run for as long as it needs to. In reality it is ultimately invoking an entry point in ^%GIF in order to do the import.

I seem to remember that the Cache and Ensemble documentation used to have a section on "legacy" utilities which are all of the ones I have listed here along with ^DATABASE, ^JOURNAL and so on but I don't think that has carried over in the IRIS documentation

Nigel

HI

I have seen this behaviour before. Basically it only loads the globals that are visible in the 'select globals' form. The trick is to check the flag 'Run in the background' and then select all of the globals and it works fine

Nigel