Escrito por

Software Engineer at Zarmik
Artigo Heloisa Paiva · 1 h atrás 6m read

Como adicionar suas APIs à Produção de Interoperabilidade

Eu já devo ter mencionado isso antes: acredito que os Visual Traces, esses diagramas de sequência com o conteúdo completo de cada etapa, são um recurso fantástico da plataforma IRIS Data! Informações detalhadas sobre como a API funciona internamente, como um rastreamento visual, podem ser muito úteis para projetos na plataforma IRIS. É claro que isso se aplica quando não estamos desenvolvendo uma solução de alta carga, caso em que simplesmente não temos tempo para salvar/ler mensagens. Para todos os outros casos, bem-vindos a este tutorial!

 

Estarei usando uma abordagem baseada em especificação (specification-first), então o primeiro passo será criar uma especificação. Pedi ao Codex para criar uma especificação OpenAPI de exemplo e obtive o seguinte JSON:

{
  "swagger": "2.0",
  "info": {
    "version": "1.0.0",
    "title": "Sample Spec API",
    "description": "Example Swagger 2.0 specification"
  },
  "basePath": "/sample-api",
  "schemes": [
    "http"
  ],
  "consumes": [
    "application/json"
  ],
  "produces": [
    "application/json"
  ],
  "paths": {
    "/sample/echo": {
      "post": {
        "summary": "Accepts JSON payload and returns another JSON payload",
        "operationId": "postSampleEcho",
        "parameters": [
          {
            "in": "body",
            "name": "body",
            "required": true,
            "schema": {
              "$ref": "#/definitions/SampleRequest"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "schema": {
              "$ref": "#/definitions/SampleResponse"
            }
          }
        }
      }
    }
  },
  "definitions": {
    "SampleRequest": {
      "type": "object",
      "required": [
        "name",
        "count"
      ],
      "properties": {
        "name": {
          "type": "string",
          "example": "test"
        },
        "count": {
          "type": "integer",
          "format": "int32",
          "example": 1
        }
      }
    },
    "SampleResponse": {
      "type": "object",
      "properties": {
        "status": {
          "type": "string",
          "example": "ok"
        },
        "message": {
          "type": "string",
          "example": "Request processed successfully"
        }
      }
    }
  }
}

Em seguida, vem a criação das classes Cache: spec, disp e impl. Usarei a API de sistema do IRIS para isso. Vamos executar a seguinte requisição no Postman (ou CURL) e exportar os arquivos gerados para o projeto:

curl --location 'http://localhost:52773/api/mgmnt/v2/dev/Sample.REST.API?IRISUsername=_system&IRISPassword=SYS' \
--header 'Content-Type: application/json' \
--data '/// sua especificação aqui ///'

Onde:

  • dev - meu namespace
  • Sample.REST.API - pacote para as classes da API

Depois disso, precisaremos de um Business Service. Proponho criar um serviço base comum e estender nosso serviço particular a partir dele. Isso é necessário para fornecer um ponto de entrada separado na Produção de Interoperabilidade para cada uma de nossas futuras APIs. Aqui está o código completo deste serviço base:

Class Sample.REST.Service.Core Extends (Ens.BusinessService, %REST.Impl)
{

/// Chame este método a partir da sua classe .impl</br> 
/// pTarget - o Business Process que processa a mensagem</br>
/// pPayload - o corpo da requisição HTTP
ClassMethod OnMessage(pTarget As %String, pPayload As %DynamicObject) As %DynamicObject
{
    Do ..%SetContentType("application/json")
    
    Set input = ##class(Ens.StreamContainer).%New()
    Set input.Stream = ##class(%Stream.GlobalCharacter).%New()
    Do input.Attributes.SetAt(pTarget, "Target")
    
    Do pPayload.%ToJSON(input.Stream)
    
    Return:$CLASSNAME()'=$GET($$$ConfigClassName($CLASSNAME())) ..Error($$$ERROR($$$EnsErrBusinessDispatchNameNotRegistered, $CLASSNAME()))
	
	Set tSC = ##class(Ens.Director).CreateBusinessService($CLASSNAME(), .service)
	Return:$$$ISERR(tSC) ..Error(tSC)
	
	Set tSC = service.ProcessInput(input, .output)
	Return:$$$ISERR(tSC) ..Error(tSC)

    Return ..Success(output)
}

ClassMethod Error(pStatus As %Status) As %DynamicObject
{
    Do ..%SetStatusCode(##class(%CSP.REST).#HTTP500INTERNALSERVERERROR)
    Do ##class(%CSP.REST).StatusToJSON(pStatus, .json)
    Return json
}

ClassMethod Success(pOutput As Ens.StreamContainer) As %DynamicObject
{
    Do ..%SetStatusCode(##class(%CSP.REST).#HTTP200OK)

    If $iso(pOutput) {
        // O status HTTP pode ser definido durante o processamento da chamada no Business Process
        Do:pOutput.Attributes.GetAt("Status")'="" ..%SetStatusCode(pOutput.Attributes.GetAt("Status"))
        Set stream = pOutput.StreamGet()
		Return:$iso(stream)&&(stream.Size>0) {}.%FromJSON(stream)
	}

    Return ""
}

Method OnProcessInput(pInput As Ens.StreamContainer, Output pOutput As Ens.StreamContainer) As %Status
{
    Return ..SendRequestSync(pInput.Attributes.GetAt("Target"), pInput, .pOutput)
}
}

Alguns detalhes de implementação. Estou usando Ens.StreamContainer para passar JSONs entre o Business Service e o Business Process. O método OnMessage() é:

  1. Chamado a partir da classe impl
  2. Recebe o corpo da requisição HTTP como um %DynamicObject
  3. Cria uma instância do Business Service
  4. Envia uma mensagem do Serviço para o Business Process manipulador (veja o parâmetro pTarget) via OnProcessInput()
  5. E retorna o %DynamicObject recebido do processo de negócio como uma resposta HTTP

Em seguida, vamos criar um manipulador simples para nossas mensagens:

Class Sample.REST.Process.Echo Extends Ens.BusinessProcess
{

Method OnRequest(pRequest As Ens.StreamContainer, Output pResponse As Ens.StreamContainer) As %Status
{
    Set jsonIn = {}.%FromJSON(pRequest.StreamGet())
    Set jsonOut = {"status": "ok", "message": ($$$FormatText("Request processed for %1 with count %2", jsonIn.name, jsonIn.count))}

    Set pResponse = ##class(Ens.StreamContainer).%New()
    Set pResponse.Stream = ##class(%Stream.GlobalCharacter).%New()
    Do jsonOut.%ToJSON(pResponse.Stream)

    Return $$$OK
}
}

E a última das classes Cache é uma classe filha de Sample.REST.Service.Core:

Class Sample.REST.Service.API Extends Sample.REST.Service.Core
{

ClassMethod OnGetConnections(Output pArray As %String, pItem As Ens.Config.Item)
{
	Set pArray(##class(Sample.REST.Process.Echo).%ClassName(1)) = ""
}
}

Aqui, você só precisa sobrescrever OnGetConnections() para descrever as conexões com os Business Processes na Produção. Isso não afeta a funcionalidade, mas é necessário para construir os links corretos na UI da Produção. 

⚠️ O que é importante aqui: você deve usar os nomes das classes para os business hosts na Produção. Isso é fundamental para que nosso exemplo funcione e é, na minha opinião, uma boa prática.

Agora, podemos adicionar a implementação à nossa classe impl:

ClassMethod postSampleEcho(body As %DynamicObject) As %DynamicObject
{
    Return ##class(Sample.REST.Service.API).OnMessage(##class(Sample.REST.Process.Echo).%ClassName(1), body)
}

A parte de codificação está finalizada. Em seguida, precisamos adicionar uma aplicação web chamada /sample-api (mesma que o basePath em nossa especificação), definir a classe Sample.REST.API.disp como Dispatch Class, e criar os seguintes business hosts na Produção:

  1. Sample.REST.Service.API
  2. Sample.REST.Process.Echo

Nossa Produção deve ficar assim:

Interface da Produção

Vamos tentar executar a requisição:

curl --location 'http://localhost:52773/sample-api/sample/echo' \
--header 'Content-Type: application/json' \
--data '{
  "name": "test",
  "count": 1
}'

Receberemos uma resposta:

{
    "status": "ok",
    "message": "Request processed for test with count 1"
}

E também, nas mensagens do serviço Sample.REST.Service.API, obteremos um rastreamento visual (visual trace) detalhando nossa chamada.

Isso é tudo o que eu queria contar hoje sobre APIs REST em Produções de Interoperabilidade. Recomendo usar o iris-web-swagger-ui para fornecer sua especificação OpenAPI para equipes externas de forma conveniente. E use a autenticação JWT integrada para segurança. Acredito que é assim que uma API harmoniosa na plataforma IRIS Data deve ser. 

Sinta-se à vontade para fazer qualquer pergunta.