Artigo
· Maio 29, 2023 10min de leitura

Alterar o trace do Visualizador de Mensagens para JSON em vez de XML

Programação e suas linguagens

Ser um programador hoje em dia é basicamente uma versão nerd de ser um poliglota. Claro, a maioria de nós aqui na comunidade InterSystems "falamos ObjectScript". Entretando, eu acredito que essa não foi a primeira língua de muita gente. Por exemplo, eu nunca tinha ouvido falar nela antes de receber o treinamento apropriado na Innovatium.

A parte mais fascinante disso é que mesmo que sejamos aptos a aprender qualquer linguagem e nos tornar fluentes nela, sempre teremos nossas favoritas - as que nos sentimos mais confortáveis e familiares. Geralmente isso tem muito a ver com a primeira linguagem que aprendemos.

Pensando nisso, me deparei com a ideia Make JSON representation of messages in Interoperability message viewer instead of XML no portal de Ideias InterSystems e descobri que @Guillaume Rongier já resolveu isso para nós. Meu objetivo nesse artigo é mostrar como usar a solução dele e discutir sobre algumas utilidades.
 

 

O contexto

Para cada produção que construímos, o Portal de Administração do InterSystems IRIS cria uma interface que exibe todos os componentes conectados e cada mensagem relacionada a seu comportamento.

A figura acima ilustra o Portal de Configurações de Produção, onde você pode ver todos os Business Services, Processes e Operations, divididos em categorias de sua escolha. Para cada um, você pode checar e/ou alterar configurações gerais ou personalizadas, além de executar ações como mudar o estado, testar, exportar, checar os jobs, filas e logs relacionados a eles, etc. No entando, o que é mais importante para nós agora é a possibilidade de checar as mensagens.

Selecionando Mensagens na aba a direita, você poderá ver uma tabela com todas as mensagens relacionadas a essa produção. Ao mesmo tempo, se clicar em Ir Ao Visualizador de Mensagens você verá uma tabela mais detalhada com várias opções de filtros. Além disso, se escolher um componente Business antes de selecionar a aba de mensagens, verá apenas as mensagens da produção que se comunicam com o componente designado.

 

Nessa outra figura, podemos observar o Visualizador de Mensagens e finalmente resgatar a informação que queremos. Se selecionar uma linha na tabela, abrirá uma sessão a direita com todo detalhe possível sobre a mensagem escolhida. Isso significa que terá uma aba para a Header (cabeçalho), outra para o Body (corpo), mais uma para Tracing (rastreamento) e uma com um diagrama que mostra de onde a mensagem veio e os componentes relacionados. A aba que procuramos é a Contents (conteúdos).

A aba de conteúdos nos mostra uma versão em XML da mensagem e hoje vamos mudá-la para uma versão em JSON.

PS.: você também pode clicar no número da Sessão na tabela para obter uma visão focada do rastreamento ao lado da aba de Header, Body e Contents, como exemplificado na imagem abaixo

 

 

Desvio!

Essa sessão tem o objetivo de ser um pequeno desvio sobre o básico de construir uma API, um caso comum que utiliza muito JSON, e para familiarizar o contexto onde a aplicação pode ajudar.

Para trazer um sentimento real das alterações que essa aplicação promove, vamos construir um exemplo simples de uma API que recebe alguma informação numa request HTTP em formato JSON. Se já está familiarizado com APIs, sinta-se à vontade para pular para a próxima sessão, embora eu recomende pelo menos dar uma olhada nessa. Assim, você acompanhará melhor os exemplos discutidos a frente.

Eu vou seguir as boas práticas da Innovatium, mas lembre-se que esse não é o único jeito possível de fazer.

 

Primeiro, precisamos criar um service Dispatch, extendendo %CSP.REST e Ens.BusinessService, com o adaptador EnsLib.HTTP.InboundAdapter que lida requisições HTTP recebidas. Devemos especificar se o serviço lida com requisições CORS. Também podemos opcionalmente implementar o método de classe OnHandleOptionsRequest e finalmente definir um XData URlMap com parâmetros de Map, que dão seguimento para a próxima classe que vamos criar.

 

Class Sample.Service.Dispatch Extends (%CSP.REST, Ens.BusinessService)
{

 Parameter HandleCorsRequest = 1;
 Parameter ADAPTER = "EnsLib.HTTP.InboundAdapter";
 XData UrlMap [ XMLNamespace = "http://www.intersystems.com/urlmap" ]
 {
    <Routes>
        <Map Prefix="/send" Forward="Sample.Service.Send"/>
    </Routes>
 }

 ClassMethod GetService(Output tService As Ens.BusinessService) As %Status
 {
 	 Quit ##class(Ens.Director).CreateBusinessService(..%ClassName(1), .tService)
 }

 ClassMethod OnHandleOptionsRequest(url As %String) As %Status
 { 
     ; your code here
 }
}

 

Então, no Portal de Administração criamos uma aplicação web com REST habilitado com a classe que acabamos de criar como a classe Dispatch.

Em seguida, desenvolvemos uma classe, que estende a Dispatch, com um XData UrlMap com parâmetros Route e construímos o método que vai de fato lidar com a requisição recebida. Isso também poderia ter sido feito direto no serviço de Dispatch. Através desse método, você pode fazer o que quiser com a requisição. No entanto, vamos focar em receber um JSON, transformá-lo em um objeto e enviar a um Business Operation que será responsável por lidar com a informação de qualquer forma necessária.

Class Sample.Service.Send Extends Sample.Service.Dispatch
{

Parameter CONTENTTYPE = "application/json";
Parameter CHARSET = "utf-8";
XData UrlMap [ XMLNamespace = "http://www.intersystems.com/urlmap" ]
{
<Routes>
<Route Method="POST" Url="/message" Call="SendMessage"/>
</Routes>
}

ClassMethod SendMessage() As %Status
{
    Set tSC = $$$OK
    Try
    {
        #Dim %request As %CSP.Request
        #Dim %response As %CSP.Response
        #Dim Service As Ens.BusinessService
        
        Set %response.ContentType = "application/json"
        Set %response.CharSet = "utf-8"
        ; code for authentication - this part was cut out for simplification
        ; in this part I set the variables tUsername and tBasicAuth, used to create the request below.
        ; implementing authentication is optional, this is just an example
        // creates service
        Set tSC = ..GetService(.tService)
        
        If $System.Status.IsError(tSC) Quit
        // gets request body
        Set tReceived = ##class(%Stream.GlobalCharacter).%New()
        Set tSC = tReceived.CopyFrom(%request.Content)
        
        If $System.Status.IsError(tSC) Quit
        Set tMessage = ##class(%DynamicObject).%FromJSON(tReceived)
        
        // creates request object for the business operation 
        Set tRequest = ##Class(Sample.Operation.SendMessage.Request).%New()
        Set tRequest.Message = tMessage.message
        Set tRequest.token = tBasicAuth
        Set tRequest.Username = tUsername
        
        Set tSC = tService.SendRequestSync("Sample.Operation",tRequest,.tResponse)
        
        If $System.Status.IsError(tSC) Quit
        // treats response
        If $IsObject(tResponse)
        {
            Set tSC = tResponse.CopyToObject(.tJSON)
            Quit:$$$ISERR(tSC)
        }
        
        Set tResult = ##class(%Stream.GlobalCharacter).%New()
        
        Do tResult.Write(tJSON.%ToJSON())
        Do tResult.OutputToDevice()
    } 
    Catch Ex
    {
        Set tSC = Ex.AsStatus()
    }	

    Quit tSC
}

}

 

É bom saber, mas por que eu deveria me incomodar?

Nessa sessão, vou tentar te convencer que é uma boa ideia ter a opção de mostrar o conteúdo de suas mensagens em formato JSON.

JSON é também legível por humanos, significando que você pode abrir um arquivo e ver o que está dentro dele sem ter que executá-lo por um parser Isso faz problemas de debug com seu código mais acessível e ajuda a documentar os dados recebidos de outras aplicações. (tradução livre)

É claro, às vezes "se sentir confortável" com uma linguagem não é suficiente para mudar algo na sua rotina de trabalho, afinal a mudança em si pode não ser confortável. No entanto, na API que acabamos de construir, há algumas outras coisas a se considerar além da questão de conveniência. Podemos notar que o código não foi escrito em XML em nada, então vê-lo nessa linguagem adicionaria um novo nível de complexidade.

Deixe-me explicar essa complexidade. Ainda que não pareça fazer uma grande diferença para um desenvolvedor ler uma simples mensagem com apenas uma propriedade string em tags (<tags>) ou formato JSON, também é importante fazer essa mensagem parecer familiar para os não-desenvolvedores que podem ter acesso a esse portal e devem ser capazes de entender de um modo geral o processamento da informação, já que JSON é muito mais amigável ao usuário. Além disso, quando as mesagens carregam propriedades de outros objetos ou tipos, somente serão enviadas adequadamente em JSON se têm a adaptação apropriada para isso. O IRIS torna isso fácil de resolver simplesmente estendendo a classe %JSON.Adaptor. Ao mesmo tempo, mensagens só aparecerão no Visualizador de Mensagens se têm a adaptação adequada para XML, que o IRIS também torna fácil estendendo %XML.Adaptor. Ainda assim, se o Visualizador de Mensagens mostrasse em JSON, esse último problema não existiria. De forma prática, isso significa diminuir a quantidade de dependências do projeto, o que, em idioma humano, significa reduzir o número de coisas que podem dar errado enquanto desenvolve e quando atualizações futuras do projeto ou até mesmo do IRIS saírem. E além de tudo, você não precisa de um time capacitado para lidar com XML.

Por fim, quando o time está procurando por erros e testando a API, você tem o formato exato da requisição recebida logada no Visualizador de mensagens, então é apenas uma questão de copiar e colar para testar em plataformas como Postman e outras para entender exatamente o que o sistema está lendo e escrevendo.

 

Se você quer saber ainda mais sobre os prós e contras de XML e JSON, eu recomendo fortemente que dê uma olhada no artigo json-vs-xml, que fornece uma incrível discussão sobre situações onde ambos são usados. A citação acima foi tirada desse website.

 

Ok, isso é demais! Me mostre como se usa!

Instalando

Você pode instalar com ZPM digitando no seu terminal no namespace desejado:

zpm "install objectscript-json-trace-viewer"

Outra opção é entrar no modo ZPM digitando zpm e então digitarinstall objectscript-json-trace-viewer

Se você não tem uma instância de IRIS na sua máquina, outra opção para testar é clonar o repositório e executar docker-compose build e então docker-compose up -d. Uma vez que tenha terminado de rodar o container, abra o link http://localhost:52777/csp/sys/UtilHome.csp?$NAMESPACE=DEMO e entre com usuário _SYSTEM e senha SYS para ver como funciona. Você também pode checar os códigos do repositório GitHub para exemplos de implementação.

 

Usando

Para cada classe que quer rastrear como JSON e não XML, adicione "Grongier.JsonTraceViewer.Message" para a lista de extensões ou qualquer de suas subclasses. Essa é a minha classe de Request do exemplo da última sessão:

Class Sample.Operation.SendMessage.Request Extends (Grongier.JsonTraceViewer.Request, Ens.Request)
{

 Parameter XMLTYPE = "RequestSample";
 Parameter RESPONSECLASSNAME = "Sample.Operation.SendMessage.Response";
 Property Message As %String;
 Property Username As %String;
 Property token As %String;
}

 

Agora, ao chamar a API novamente, o Visualizador de Mensagens exibe o conteúdo como a imagem a seguir:

 

 

Erros que você pode enfrentar

  • Erro ao tentar executar o docker-compose build: 
failed to solve: rpc error: code = Unknown desc = failed to solve with frontend dockerfile.v0: failed to create LLB definition: docker.io/store/intersystems/iris-community:2020.2.0.196.0: not found

SOLUCIONADO  ao abrir a Dockerfile e mudar o valor de ARG IMAGE para uma imagem disponível no seu docker. Outra opção é usar a InterSystems extension de CaretDev e executar um pull na imagem iris-community mais recente. No meu caso, a primeira linha da Dockerfile ficou assim:

ARG IMAGE=containers.intersystems.com/intersystems/iris-community:2023.1.0.229.0

 

 

  • Erro ao tentar rodar o container:
#12 19.05 Error: ERROR #5001: Could not start SuperServer on port 1972, may be in use by another instance - Shutting down the system : $zu(56,2)=$Id: //iris/2023.1.0/kernel/common/src/journal.c#3 $ 10906 0Starting IRIS
failed to solve: executor failed running [/irissession.sh do $SYSTEM.OBJ.Load("Installer.cls", "ck")   set sc = ##class(App.Installer).setup()   do $system.OBJ.Load("/tmp/deps/zpm.xml", "ck")   zn "DEMO"]: exit code: 225

SOLUCIONADO ao parar qualquer instância de IRIS existente na máquina e tentar rodar novamente.

 

  • Erro ao tentar rodar o container:
Error response from daemon: Ports are not available: exposing port TCP 0.0.0.0:52777 -> 0.0.0.0:0: listen tcp 0.0.0.0:52777: bind: An attempt was made to access a socket in a way forbidden by its access permissions.

SOLUCIONADO no Windows ao executar o terminal do sistema como administrador e rodar o comando a seguir. Esse erro pode ocorrer se a porta escolhida já estiver sendo usada. Você pode simplesmente alterá-lal no docker-compose.yml ou parar o processo que a esteja utilizando.

net stop hns
net start hns

 

  • Erro ao compilar classes personalizadas usando as classes deJsonTraceViewer:
Error #6281: XMLTYPE of Sample.Operation.SendMessage.Request class must be able to differentiate child classes of Grongier.JsonTraceViewer.Request.
> Error #5090: An error occurred while creating projection Grongier.JsonTraceViewer.Request:XMLEnabled.

​​​​​​SOLUCIONADO ao adicionar "Parameter XMLTYPE = 'type'" para toda classe que estende Grongier.JsonTraceViewer.Message, e alterando "type" por tipos únicos para cada classe.

Discussão (0)1
Entre ou crie uma conta para continuar