Criando serviços REST avançados no Protheus
Com a chegada de THF, será rotineira a criação de serviços REST para comunicação entre ERP e a camada web. Mas será que precisamos de um framework para isso?
Há diversos tratamentos que um serviço REST deve fazer por padrão, principalmente no verbo GET. Entre eles, podemos citar:
- Paginação de registros
- Ordenação de registros
- Seleção de campos
- Filtro de registros com oData4
- Gerar ws de rotinas legadas que retornam array
Seria muito trabalhoso criar todo esse mecanismo para cada serviço, além do retrabalho para correção de bugs ou implementação de uma melhoria. Por estes e mais alguns motivos se torna interessante a ideia de uma base para construir os serviços e permitir foco somente no que sua API deve fazer.
A seguir, mostramos um exemplo simples de como desenvolver um serviço que retorne algumas informações do cadastro de produtos.
Pré-requisitos:
Protheus com versão 12.1.23 ou posterior.
Protheus configurado com o servidor REST. Os detalhes de como configurar o serviço podem ser encontrados na documentação oficial aqui: http://tdn.totvs.com/pages/viewpage.action?pageId=185747842 .
Fontes utilizados como exemplo estão disponíveis no Github em: https://github.com/andersontoledo/ProtheusRestSamples/tree/master/Artigo01 .
Iremos começar criando o programa com nome products.prw.
Parte 1 — Criação do webservice
Iremos abordar superficialmente a criação do webservice REST, mas a documentação oficial tem bastante conteúdo (o link está no final do artigo ).
1) Adicionar as includes
2) Definição do webservice
Seguindo a documentação padrão, a sintaxe do WSRESTFUL é a seguinte:
WSRESTFUL <cServiceName> DESCRIPTION <cDescription> [SECURITY <cSecurity>] [FORMAT <cFormat>] [SSL ONLY]
cServiceName: Indica o nome da classe REST que será declarada.
cDescription: Informa a descrição do serviço. Essa informação é utilizada na listagem dos serviços REST disponíveis no TOTVS| Application Server e serve como documentação do serviço.
cSecurity: Informa o nome da rotina que tem relação com a API REST (exemplo MATA030 ou MATA410) e deve ser usado no controle de segurança.
cFormat: Informe o formato de exportação do serviço. Esta informação é utilizada na listagem dos serviços REST.
SSL ONLY: Indica que a classe só permitirá o acesso via conexão segura do tipo SSL (Secure Socket Layer).
No nosso caso fizemos uma declaração sem utilizar todos os recursos, sendo:
Nome do serviço: products
Descrição: endpoint products API
Formato: application/json
Não indicamos nada referente a segurança, algo importante quando se tratar de um serviço de produção.
3) Definição das propriedades que irão receber os queryParm
Inicialmente, iremos tratar somente o número da página solicitada, nos demais tutoriais iremos ver com mais calma as outras propriedades.
4) Definição do método
A sintaxe para definição do método é a seguinte:
WSMETHOD <cVerb> [cId] DESCRIPTION <cDescription> [WSSYNTAX <cSintax>] [PATH <cPath>][PRODUCES <cProduce>] [TTALK <cTTalkVersion>]
cVerb: verbo HTTP, sendo: PUT, POST, GET ou DELETE
cId: ID para diferenciar e possibilitar a criação de métodos que utilizam verbos http repetidos
cDescription: Descrição do método REST
cSintax: Sintaxe HTTP da chamada REST. Esta informação é utilizada apenas para documentação do REST.
cPath: Definição do endpoint que irá acionar aquele método.
cProduce: O que o WS irá produzir (no caso retornar)
cTTalkVersion: Valor “v1” para sinalizar que o método utiliza o padrão de mensagem de erro do TTALK.
Novamente criamos um exemplo simplificado onde:
Verbo: GET
Id: ProdList
Descrição: Retorna uma lista de produtos
Sintax:/api/v1/products
Path:/api/v1/products
Produces: APPLICATION_JSON
5) Encerrar a definição do WS
Simples, fechamos a definição do WS
6) Definir o tratamento do método GET
Implementar o método GET com o Id ProdList. No nosso caso chamamos a Static Function getPrdList passando o próprio objeto do WS como parâmetro. Podemos notar que estamos tratando somente a propriedade Page do QueryParam. Deixaremos para abordar os demais parâmetros da WSMETHOD em outro artigo. Neste, o foco é explorar a API…
Parte 2 — Utilizando a API
Até agora, ficamos apenas na criação do WS e nenhuma novidade até aqui… Mas, enfim, iremos começar a escrever nossa API.
Toda classe que for utilizar a API Rest deve herdar a classe FWAdapterBaseV2, a escolha, por ser uma herança da classe, foi por aumentar a flexibilidade da API, permitindo o override de alguns métodos. E alguns devem ser obrigatoriamente reimplementados (Sim, deveria ser uma classe abstrata. E o pessoal da Tecnologia está estudando a implementação desta funcionalidade no TLPP ).
Vamos por partes…
Criar um novo fonte para o Adapter com o nome PrdAdapter.prw.
1) Adicionar as includes
2) Criação da classe herdando da FWAdapterBaseV2
Definimos os métodos que irão compor nossa classe, sendo:
New() o método construtor
GetListProd() o método que irá fornecer as informações necessárias para a API gerar as informações
3) Método New
O método New da classe FWAdapterBaseV2 recebe dois parametros:
cVerb o método HTTP utilizado
lList se o retorno será um único item ou uma lista de informações.
Neste caso, queremos uma lista de produtos
4) Método GetListProd
Este método irá realizar a chamada dos métodos. Iremos utilizar o mínimo necessário para retornar a lista de produtos, a API possui mais funcionalidades que serão apresentadas em outros artigos.
Iremos definir um Mapa de campos Json/ERP através do método AddMapFields().
Alguns milisegundos podem ser a diferença entre o cliente receber as informações ou ter um timeout. Então, por questões de performance, a Query foi dividida para não ser necessário realizar um parse completo na statement para adicionar os controles internos.
Utilizamos o método SetQuery() para informar a consulta que iremos utilizar, SetWhere() para informar a cláusula Where e SetOrder() para definir a ordenação.
4) Método AddMapFields
O método AddMapFields adiciona o mapa de campos entre o Json e o campo a ser consultado. Com base nestes valores que a API irá gerar o Json de retorno. No exemplo foi chamada através de uma static function para um código mais limpo.
A sintaxe é:
AddMapFields( cFieldJson, cFieldQuery, lJsonField, lFixed, aStruct )
E os parâmetros são:
cFieldJson, Nome do campo no objeto Json
cFieldQuery, Nome do campo que será utilizado no ResultSet
lJsonField, Se .T. informa que o campo será exportado ao Json
lFixed, Se .T. informa que o campo não pode ser removido pelo FIELDS do QueryParam
aStruct, Vetor com a estrutura do campo no padrão {“CAMPO”, “TIPO”, Tamanho, Decimal}.
4) Método SetQuery
Informa a Query a ser utilizada na API. Deve ser passada a string com a consulta SQL e obrigatoriamente tenha o id #QueryFields# que será alterado dinamicamente com base no Mapa de campos e os campos solicitados para retorno via QueryParam, e #QueryWhere# também alterado dinamicamente com a string informada no método SetWhere e os demais QueryParams.
Vamos aos testes
Utilizando nosso velho amigo Postman, informamos a url com o verbo GET e temos nossa listagem de produtos.
Olhando no final do json temos “hasNext” : true, isso indica que a API retornou a quantidade de registros que configuramos ( no caso 10), mas existem registros que ainda não foram enviados.
Para consultar a próxima página adicione ?page=2 no final da url.
Com o “hasNext” : true, sabemos que a listagem chegou ao final. E como a paginação é realizada via banco de dados, o retorno é muito rápido. Mas o teste de performance fica para outro artigo.
Conclusão
Vimos que com poucas linhas de código foi possível criar uma API Rest com algumas funcionalidades interessantes, mas ainda tem muito o que apresentar nos próximos artigos… Já pensou em transformar aquela função que retorna um array em API com ordenação, paginação e filtros… mesmo ela já sendo escrita a vários anos? Fique de olho nos próximos artigos.
Referências