Initial Release
The goal of this formation is to learn InterSystems’ interoperability framework, and particularly the use of:
TABLE OF CONTENTS:
This is the IRIS Framework.
The components inside of IRIS represent a production. Inbound adapters and outbound adapters enable us to use different kind of format as input and output for our databse. The composite applications will give us access to the production through external applications like REST services.
The arrows between them all of this components are messages. They can be requests or responses.
In our case, we will read lines in a csv file and save it into the IRIS database.
We will then add an operation that will enable us to save objects in an extern database too, using JDBC. This database will be located in a docker container, using postgre.
Finally, we will see how to use composite applications to insert new objects in our database or to consult this database (in our case, through a REST service).
The framework adapted to our purpose gives us:
For this formation, you’ll need:
In order to have access to the InterSystems images, we need to go to the following url: http://container.intersystems.com. After connecting with our InterSystems credentials, we will get our password to connect to the registry. In the docker VScode addon, in the image tab, by pressing connect registry and entering the same url as before (http://container.intersystems.com) as a generic registry, we will be asked to give our credentials. The login is the usual one but the password is the one we got from the website.
From there, we should be able to build and compose our containers (with the docker-compose.yml
and Dockerfile
files given).
We will open a Management Portal. It will give us access to an webpage where we will be able to create our production. The portal should be located at the url: http://localhost:52775/csp/sys/UtilHome.csp?$NAMESPACE=IRISAPP. You will need the following credentials:
LOGIN: SuperUser
PASSWORD: SYS
A part of the things we will be doing will be saved locally, but all the processes and productions are saved in the docker container. In order to persist all of our progress, we need to export every class that is created through the Management Portal with the InterSystems addon ObjectScript
:
We will have to save our Production, Record Map, Business Processes and Data Transfromation this way. After that, when we close our docker container and compose it up again, we will still have all of our progress saved locally (it is, of course, to be done after every change through the portal). To make it accessible to IRIS again we need to compile the exported files (by saving them, InterSystems addons take care of the rest).
We can now create our first production. For this, we will go through the [Interoperability] and [Configure] menus:
We then have to press [New], select the [Formation] package and chose a name for our production:
Immediatly after creating our production, we will need to click on [Production Settings] just above the [Operations] section. In the right sidebar menu, we will have to activate [Testing Enabled] in the [Development and Debugging] part of the [Settings] tab (don’t forget to press [Apply]).
In this first production we will now add Business Operations.
A Business Operation (BO) is a specific operation that will enable us to send requests from IRIS to an external application / system. It can also be used to directly save in IRIS what we want.
We will create those operations in local, that is, in the Formation/BO/
file. Saving the files will compile them in IRIS.
For our first operation we will save the content of a message in the local database.
We need to have a way of storing this message first.
Storage classes in IRIS extends the type %Persistent
. They will be saved in the intern database.
In our Formation/Table/Training.cls
file we have:
Class Formation.Table.Training Extends %Persistent {
Property Name As %String;
Property Room As %String;
}
Note that when saving, additional lines are automatically added to the file. They are mandatory and are added by the InterSystems addons.
This message will contain a Formation
object, located in the Formation/Obj/Formation.cls
file:
Class Formation.Obj.Formation Extends (%SerialObject, %XML.Adaptor) {
Property Nom As %String;
Property Salle As %String;
}
The Message
class will use that Formation
object, src/Formation/Msg/FormationInsertRequest.cls
:
Class Formation.Msg.FormationInsertRequest Extends Ens.Request {
Property Formation As Formation.Obj.Formation;
}
Now that we have all the elements we need, we can create our operation, in the Formation/BO/LocalBDD.cls
file:
Class Formation.BO.LocalBDD Extends Ens.BusinessOperation {
Parameter INVOCATION = "Queue";
Method InsertLocalBDD(pRequest As Formation.Msg.FormationInsertRequest, Output pResponse As Ens.StringResponse) As %Status
{
set tStatus = $$$OKtry{ set pResponse = ##class(Ens.Response).%New() set tTraining = ##class(Formation.Table.Training).%New() set tTraining.Name = pRequest.Formation.Nom set tTraining.Room = pRequest.Formation.Salle $$$ThrowOnError(tTraining.%Save()) } catch exp { Set tStatus = exp.AsStatus() } Quit tStatus
}
XData MessageMap
{
InsertLocalBDD
}}
The MessageMap gives us the method to launch depending on the type of the request (the message sent to the operation).
As we can see, if the operation received a message of the type Formation.Msg.FormationInsertRequest
, the InsertLocalBDD
method will be called. This method will save the message in the IRIS local database.
We now need to add this operation to the production. For this, we use the Management Portal. By pressing the [+] sign next to [Operations], we have access to the [Business Operation Wizard]. There, we chose the operation class we just created in the scrolling menu.
Double clicking on the operation will enable us to activate it. After that, by selecting the operation and going in the [Actions] tabs in the right sidebar menu, we should be able to test the operation (if not see the production creation part to activate testings / you may need to start the production if stopped).
By doing so, we will send the operation a message of the type we declared earlier. If all goes well, the results should be as shown below:
Showing the visual trace will enable us to see what happened between the processes, services and operations. here, we can see the message being sent to the operation by the process, and the operation sending back a response (that is just an empty string).
Business Processes (BP) are the business logic of our production. They are used to process requests or relay those requests to other components of the production.
Business Processes are created within the Management Portal:
We are now in the Business Process Designer. We are going to create a simple BP that will call our operation:
A BP has a Context. It is composed of a request class, the class of the input, and of a response class, the class of the output. Business Processes only have one input and one output. It is also possible to add properties.
Since our BP will only be used to call our BO, we can put as request class the message class we created (we don’t need an output as we just want to insert into the database).
We then chose the target of the call function : our BO. That operation, being called has a callrequest property. We need to bind that callrequest to the request of the BP (they both are of the class Formation.Msg.FormationInsertRequest
), we do that by clicking on the call function and using the request builder:
https://github.com/user-attachments/assets/5a663571-d505-4115-8cae-5538259947e1
We can now save this BP (in the package ‘Formation.BP‘ and under the name ‘InsertLocalBDD‘ or ‘Main’, for example). Just like the operations, the processes can be instantiated and tested through the production configuration, for that they need to be compiled beforehand (on the Business Process Designer screen).
Our Process for now only passes the message to our Operation. We are going to complexify it so that the BP will take as input one line of a CSV file.
In order to read a file and put its content into a file, we need a Record Map (RM). There is a Record Mapper specialized for CSV files in the [Interoperability > Build] menu of the management portal:
We will create the mapper like this:
You should now have this Record Map:
Now that the Map is created, we have to generate it (with the Generate button). We now need to have a Data Transformation from the record map format and an insertion message.
We will find the Data Transformation (DT) Builder in the [Interoperability > Builder] menu. We will create our DT like this (if you can’t find Formation.RM.Csv.Record
, maybe you didn’t generate the record map):
Now, we can map the different fields together:
https://github.com/user-attachments/assets/a7b47788-1c6f-4d82-88e7-f33f985cce40
The first thing we have to change is the BP’s request class, since we need to have in input the Record Map we created.
We can then add our transformation (the name of the process doesn’t change anything, from here we chose to name it Main
):
https://github.com/user-attachments/assets/37c075ec-cc8a-4020-b263-dfd332120e54
The transform activity will take the request of the BP (a Record of the CSV file, thanks to our Record Mapper), and transform it into a FormationInsertRequest
message. In order to store that message to send it to the BO, we need to add a property to the context of the BP.
We can now configure our transform function so that it takes it input as the input of the BP and saves its output in the newly created property. The source and target of the RmToMsg
transformation are respectively request
and context.Msg
:
We need to do the same for Call BO
. Its input, or callrequest
, is the value stored in context.msg
:
https://github.com/user-attachments/assets/1d7c6bff-246c-4145-9956-8a33033c938d
In the end, the flow in the BP can be represented like this:
With the [+] sign, we can add our new process to the production (if not already done). We also need a generic service to use the record map, we use EnsLib.RecordMap.Service.FileService
(we add it with the [+] button next to services). We then parameter this service:
https://github.com/user-attachments/assets/39ea761f-56ad-440c-adf7-fac7b46d8acd
We should now be able to test our BP.
We test the whole production this way:
https://github.com/user-attachments/assets/37535201-50c6-42a4-a576-2eb1514483d6
In System Explorer > SQL
menu, you can execute the command
SELECT
ID, Name, Room
FROM Formation_Table.Training
to see the objects we just saved.
In this section, we will create an operation to save our objects in an extern database. We will be using the JDBC API, as well as the other docker container that we set up, with postgre on it.
Our new operation, in the file Formation/BO/RemoteBDD.cls
is as follows:
Include EnsSQLTypes
Class Formation.BO.RemoteBDD Extends Ens.BusinessOperation
{Parameter ADAPTER = "EnsLib.SQL.OutboundAdapter";
Property Adapter As EnsLib.SQL.OutboundAdapter;
Parameter INVOCATION = "Queue";
Method InsertRemoteBDD(pRequest As Formation.Msg.FormationInsertRequest, Output pResponse As Ens.StringResponse) As %Status
{
set tStatus = $$$OKtry{ set pResponse = ##class(Ens.Response).%New() set ^inc = $I(^inc) set tInsertSql = "INSERT INTO public.formation (id, nom, salle) VALUES(?, ?, ?)" $$$ThrowOnError(..Adapter.ExecuteUpdate(.nrows,tInsertSql,^inc,pRequest.Formation.Nom, pRequest.Formation.Salle )) } catch exp { Set tStatus = exp.AsStatus() } Quit tStatus
}
XData MessageMap
{
InsertRemoteBDD
}
}
This operation is similar to the first one we created. When it will receive a message of the type Formation.Msg.FormationInsertRequest
, it will use an adapter to execute SQL requests. Those requests will be sent to our postgre database.
Now, through the Management Portal, we will instantiate that operation (by adding it with the [+] sign in the production).
We will also need to add the JavaGateway for the JDBC driver in the services. The full name of this service is EnsLib.JavaGateway.Service
.
We now need to configure our operation. Since we have set up a postgre container, and connected its port 5432
, the value we need in the following parameters are:
DSN:
jdbc:postgresql://db:5432/DemoData
JDBC Driver:
org.postgresql.Driver
JDBC Classpath:
/tmp/iris/postgresql-42.2.14.jar
Finally, we need to configure the credentials to have access to the remote database. For that, we need to open the Credential Viewer:
The login and password are both DemoData
, as we set up in the docker-compose.yml
file.
https://github.com/user-attachments/assets/b6e63659-be2b-4ec3-8301-dfc6ae7652d9
Back to the production, we can add "Postgre"
in the [Credential] field in the settings of our operation (it should be in the scrolling menu). Before being able to test it, we need to add the JGService to the operation. In the [Settings] tab, in the [Additional Settings]:
When testing the visual trace should show a success:
We have successfully connected with an extern database.
As an exercise, it could be interesting to modify BO.LocalBDD so that it returns a boolean that will tell the BP to call BO.RemoteBDD depending on the value of that boolean.
Hint: This can be done by changing the type of reponse LocalBDD returns and by adding a new property to the context and using the if
activity in our BP.
First, we need to have a response from our LocalBDD operation. We are going to create a new message, in the Formation/Msg/FormationInsertResponse.cls
:
Class Formation.Msg.FormationInsertResponse Extends Ens.Response {
Property Double As %Boolean;
}
Then, we change the response of LocalBDD by that response, and set the value of its boolean randomly (or not):
Method InsertLocalBDD(pRequest As Formation.Msg.FormationInsertRequest, Output pResponse As Formation.Msg.FormationInsertResponse) As %Status { set tStatus = $$$OK
try{ set pResponse = ##class(Formation.Msg.FormationInsertResponse).%New() if $RANDOM(10) < 5 { set pResponse.Double = 1 } else { set pResponse.Double = 0 }
...
We will now create a new process (copied from the one we made), where we will add a new context property, of type %Boolean
:
This property will be filled with the value of the callresponse.Double of our operation call (we need to set the [Response Message Class] to our new message class):
We then add an if
activity, with the context.Double
property as condition:
VERY IMPORTANT : we need to uncheck Asynchronous in the settings of our LocallBDD Call, or the if activity will set off before receiving the boolean response.
Finally we set up our call activity with as a target the RemoteBDD BO:
To complete the if activity, we need to drag another connector from the output of the if
to the join
triangle below. As we won’t do anything if the boolean is false, we will leave this connector empty.
After compiling and instantiating, we should be able to test our new process. For that, we need to change the Target Config Name
of our File Service.
In the trace, we should have approximately half of objects read in the csv saved also in the remote database.
In this part, we will create and use a REST Service.
To create a REST service, we need a cless that extends %CSP.REST, in Formation/REST/Dispatch.cls
we have:
Class Formation.REST.Dispatch Extends %CSP.REST {
/// Ignore any writes done directly by the REST method.
Parameter IgnoreWrites = 0;/// By default convert the input stream to Unicode
Parameter CONVERTINPUTSTREAM = 1;/// The default response charset is utf-8
Parameter CHARSET = "utf-8";Parameter HandleCorsRequest = 1;
XData UrlMap [ XMLNamespace = "http://www.intersystems.com/urlmap" ]
}
{
/// Get this spec
ClassMethod import() As %Status
{
set tSc = $$$OKTry {
set tBsName = "Formation.BS.RestInput" set tMsg = ##class(Formation.Msg.FormationInsertRequest).%New() set body = $zcvt(%request.Content.Read(),"I","UTF8") set dyna = {}.%FromJSON(body) set tFormation = ##class(Formation.Obj.Formation).%New() set tFormation.Nom = dyna.nom set tFormation.Salle = dyna.salle set tMsg.Formation = tFormation $$$ThrowOnError(##class(Ens.Director).CreateBusinessService(tBsName,.tService)) $$$ThrowOnError(tService.ProcessInput(tMsg,.output))
} Catch ex {
set tSc = ex.AsStatus()
}Quit tSc
}
}
This class contains a route to import an object, bound to the POST verb:
The import method will create a message that will be sent to a Business Service.
We are going to create a generic class that will route all of its sollicitations towards TargetConfigNames
. This target will be configured when we will instantiate this service. In the Formation/BS/RestInput.cls
file we have:
Class Formation.BS.RestInput Extends Ens.BusinessService {
Property TargetConfigNames As %String(MAXLEN = 1000) [ InitialExpression = "BuisnessProcess" ];
Parameter SETTINGS = "TargetConfigNames:Basic:selector?multiSelect=1&context={Ens.ContextSearch/ProductionItems?targets=1&productionName=@productionId}";
Method OnProcessInput(pDocIn As %RegisteredObject, Output pDocOut As %RegisteredObject) As %Status
{
set status = $$$OKtry { for iTarget=1:1:$L(..TargetConfigNames, ",") { set tOneTarget=$ZStrip($P(..TargetConfigNames,",",iTarget),"<>W") Continue:""=tOneTarget $$$ThrowOnError(..SendRequestSync(tOneTarget,pDocIn,.pDocOut)) } } catch ex { set status = ex.AsStatus() } Quit status
}
}
Back to the production configuration, we add the service the usual way. In the [Target Config Names], we put our BO LocalBDD:
To use this service, we need to publish it. For that, we use the [Edit Web Application] menu:
https://github.com/user-attachments/assets/7821c249-0a96-42d4-a4d6-8573bf683b2e
Finally, we can test our service with any kind of REST client:
https://github.com/user-attachments/assets/2000a8d5-e48a-41df-97ba-ae5c69a9c1d0
Through this formation, we have created a production that is able to read lines from a csv file and save the read data into both the IRIS database and an extern database using JDBC. We also added a REST service in order to use the POST verb to save new objects.
We have discovered the main elements of InterSystems’ interoperability Framework.
We have done so using docker, vscode and InterSystems’ IRIS Management Portal.