Si tuvieras la oportunidad de cambiar algo en el Visualizador de Mensajes de Interoperabilidad en IRIS, ¿qué harías?
Bueno, antes de nada. Por lo general, se empieza configurando una producción de interoperabilidad, y después se exporta y se implementa en el sistema de destino, como se indica en la documentación. Este es un proceso que realmente no me gusta. En realidad, no es que haya algo malo en él. Solo que he idealizado hacer todo mediante el código.
Espero que cada vez que alguien ejecute este tipo de proyectos, empiece de esta manera:
$ docker-compose build
$ docker-compose up -d
¡¡¡Y eso es todo!!!
Con estos sencillos pasos en mente, comencé a buscar en la comunidad de InterSystems y encontré algunos consejos. En una de las publicaciones surgió la pregunta que me estaba haciendo: ¿Cómo crear producciones desde una rutina?
En esa publicación, @Eduard Lebedyuk respondió, mostrando cómo crear una producción mediante el uso de un código.
"Para crear la clase de producción de forma automática es necesario:
- Crear el objeto %Dictionary.ClassDefinition para tu producción de prueba
- Crear el objeto Ens.Config.Production
- Crear %Dictionary.XDataDefinition
- Serializar (2) en (3)
- Insertar XData (3) en (1)
- Guardar y compilar (1)"
También encontré un comentario de @Jenny Ames:
"Una de las prácticas que recomendamos on frecuencia es crear hacia atrás. Crea las business operations primero, después los business processes, después los business services…"
Así que, ¡hagámoslo!
Solicitudes, Business operations y Business services
La clase diashenrique.messageviewer.util.InstallerProduction.cls es, como su nombre lo indica, la clase que se encarga de instalar nuestra producción. El manifiesto del instalador invoca el ClassMethod **Install** desde esa clase:
/// Helper to install a production to display capabilities of the enhanced viewer
ClassMethod Install() As %Status
{
Set sc = $$$OK
Try {
Set sc = $$$ADDSC(sc,..InstallProduction()) quit:$$$ISERR(sc)
Set sc = $$$ADDSC(sc,..GenerateMessages()) quit:$$$ISERR(sc)
Set sc = $$$ADDSC(sc,..GenerateUsingEnsDirector()) quit:$$$ISERR(sc)
}
Catch (err) {
Set sc = $$$ADDSC(sc,err.AsStatus())
}
Return sc
}
El ClassMethod InstallProduction reúne la estructura principal que permite crear una producción mediante la creación de:
- una solicitud
- una business operation
- un business service
- una producción de interoperabilidad
Dado que la idea es crear una producción de interoperabilidad mediante código, vamos al modo de codificación completa para crear todas las clases para la solicitud, la business operation y los business services. Al hacer eso, haremos un amplio uso de algunos paquetes de librerías de InterSystems:
- %Dictionary.ClassDefinition
- %Dictionary.PropertyDefinition
- %Dictionary.XDataDefinition
- %Dictionary.MethodDefinition
- %Dictionary.ParameterDefinition
El ClassMethod InstallProduction crea dos clases que se extienden desde Ens.Request, por medio de las siguientes líneas:
Set sc = $$$ADDSC(sc,..CreateRequest("diashenrique.messageviewer.Message.SimpleRequest","Message")) quit:$$$ISERR(sc)
Set sc = $$$ADDSC(sc,..CreateRequest("diashenrique.messageviewer.Message.AnotherRequest","Something")) quit:$$$ISERR(sc)
ClassMethod CreateRequest(classname As %String, prop As %String) As %Status [ Private ]
{
New $Namespace
Set $Namespace = ..#NAMESPACE
Set sc = $$$OK
Try {
Set class = ##class(%Dictionary.ClassDefinition).%New(classname)
Set class.GeneratedBy = $ClassName()
Set class.Super = "Ens.Request"
Set class.ProcedureBlock = 1
Set class.Inheritance = "left"
Set sc = $$$ADDSC(sc,class.%Save())
#; create adapter
Set property = ##class(%Dictionary.PropertyDefinition).%New(classname)
Set property.Name = prop
Set property.Type = "%String"
Set sc = $$$ADDSC(sc,property.%Save())
Set sc = $$$ADDSC(sc,$System.OBJ.Compile(classname,"fck-dv"))
}
Catch (err) {
Set sc = $$$ADDSC(sc,err.AsStatus())
}
Return sc
}
Ahora vamos a crear la clase para una business operation que se extiende desde Ens.BusinessOperation:
Set sc = $$$ADDSC(sc,..CreateOperation()) quit:$$$ISERR(sc)
Además de crear la clase, creamos MessageMap y el método Consume:
ClassMethod CreateOperation() As %Status [ Private ]
{
New $Namespace
Set $Namespace = ..#NAMESPACE
Set sc = $$$OK
Try {
Set classname = "diashenrique.messageviewer.Operation.Consumer"
Set class = ##class(%Dictionary.ClassDefinition).%New(classname)
Set class.GeneratedBy = $ClassName()
Set class.Super = "Ens.BusinessOperation"
Set class.ProcedureBlock = 1
Set class.Inheritance = "left"
Set xdata = ##class(%Dictionary.XDataDefinition).%New()
Set xdata.Name = "MessageMap"
Set xdata.XMLNamespace = "http://www.intersystems.com/urlmap"
Do xdata.Data.WriteLine("<MapItems>")
Do xdata.Data.WriteLine("<MapItem MessageType=""diashenrique.messageviewer.Message.SimpleRequest"">")
Do xdata.Data.WriteLine("<Method>Consume</Method>")
Do xdata.Data.WriteLine("</MapItem>")
Do xdata.Data.WriteLine("<MapItem MessageType=""diashenrique.messageviewer.Message.AnotherRequest"">")
Do xdata.Data.WriteLine("<Method>Consume</Method>")
Do xdata.Data.WriteLine("</MapItem>")
Do xdata.Data.WriteLine("</MapItems>")
Do class.XDatas.Insert(xdata)
Set sc = $$$ADDSC(sc,class.%Save())
Set method = ##class(%Dictionary.MethodDefinition).%New(classname)
Set method.Name = "Consume"
Set method.ClassMethod = 0
Set method.ReturnType = "%Status"
Set method.FormalSpec = "input:diashenrique.messageviewer.Message.SimpleRequest,&output:Ens.Response"
Set stream = ##class(%Stream.TmpCharacter).%New()
Do stream.WriteLine(" set sc = $$$OK")
Do stream.WriteLine(" $$$TRACE(input.Message)")
Do stream.WriteLine(" return sc")
Set method.Implementation = stream
Set sc = $$$ADDSC(sc,method.%Save())
Set sc = $$$ADDSC(sc,$System.OBJ.Compile(classname,"fck-dv"))
}
Catch (err) {
Set sc = $$$ADDSC(sc,err.AsStatus())
}
Return sc
}
En el último paso antes de realmente crear la producción de interoperabilidad, vamos a crear la clase responsable del business service:
Set sc = $$$ADDSC(sc,..CreateRESTService()) quit:$$$ISERR(sc)
Esta clase tiene UrlMap y Routes para recibir solicitudes Http.
ClassMethod CreateRESTService() As %Status [ Private ]
{
New $Namespace
Set $Namespace = ..#NAMESPACE
Set sc = $$$OK
Try {
Set classname = "diashenrique.messageviewer.Service.REST"
Set class = ##class(%Dictionary.ClassDefinition).%New(classname)
Set class.GeneratedBy = $ClassName()
Set class.Super = "EnsLib.REST.Service, Ens.BusinessService"
Set class.ProcedureBlock = 1
Set class.Inheritance = "left"
Set xdata = ##class(%Dictionary.XDataDefinition).%New()
Set xdata.Name = "UrlMap"
Set xdata.XMLNamespace = "http://www.intersystems.com/urlmap"
Do xdata.Data.WriteLine("<Routes>")
Do xdata.Data.WriteLine("<Route Url=""/send/message"" Method=""POST"" Call=""SendMessage""/>")
Do xdata.Data.WriteLine("<Route Url=""/send/something"" Method=""POST"" Call=""SendSomething""/>")
Do xdata.Data.WriteLine("</Routes>")
Do class.XDatas.Insert(xdata)
Set sc = $$$ADDSC(sc,class.%Save())
#; create adapter
Set adapter = ##class(%Dictionary.ParameterDefinition).%New(classname)
Set class.GeneratedBy = $ClassName()
Set adapter.Name = "ADAPTER"
Set adapter.SequenceNumber = 1
Set adapter.Default = "EnsLib.HTTP.InboundAdapter"
Set sc = $$$ADDSC(sc,adapter.%Save())
#; add prefix
Set prefix = ##class(%Dictionary.ParameterDefinition).%New(classname)
Set prefix.Name = "EnsServicePrefix"
Set prefix.SequenceNumber = 2
Set prefix.Default = "|demoiris"
Set sc = $$$ADDSC(sc,prefix.%Save())
Set method = ##class(%Dictionary.MethodDefinition).%New(classname)
Set method.Name = "SendMessage"
Set method.ClassMethod = 0
Set method.ReturnType = "%Status"
Set method.FormalSpec = "input:%Library.AbstractStream,&output:%Stream.Object"
Set stream = ##class(%Stream.TmpCharacter).%New()
Do stream.WriteLine(" set sc = $$$OK")
Do stream.WriteLine(" set request = ##class(diashenrique.messageviewer.Message.SimpleRequest).%New()")
Do stream.WriteLine(" set data = {}.%FromJSON(input)")
Do stream.WriteLine(" set request.Message = data.Message")
Do stream.WriteLine(" set sc = $$$ADDSC(sc,..SendRequestSync(""diashenrique.messageviewer.Operation.Consumer"",request,.response))")
Do stream.WriteLine(" return sc")
Set method.Implementation = stream
Set sc = $$$ADDSC(sc,method.%Save())
Set method = ##class(%Dictionary.MethodDefinition).%New(classname)
Set method.Name = "SendSomething"
Set method.ClassMethod = 0
Set method.ReturnType = "%Status"
Set method.FormalSpec = "input:%Library.AbstractStream,&output:%Stream.Object"
Set stream = ##class(%Stream.TmpCharacter).%New()
Do stream.WriteLine(" set sc = $$$OK")
Do stream.WriteLine(" set request = ##class(diashenrique.messageviewer.Message.AnotherRequest).%New()")
Do stream.WriteLine(" set data = {}.%FromJSON(input)")
Do stream.WriteLine(" set request.Something = data.Something")
Do stream.WriteLine(" set sc = $$$ADDSC(sc,..SendRequestSync(""diashenrique.messageviewer.Operation.Consumer"",request,.response))")
Do stream.WriteLine(" return sc")
Set method.Implementation = stream
Set sc = $$$ADDSC(sc,method.%Save())
Set sc = $$$ADDSC(sc,$System.OBJ.Compile(classname,"fck-dv"))
}
Catch (err) {
Set sc = $$$ADDSC(sc,err.AsStatus())
}
Return sc
}
Usando Visual Studio Code
Crear las clases mediante el paquete %Dictionary puede ser difícil, y difícil de leer también, pero es práctico. Para mostrar un procedimiento un poco más sencillo con una mejor comprensión del código, también crearé nuevas clases de solicitud, business service y business operations por medio de Visual Studio Code:
- diashenrique.messageviewer.Message.SimpleMessage.cls
- diashenrique.messageviewer.Operation.ConsumeMessageClass.cls
- diashenrique.messageviewer.Service.SendMessage.cls
Class diashenrique.messageviewer.Message.SimpleMessage Extends Ens.Request [ Inheritance = left, ProcedureBlock ]
{
Property ClassMessage As %String;
}
Class diashenrique.messageviewer.Operation.ConsumeMessageClass Extends Ens.BusinessOperation [ Inheritance = left, ProcedureBlock ]
{
Method Consume(input As diashenrique.messageviewer.Message.SimpleMessage, ByRef output As Ens.Response) As %Status
{
Set sc = $$$OK
$$$TRACE(pRequest.ClassMessage)
Return sc
}
XData MessageMap [ XMLNamespace = "http://www.intersystems.com/urlmap" ]
{
<MapItems>
<MapItem MessageType="diashenrique.messageviewer.Message.SimpleMessage">
<Method>Consume</Method>
</MapItem>
</MapItems>
}
}
Class diashenrique.messageviewer.Service.SendMessage Extends Ens.BusinessService [ ProcedureBlock ]
{
Method OnProcessInput(input As %Library.AbstractStream, ByRef output As %Stream.Object) As %Status
{
Set tSC = $$$OK
// Create the request message
Set request = ##class(diashenrique.messageviewer.Message.SimpleMessage).%New()
// Place a value in the request message property
Set request.ClassMessage = input
// Make a synchronous call to the business process and use the response message as our response
Set tSC = ..SendRequestSync("diashenrique.messageviewer.Operation.ConsumeMessageClass",request,.output)
Quit tSC
}
}
Desde el punto de vista de la legibilidad del código, la diferencia es enorme.
Creando la Producción de Interoperabilidad
Vamos a terminar la creación de nuestra producción de interoperabilidad. Para ello, crearemos una clase de producción, y después la asociaremos con las Business Operation y Business Services.
Set sc = $$$ADDSC(sc,..CreateProduction()) quit:$$$ISERR(sc)
ClassMethod CreateProduction(purge As %Boolean = 0) As %Status [ Private ]
{
New $Namespace
Set $Namespace = ..#NAMESPACE
Set sc = $$$OK
Try {
#; create new production
Set class = ##class(%Dictionary.ClassDefinition).%New(..#PRODUCTION)
Set class.ProcedureBlock = 1
Set class.Super = "Ens.Production"
Set class.GeneratedBy = $ClassName()
Set xdata = ##class(%Dictionary.XDataDefinition).%New()
Set xdata.Name = "ProductionDefinition"
Do xdata.Data.Write("<Production Name="""_..#PRODUCTION_""" LogGeneralTraceEvents=""true""></Production>")
Do class.XDatas.Insert(xdata)
Set sc = $$$ADDSC(sc,class.%Save())
Set sc = $$$ADDSC(sc,$System.OBJ.Compile(..#PRODUCTION,"fck-dv"))
Set production = ##class(Ens.Config.Production).%OpenId(..#PRODUCTION)
Set item = ##class(Ens.Config.Item).%New()
Set item.ClassName = "diashenrique.messageviewer.Service.REST"
Do production.Items.Insert(item)
Set sc = $$$ADDSC(sc,production.%Save())
Set item = ##class(Ens.Config.Item).%New()
Set item.ClassName = "diashenrique.messageviewer.Operation.Consumer"
Do production.Items.Insert(item)
Set sc = $$$ADDSC(sc,production.%Save())
Set item = ##class(Ens.Config.Item).%New()
Set item.ClassName = "diashenrique.messageviewer.Service.SendMessage"
Do production.Items.Insert(item)
Set sc = $$$ADDSC(sc,production.%Save())
Set item = ##class(Ens.Config.Item).%New()
Set item.ClassName = "diashenrique.messageviewer.Operation.ConsumeMessageClass"
Do production.Items.Insert(item)
Set sc = $$$ADDSC(sc,production.%Save())
}
Catch (err) {
Set sc = $$$ADDSC(sc,err.AsStatus())
}
Return sc
}
Utilizamos la clase Ens.Config.Item para asociar la clase de producción con las clases Business Operation y Business Service. Puedes hacer esto tanto si creaste tu clase usando el paquete %Dictionary como con VS Code, Studio o Atelier.
En cualquier caso, ¡lo conseguimos! Creamos una producción de interoperabilidad por medio de código.
Pero recuerda el propósito original de todo este código: crear una producción y mensajes para mostrar las funcionalidades del Visualizador de Mensajes mejorado. Usando los métodos de clase siguientes, ejecutaremos los dos business services y generaremos los mensajes.
Generando mensajes con %Net.HttpRequest:
ClassMethod GenerateMessages() As %Status [ Private ]
{
New $Namespace
Set $Namespace = ..#NAMESPACE
Set sc = $$$OK
Try {
Set action(0) = "/demoiris/send/message"
Set action(1) = "/demoiris/send/something"
For i=1:1:..#LIMIT {
Set content = { }
Set content.Message = "Hi, I'm just a random message named "_$Random(30000)
Set content.Something = "Hi, I'm just a random something named "_$Random(30000)
Set httprequest = ##class(%Net.HttpRequest).%New()
Set httprequest.SSLCheckServerIdentity = 0
Set httprequest.SSLConfiguration = ""
Set httprequest.Https = 0
Set httprequest.Server = "localhost"
Set httprequest.Port = 9980
Set serverUrl = action($Random(2))
Do httprequest.EntityBody.Write(content.%ToJSON())
Set sc = httprequest.Post(serverUrl)
Quit:$$$ISERR(sc)
}
}
Catch (err) {
Set sc = $$$ADDSC(sc,err.AsStatus())
}
Return sc
}
Generando mensajes con EnsDirector:
ClassMethod GenerateUsingEnsDirector() As %Status [ Private ]
{
New $Namespace
Set $Namespace = ..#NAMESPACE
Set sc = $$$OK
Try {
For i=1:1:..#LIMIT {
Set tSC = ##class(Ens.Director).CreateBusinessService("diashenrique.messageviewer.Service.SendMessage",.tService)
Set message = "Message Generated By CreateBusinessService "_$Random(1000)
Set tSC = tService.ProcessInput(message,.output)
Quit:$$$ISERR(sc)
}
}
Catch (err) {
Set sc = $$$ADDSC(sc,err.AsStatus())
}
Return sc
}
}
Eso todo lo que se necesita para el código. Encontrarás el proyecto completo en https://github.com/diashenrique/iris-message-viewer.
Ejecutando el proyecto
Ahora veamos el proyecto en marcha.
Primero, git clone o git pull el repositorio en cualquier directorio local:
git clone https://github.com/diashenrique/iris-message-viewer.git
Después, abre el terminal en este directorio y ejecuta:
docker-compose build
Finalmente, ejecuta el contenedor IRIS con tu proyecto:
docker-compose up -d
Ahora accederemos al Portal de Administración por medio de la página http://localhost:52773/csp/sys/UtilHome.csp
El Visualizador de Mensajes mejorado
http://localhost:52773/csp/msgviewer/messageviewer.csp
##class(Ens.MessageHeader).ResendDuplicatedMessage(id)