Artigo
· Fev. 18 34min de leitura

API REST com Swagger no InterSystems IRIS

API REST com Swagger no InterSystems IRIS

 

Olá,

O protocolo HTTP permite a obtenção de recursos, como documentos HTML. É a base de qualquer troca de dados na Web e um protocolo cliente-servidor, o que significa que as requisições são iniciadas pelo destinatário, geralmente um navegador da Web.

As API REST se beneficiam deste protocolo para trocar mensagens entre cliente e servidor. Isso torna as APIs REST rápidas, leves e flexíveis. As API REST utilizam os verbos HTTP GET, POST, PUT, DELETE e outros para indicar as ações que desejam realizar.

Quando realizamos uma chamada a uma API RESt na verdade o que ocorre é uma chamada HTTP. A API recebe esta chamada e de acordo com o verbo e o caminho solicitados a API executa a ação desejada. No caso da implementação em Iris podemos ver isso claramente na área de definição da URLMap:

XData UrlMap
{
<Routes>
        <Route Url="/cliente" Method="POST" Call="Incluir"  Cors="true"/>
        <Route Url="/cliente/:chave" Method="PUT" Call="Alterar"  Cors="true"/>
        <Route Url="/cliente/:chave" Method="DELETE" Call="Deletar"  Cors="true"/>
        <Route Url="/cliente/:chave" Method="GET" Call="Pesquisar"  Cors="true"/>
        <Route Url="/cliente" Method="GET" Call="Listar"  Cors="true"/>
    </Routes>
}

Veja que temos o caminho (Url) e o verbo (Method) definido para cada chamada (Call). Assim o código que atende a API sabe o que deve realizar.

Essa estrutura na API REST não atende somente ao roteamento das ações que chegam para a API.

Ela também é a base para a criação do arquivo de Swagger da API, conforme vemos na documentação da classe %REST.API, método GetWebRESTApplication: https://docs.intersystems.com/irislatest/csp/documatic/%25CSP.Documatic....

Vamos ver como podemos gerar essa documentação então.

Primeiro vamos publicar nossa API. Vamos utilizar a mesma API do artigo https://community.intersystems.com/post/using-rest-api-flask-and-iam-intersystems-iris-part-1-rest-api

Basta então seguir as orientações e utilizar o código que o artigo disponibiliza para termos nossa API publicada.

Uma vez publicada a API vamos incluir uma nova rota no URLMap e um novo método no nosso código:

 

        <Route Url="/openapi" Method="GET" Call="OpenAPI" Cors="true"/>

ClassMethod OpenAPI() As %Status
{
    Do ##class(%REST.Impl).%SetContentType("application/json")
    
    Set sc = ##class(%REST.API).GetWebRESTApplication("", "/rest/servico", .swagger)
    
    If $$$ISERR(sc) {
        Quit sc  // If an error occurred, exit the method
    }

        
    Write swagger.%ToJSON()

    Quit $$$OK
}

 

Inclua no código da API a nova rota e o método associado a ela. Vamos agora fazer um teste. Vamos abrir o Postman e chamar o endpoint referente a geração do Swagger, que é o /openapi:

 

E a seguir temos a definição completa do Swagger gerado pela nossa chamada:

{

    "info": {

        "title": "",

        "description": "",

        "version": "",

        "x-ISC_Namespace": "DEMO"

    },

    "basePath": "/rest/servico",

    "paths": {

        "/cliente": {

            "post": {

                "parameters": [

                    {

                        "name": "payloadBody",

                        "in": "body",

                        "description": "Request body contents",

                        "required": false,

                        "schema": {

                            "type": "string"

                        }

                    }

                ],

                "operationId": "Incluir",

                "x-ISC_ServiceMethod": "Incluir",

                "x-ISC_CORS": true,

                "responses": {

                    "default": {

                        "description": "(Unexpected Error)"

                    },

                    "200": {

                        "description": "(Expected Result)"

                    }

                }

            },

            "get": {

                "operationId": "Listar",

                "x-ISC_ServiceMethod": "Listar",

                "x-ISC_CORS": true,

                "responses": {

                    "default": {

                        "description": "(Unexpected Error)"

                    },

                    "200": {

                        "description": "(Expected Result)"

                    }

                }

            }

        },

        "/cliente/{chave}": {

            "put": {

                "parameters": [

                    {

                        "name": "chave",

                        "in": "path",

                        "required": true,

                        "type": "string"

                    },

                    {

                        "name": "payloadBody",

                        "in": "body",

                        "description": "Request body contents",

                        "required": false,

                        "schema": {

                            "type": "string"

                        }

                    }

                ],

                "operationId": "Alterar",

                "x-ISC_ServiceMethod": "Alterar",

                "x-ISC_CORS": true,

                "responses": {

                    "default": {

                        "description": "(Unexpected Error)"

                    },

                    "200": {

                        "description": "(Expected Result)"

                    }

                }

            },

            "delete": {

                "parameters": [

                    {

                        "name": "chave",

                        "in": "path",

                        "required": true,

                        "type": "string"

                    }

                ],

                "operationId": "Deletar",

                "x-ISC_ServiceMethod": "Deletar",

                "x-ISC_CORS": true,

                "responses": {

                    "default": {

                        "description": "(Unexpected Error)"

                    },

                    "200": {

                        "description": "(Expected Result)"

                    }

                }

            },

            "get": {

                "parameters": [

                    {

                        "name": "chave",

                        "in": "path",

                        "required": true,

                        "type": "string"

                    }

                ],

                "operationId": "Pesquisar",

                "x-ISC_ServiceMethod": "Pesquisar",

                "x-ISC_CORS": true,

                "responses": {

                    "default": {

                        "description": "(Unexpected Error)"

                    },

                    "200": {

                        "description": "(Expected Result)"

                    }

                }

            }

        },

        "/openapi": {

            "get": {

                "operationId": "OpenAPI",

                "x-ISC_ServiceMethod": "OpenAPI",

                "x-ISC_CORS": true,

                "responses": {

                    "default": {

                        "description": "(Unexpected Error)"

                    },

                    "200": {

                        "description": "(Expected Result)"

                    }

                }

            }

        },

        "/test": {

            "get": {

                "operationId": "Test",

                "x-ISC_ServiceMethod": "Test",

                "x-ISC_CORS": true,

                "responses": {

                    "default": {

                        "description": "(Unexpected Error)"

                    },

                    "200": {

                        "description": "(Expected Result)"

                    }

                }

            }

        }

    },

    "swagger": "2.0"

}

 

O link a seguir nos dá acesso a documentação do Iris que aponta uma ferramenta que recebe a saída da nossa chamada de API e transforma em uma interface amigável de documentação e testes do serviço: https://docs.intersystems.com/irislatest/csp/docbook/DocBook.UI.Page.cls...

Vamos acionar esta URL e informar o caminho para recuperar a definição Swagger da nossa API: https://swagger.io/tools/swagger-ui/

Vamos substituir a chamada de demo da páguma (a chamada petstore) pela nossa chamada: http://127.0.0.1/iris_iss/rest/servico/openapi e veja a tela com a documentação Swagger gerada:

No alto da chamada temos as informações básicas da nossa API:

E podemos ainda navegar pelas chamadas e visualizar informações importantes, como na chamada POST para incluir um novo cliente:

Mas, como vimos um pouco acima, a definição Swagger é na verdade um arquivo no formato JSON que fica disponível para nós na forma de um %DynamicObject (ver documentação em https://docs.intersystems.com/irislatest/csp/documatic/%25CSP.Documatic....) após a chamada ##class(%REST.API).GetWebRESTApplication que realizamos no nosso método OpenAPI. Desta forma, baseados na definição OpenAPI 2.0 podemos incluir ou remover informações do nosso Swagger. Vamos, por exemplo, enriquecer as informações básicas da API. Segundo a definição da Versão 2.0 (https://swagger.io/specification/v2/ ) podemos ter as seguintes informações disponibilizadas:

 

Vamos então completar as informações que podemos disponibilizar. Para isso vamos alterar nosso método OpenAPI, incluindo as informações básicas, protocolo aceito, autenticação e dados de acesso (host e basePath):

ClassMethod OpenAPI() As %Status
{
              Do ##class(%REST.Impl).%SetContentType("application/json")
    
    Set sc = ##class(%REST.API).GetWebRESTApplication("", "/rest/servico", .swagger)
    
    If $$$ISERR(sc) {
        Quit sc  // If an error occurred, exit the method
    }

 


              Do ##class(%REST.Impl).%SetContentType("application/json")
    
    Set sc = ##class(%REST.API).GetWebRESTApplication("", "/rest/servico", .swagger)
    
    If $$$ISERR(sc) {
        Quit sc  // If an error occurred, exit the method
    }
    
    Set info = {
        "title": "Cliente",
        "description": "API de Manipulação de Cliente",
        "version": "1.00"
    }

   Do swagger.%Set("info", info)

 

   Do swagger.%Set("host", "127.0.0.1")
   Do swagger.%Set("basePath", "/iris_iss/rest/servico")

 

   Set schemes = []
   Do schemes.%Push("http")

   Do swagger.%Set("schemes", schemes)
             
   Set security = [{"basicAuth":[]}]

   Do swagger.%Set("security", security)


   Set securityDefinitions = {}
   Do securityDefinitions.%Set("basicAuth", {})
   Do securityDefinitions.basicAuth.%Set("type", "basic")   

   Do swagger.%Set("securityDefinitions", securityDefinitions)  

 
    Write swagger.%ToJSON()

    Quit $$$OK
}


Chamando novamente a documentação Swagger na página de visualização agora temos a seguinte saída:

 

Veja que a nossa documentação está bem mais completa, com as informações sobre a API mais detalhadas, como por exemplo, o mecanismo de autenticação (Basic Auth), o protocolo aceito (HTTP) e as definições de versão, descrição e URL.

Podemos agora enriquecer a definição das chamadas, colocando a estrutura esperada nos payloads e exemplos de dados para a chamada. Para isso vamos sobrepor a informação em paths para “/cliente”:

ClassMethod OpenAPI() As %Status
{
              Do ##class(%REST.Impl).%SetContentType("application/json")
    
    Set sc = ##class(%REST.API).GetWebRESTApplication("", "/rest/servico", .swagger)
    
    If $$$ISERR(sc) {
        Quit sc  // If an error occurred, exit the method
    }


              Do ##class(%REST.Impl).%SetContentType("application/json")
    
    Set sc = ##class(%REST.API).GetWebRESTApplication("", "/rest/servico", .swagger)
    
    If $$$ISERR(sc) {
        Quit sc  // If an error occurred, exit the method
    }
    
    Set info = {
        "title": "Cliente",
        "description": "API de Manipulação de Cliente",
        "version": "1.00"
    }
    Do swagger.%Set("info", info)

 

    Do swagger.%Set("host", "127.0.0.1")
    Do swagger.%Set("basePath", "/iris_iss/rest/servico")

    Set schemes = []
    Do schemes.%Push("http")
    Do swagger.%Set("schemes", schemes)
             
    Set security = [{"basicAuth":[]}]
    Do swagger.%Set("security", security)

    Set securityDefinitions = {}
    Do securityDefinitions.%Set("basicAuth", {})
    Do securityDefinitions.basicAuth.%Set("type", "basic")
    Do swagger.%Set("securityDefinitions", securityDefinitions)
   
    Set incluirPesquisar = {
        "post": {
            "summary": "Criar um novo cliente",
            "description": "Recebe os dados de um cliente e cadastra no sistema.",
            "parameters": [
                {
                    "name": "body",
                    "in": "body",
                    "required": true,
                    "schema": {
                        "type": "object",
                        "properties": {
                            "nome": {
                                "type": "string",
                                "description": "Nome do cliente"
                            },
                            "idade": {
                                "type": "integer",
                                "description": "Idade do cliente"
                            }
                        },                              
                        "required": ["nome", "idade"],
                                           "example": {
                                                          "nome": "João Silva",
                                                          "idade": 30
                                           }
                    }
                }
            ],
            "responses": {
                          "default": {
                    "description": "Falha na chamada da API"
                },
                "200": {
                    "description": "Cliente criado com sucesso"
                },
                "406": {
                    "description": "Falha na inclusão do cliente"
                }                
            }
                            },
              "get": {
                    "summary": "Recupera todos os clientes",
                    "description": "Retorna a lista de clientes com suas informações.",
                    "responses": {
                                  "default": {
                    "description": "Falha na chamada da API"
                },
                      "200": {
                        "description": "Lista de clientes",
                        "schema": {
                          "type": "object",
                          "properties": {
                            "clientes": {
                              "type": "array",
                              "items": {
                                "type": "object",
                                "properties": {
                                  "chave": {
                                    "type": "string",
                                    "description": "Chave única do cliente"
                                  },
                                  "nome": {
                                    "type": "string",
                                    "description": "Nome do cliente"
                                  },
                                  "idade": {
                                    "type": "integer",
                                    "description": "Idade do cliente"
                                  }
                                },
                                "required": ["chave", "nome", "idade"]
                              }
                            }
                          },
                          "example": {
                            "clientes": [
                              {
                                "chave": "1",
                                "nome": "Maria",
                                "idade": 35
                              },
                              {
                                "chave": "2",
                                "nome": "Julio",
                                "idade": 57
                              },
                              {
                                "chave": "4",
                                "nome": "Julia",
                                "idade": 25
                              },
                              {
                                "chave": "5",
                                "nome": "Julia",
                                "idade": 22
                              }
                            ]
                          }
                        }
                      }
                    }      
                            }            
      }
    Do swagger.paths.%Set("/cliente", incluirPesquisar)
   
 
    Write swagger.%ToJSON()

    Quit $$$OK
}

 

 

Chamando novamente a documentação podemos visualizar agora os métodos POST para incluir um cliente e GET para recuperar um cliente:

Veja que já temos uma explicação do que é cada um dos métodos disponíveis para o POST e o GET que sobrescrevemos.

Note também não temos mais as tags "x-ISC_ServiceMethod" e "x-ISC_Namespace" que não colocamos na nossa nova definição.

Clicando para expandir o método POST por exemplo, agora temos visualmente um exemplo de chamada (Example Value):

E o formato do payload da chamada (Model):

Já na recuperação dos clientes, temos a definição do Response devolvido com exemplo:

E também com o modelo esperado como resposta:

 

Ainda, podemos testar as chamadas a API pela própria página de documentação. Veja o botão Try it out disponível na página. Clique nele, e a seguir em Execute e veja o resultado:

 

Estas possibilidades para a abordagem Code First de uma API REST nos abrem muitas possibilidades de documentação, permitindo controlar o que será divulgado, adicionando ou subtraindo as informações que julgamos convenientes no material publicado. A definição OpenAPI é muito completa e detalhada. Existem diversas informações que podemos incluir ou retirar, dependendo do que precisamos ou desejamos disponibilizar nela.

Até a próxima!

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