Páginas filhas
  • Desenvolvimento de APIs para o produto Logix

Índice

Conceito


Uma API (acrônimo de Application Programming Interface ou Interface de Programação de Aplicação em português) é um conjunto de rotinas e padrões estabelecidos por um software para a utilização de seus recursos por aplicativos que não pretendem envolver-se em detalhes da implementação do software, mas apenas usar seus serviços. De modo geral, uma API é composta por uma série de funções acessíveis somente por programação e que permitem utilizar características do software menos evidentes ao usuário tradicional.

No desenvolvimento do produto Logix uma API antigamente chamava-se RNL acrônimo de Regra de Negócio Logix.

Desenvolvimento


Para o correto desenvolvimento é preciso ter em mente alguns cuidados que devem ser sempre considerados na construção de uma API:

  • Nunca desenvolva ou solicite qualquer interação com o usuário, seja ela através de telas, mensagens ou perguntas;
  • Simplifique suas funções, não é uma boa prática construir uma função "faz tudo", separe sempre sua lógica em diversas funções que possam ser executadas de formas distintas;
  • Evite que suas funções dependam do produto, lembre-se que elas poderão ser executadas através de outros produtos ou serviços; e
  • Sempre desenvolva visando a solução de único objetivo de negócio. Uma API para manutenção de pedidos não pode conter manutenção de empresas, por exemplo.

Nos próximos itens deste documento, serão detalhadas as técnicas que devem ser seguidas na construção do código fonte da API.

Clique AQUI para obter o código 4GL base de uma API para desenvolvimento e complemente o código conforme a necessidade da sua API.

Para específicos

Utilize sempre como base para a criação de uma API TOTVS o Guia de Implementação de API TOTVS, disponível em: http://tdn.totvs.com/x/nDUxE

01. Introdução


     O desenvolvimento de APIs permite a exposição e o consumo de dados para integração (front-end, portais, customizações, etc) com o back-end do produto Logix, de maneira segura e padronizada.

     A estrutura de integração de APIs Logix suporta o envio de requisições no estilo de arquitetura REST com o desenvolvimento da regra de negócio em 4GL e está disponível para utilização conforme apresentado no quadro abaixo:


Matriz de Evolução

Versão / ReleaseFuncionalidade
12.1.22

Requisições através dos verbos GET / POST / PUT / DELETE utilizando o contexto /restlogix

Classe utilitária para tratamento das respostas da requisição (response) em 4GL.

12.1.24

Requisições através dos verbos GET / POST / PUT / DELETE utilizando o contexto /api

Classe utilitária para tratamento das respostas da requisição (response) em 4GL.

O contexto /restlogix está em processo de depreciação.


02. Formato URL


      O Guia de Implementação de API TOTVS define que o formato das URIs dos endpoints devem conter o nome do produto, o módulo, a versão da API e o recurso alvo da funcionalidade em questão. Tomando como exemplo o endpoint de integração do recurso de "Dimensões" do módulo de "WMS" do produto "Logix", a URI básica deste serviço deve ser: /api/wms/v1/dimensoes


A URL definida pelo Padrão de API TOTVS segue o seguinte formato:

/api/<módulo>/<versão API>/<recurso>/

Informações que forem passadas após o recurso, serão tratadas como parâmetros PATH.


03. Serviços 4GL

     Para "publicar" a funcionalidade 4GL basta criar o programa (.4gl) com o seguinte caminho: <módulo>/api/<versão API>/<recurso>.4gl. 

     A sub-pasta "api" passa a concentrar todas as funcionalidades de integração dos módulos existentes. Outros caminhos e parâmetros podem ser adicionados a URL, mas sempre de acordo com o Guia de Implementação de APIs.


Importante

Os programas 4GL disponibilizados, deverão seguir o padrão de localização abaixo:

Exemplos:

Objeto de NegócioFunção de roteamento
\suprimentos\obrigacoes_fiscais\api\v1\transportadoras.4globf_v1_transportadoras
\suprimentos\suprimentos\api\v1\estoque.4glsup_v1_estoque
\adm_producao\manufatura\api\v1\apontamento_horas.4glman_v1_apontamento_horas


     Dentro do código fonte 4GL a definição da função principal (roteadora) é de fundamental importância, pois é ela que será primeiramente chamada e que definirá como será a execução das outras funções com base na requisição solicitada. Segue abaixo um exemplo de definição:


04. Exemplo de desenvolvimento das funções em 4GL


NOMES DAS FUNÇÕES

Exemplo de definição de funções e como será realizada a requisição WEB de execução destas funções:

FunçãoRequisição

FUNCTION

obf_v1_transportadoras()

GET/POST/PUT/DELETE

/api/obf/v1/transportadoras

FUNCTION

sup_v1_estoque()

GET/POST/PUT/DELETE

/api/sup/v1/estoque

FUNCTION

man_v1_apontamento_horas()

GET/POST/PUT/DELETE

/api/man/v1/apontamento_horas

APIS Específicas de clientes (Fábrica de Software)


Para funções de APIs específicas mantidas pela Fábrica de Software, conforme citado na nota IMPORTANTE no final do tópico 03. Serviços 4GL,  a sigla do módulo indicada no início das funções é acrescida de "e".

Exemplos:  

   obfe_v1_transportadoras()

   supe_v1_estoque()

   mane_v1_apontamento_horas()


Quais funções devem sempre existir e respeitar padronização de nomenclatura:

  • Função principal roteadora. Esta função não possui parâmetros e tem como objetivo a definição das rotas.
  • Funções definidas na função roteadora para cada uma das rotas. Estas funções devem ter sempre a definição de 1 parâmetro do tipo VARCHAR(10) que será a referência do objeto de classe LJsonObject que é enviado no ato de toda requisição. Também sempre devem ter um retorno no formato JSON que será o retorno da requisição, utilizando para isso a classe LRestLogixResponse. Ambas classes estão detalhadas em Classes Utilitárias.


EXEMPLO DE IMPLEMENTAÇÃO

DATABASE Logix

{#
 #Função roteadora principal que será executada pelo Logix REST quando feito 
 #uma requisição que combine com o módulo, a versão e o recurso da função.
 #}
#---------------------------#
 FUNCTION wms_v1_dimensoes()
#---------------------------#
{DEFINIÇÃO DAS ROTAS}

    #Inicia a técnica para definição das rotas de execução das funções conforme a requisição recebida.
 	CALL _ADVPL_create_rest_logix_routes()
 	
    #Definição de rota onde toda requisição de método GET, que contenha o filtro a seguir,
    #será direcionada para função wms_v1_dimensoes_get_normal().
    #FILTRO:
    # - Serão capturadas todas as requisições que possuírem um parâmetro Path "/normal" 
    #   e um parâmetro Query "fields" com qualquer conteúdo (*). 
 	CALL _ADVPL_add_rest_logix_routes("GET",                      #--# Método #--#
 	                                  "/normal/*/",               #--# Path Param a ser filtrado #--#
 	                                  "fields=*",                 #--# QueryParam a ser filtrado #--#
 	                                  "wms_v1_dimensoes_get_normal")  #--# Função a ser executada #--#
 	 
    #Definição de outra rota, onde toda requisição de método GET, que contenha o filtro a seguir,
    #será direcionada para função wms_v1_dimensoes_get_ordenado().
    #FILTRO:
    # - Serão capturadas todas requisições que contenha qualquer parâmetro Path ("/*"  indica "Todos Paths" ou "nenhum") 
    #   e um parâmetro Query "order" com valor "dimensao"
    CALL _ADVPL_add_rest_logix_routes("GET",                           
 	                                  "/*",                            
 	                                  "order=dimensao",                
 	                                  "wms_v1_dimensoes_get_ordenado")  
 	
    #Definição de outra rota, onde todas as requisições de método GET, que possuírem quaisquer parâmetros 
    #(Query e/ou Path) informados, serão direcionadas para a função wms_v1_dimensoes_get().
 	CALL _ADVPL_add_rest_logix_routes("GET",                          
 	                                  "/*",                           
 	                                  "",                             
 	                                  "wms_v1_dimensoes_get")        
 	                             
    #Definição de rota onde todas as requisições de método POST, que possuírem quaisquer parâmetros (Query e/ou Path) informados, 
    #serão direcionadas para a função wms_v1_dimensoes_post().
	CALL _ADVPL_add_rest_logix_routes("POST",                        
 	                                  "/*",                         
 	                                  "",                           
 	                                  "wms_v1_dimensoes_post")       
 	                                  
    #Definição de rota onde todas as requisições de método PUT (update), que possuírem quaisquer parâmetros (Query e/ou Path) informados, 
    #serão direcionadas para a função wms_v1_dimensoes_put().
 	CALL _ADVPL_add_rest_logix_routes("PUT",                           
 	                                  "/*",                            
 	                                  "",                             
 	                                  "wms_v1_dimensoes_put")  
 	                                  
    #Definição de rota onde todas as requisições de método DELETE, que possuírem quaisquer parâmetros (Query e/ou Path) informados, 
    #serão direcionadas para a função wms_v1_dimensoes_delete().
    CALL _ADVPL_add_rest_logix_routes("DELETE",                       
 	                                  "/*",                          
 	                                  "",                            
 	                                  "wms_v1_dimensoes_delete")   
END FUNCTION

#------------------------------------------------------#
 FUNCTION wms_v1_dimensoes_get_normal(l_json_reference)
#------------------------------------------------------#
#FUNÇÃO GET COM PATH PARAM "NORMAL" ADICIONADA COMO ROTA NA FUNÇÃO wms_v1_dimensoes()

	DEFINE l_json_reference VARCHAR(10)
	DEFINE l_json VARCHAR(1000)
	
	.
	.
	.

	RETURN l_json
END FUNCTION


#-------------------------------------------------------#
 FUNCTION wms_v1_dimensoes_get_ordenado(l_json_reference)
#-------------------------------------------------------#
#FUNÇÃO GET COM PATH PARAM "ORDENADO" ADICIONADA COMO ROTA NA FUNÇÃO wms_v1_dimensoes()

	DEFINE l_json_reference VARCHAR(10)
	DEFINE l_json VARCHAR(1000)
	
	.
	.
	.

	RETURN l_json
END FUNCTION

IMPORTANTE

Para PATH com valor obrigatório informe ?* (interrogação seguido de asterisco), que determina que o PATH tem ao menos 1 caracter qualquer (?), seguido de 0 ou mais caracteres (*).

Exemplo: "/normal/?*/", #--# Path Param obrigatório a ser filtrado #--#

OBSERVAÇÕES

Algumas considerações sobre o uso de roteamento através da função _ADVPL_add_rest_logix_routes():

  • Os roteamentos devem ser definidos sempre do mais específico (detalhado) para o mais genérico (simples).
  • O Logix REST utiliza a função Match() do ADVPL, que basicamente permite o uso do sinal "?" (interrogação) como coringa para uma determina um único caracter obrigatório e o sinal "*" (asterisco) para um conjunto de caracteres variáveis (zero ou mais caracteres). Neste caso, quando houver necessidade de ter ao menos 1 caracter obrigatório, deve-se informar "?*" que determinará "1 ou mais caracteres" e quando informar "*" (apenas asterisco) determinará "0 ou mais caracteres". Para mais detalhes acesse a documentação da função Match().
  • Podem ser definidos um ou mais parâmetros de pesquisa utilizando a "," (vírgula) como separador e a pesquisa sempre será realizada utilizando o operador AND.
    Exemplo:
    CALL _ADVPL_add_rest_logix_routes("GET",                           
     	                              "/*",                            
     	                              "order=dimensao,cliente=*",                
     	                              "wms_v1_get_dimensoes_ordenado")

    Veja que o parâmetro QUERY foi repassado como "order=dimensao,cliente=*" onde existem 2 filtros separados por uma vírgula, sendo:

FILTRO 1: order=dimensao

FILTRO 2: cliente=*

  • Atente aos valores repassados como QUERY e PATH pois são processados como CaseSensitve.


05. Formato Mensagem JSON

      A variável de referência de um objeto LJSONOBJECT, recebida pela requisição como parâmetro na função 4GL definida na rota conterá informações completas da requisição, desde informações do HEADER, QUERY PARAMs, PATH PARAMs e o próprio PAYLOAD.

      Através desta mensagem, o desenvolvedor poderá efetuar os devidos filtros e lógicas necessárias. 


EXEMPLO DE MENSAGEM JSON

{ 
    uri: valor,
	method: GET,
    headers: {},
	pathParams: [ "param1", "param2" ],
	queryParams: { query1: valor1, query2: valor1},
	payload: {}
}

06. Classes Utilitárias 


      Com o objetivo de facilitar a manipulação dos objetos JsonObject recebidos e enviados pela API 4GL, foram desenvolvidas algumas classes de utilitários:

LJSONObject

Permite manipular o JSON recebido como parâmetro pela função. 

Acesse a documentação referente ao componente Logix LJSONOBJECT para saber mais detalhes de como manipular informações recebidas num formato JSON.


LRestLogixResponse 

Trata a criação do JSON de response da requisição.

Acesse a documentação referente ao componente LOGIX LRESTLOGIXRESPONSE para saber mais detalhes a respeito das propriedades SET e GET disponíveis.


07. Exemplo de montagem do JSON de retorno


EXEMPLO MONTAGEM DO JSON DE RETORNO DA REQUISIÇÃO

Criação de Response
#-----------------------------------------------#
 FUNCTION wms_v1_dimensoes_get(l_json_reference)
#-----------------------------------------------#
	DEFINE l_json_reference VARCHAR(10)
    DEFINE l_logix_response VARCHAR(10)
	DEFINE l_json CHAR(1000)
	
    #--# Utilização do método SERIALIZE da classe LJSONOBJECT  #--#
	LET l_json = _ADVPL_get_property(l_json_reference,"SERIALIZE")
	
	#--# Criação da resposta padronizada utilizando a classe LRestLogixResponse  #--#
	LET l_logix_response = _ADVPL_create_component(NULL,"LRestLogixResponse")
    CALL _ADVPL_set_property(l_logix_response,"PAYLOAD",l_json,"payload")

	#--# Propriedades opcionais (mensagem de erro, detalhamento do erro, código do erro #--#
    CALL _ADVPL_set_property(l_logix_response,"MESSAGE","Mensagem de erro","Detalhamento do erro", "10")
	#--# Definição do status de retorno da requisição
    CALL _ADVPL_set_property(l_logix_response,"STATUS",'200')

	#--# Opcional, utilizada quando o conteúdo de retorno for um JSONArray #--#
	CALL _ADVPL_set_property(l_logix_response,"HAS_NEXT",TRUE)
    
    #Retorno do conteúdo do componente LRestLogixResponse no formato JSON
	RETURN _ADVPL_get_property(l_logix_response,"GENERATE")
END FUNCTION


08. Exemplo de leitura dos dados da requisição


EXEMPLO DE LEITURA DOS DADOS ENVIADOS NO JSON DA REQUISIÇÃO

A leitura dos dados do JSON da requisição é realizada utilizando a função _ADVPL_get_property(l_json_reference, "VALUE",<campo>)

Carregando um Array de Record
   DEFINE l_json_data          CHAR(30000)
   DEFINE l_json_reference     VARCHAR(10)    
   DEFINE l_length_ajusts      SMALLINT
   DEFINE l_status             SMALLINT
   DEFINE l_ind                SMALLINT 
   
   DEFINE ma_ajust_bxa_adt_integ  ARRAY[500] OF RECORD
	                                               cod_tip_val      LIKE ad_valores.cod_tip_val,
	                                               valor            LIKE ad_valores.valor,
	                                               num_ad_nf_orig   LIKE adiant.num_ad_nf_orig,
	                                               ser_nf           LIKE adiant.ser_nf,
	                                               ssr_nf           LIKE adiant.ssr_nf,
	                                               cod_fornecedor   LIKE adiant.cod_fornecedor,
	                                               dat_mov          LIKE mov_adiant.dat_mov      
	                                            END RECORD                  
                                                                
    #Esta informação da variável l_json_data abaixo é apenas um exemplo do conteúdo JSON que já é recebido pelas requisições REST.
    LET l_json_data = 
   '{
   		"payload": {
   		    "ajustBxaAdtInteg": [
	   		    {
	   		       "codTipVal": "1",
	   		       "valor": 1000,
	   		       "numAdNfOrig": 123456,
	   		       "serNf": "AD",
	   		       "ssrNF": "A",
	   		       "codFornecedor": "12",
	   		       "datMov": "10/10/2019"
	   		    },
	   		    {
	   		       "codTipVal": "2",
	   		       "valor": 3000,
	   		       "numAdNfOrig": 654321,
	   		       "serNf": "AF",
	   		       "ssrNF": "B",
	   		       "codFornecedor": "13",
	   		       "datMov": "01/12/2018"
	   		    },
	   		    {
	   		       "codTipVal": "3",
	   		       "valor": 2000,
	   		       "numAdNfOrig": 555555,
	   		       "serNf": "AJ",
	   		       "ssrNF": "C",
	   		       "codFornecedor": "14",
	   		       "datMov": "31/10/2019"
	   		    }
   		    ]
   		}
   }'
   
   LET l_json_reference = _ADVPL_create_component(NULL, "LJSONOBJECT")
   LET l_status         = _ADVPL_get_property(l_json_reference,"ACTIVATE",l_json_data CLIPPED)
   
   LET l_length_ajusts = _ADVPL_get_property(l_json_reference,"LENGTH","payload/ajustBxaAdtInteg")

   #Leitura dos itens da lista "ajustBxaAdtInteg" existentes no JSON da requisição   
   FOR l_ind = 1 TO l_length_ajusts
       LET ma_ajust_bxa_adt_integ[l_ind].cod_tip_val    = _ADVPL_get_property(l_json_reference, "VALUE", "payload/ajustBxaAdtInteg["||l_ind||"]/codTipVal")
       LET ma_ajust_bxa_adt_integ[l_ind].valor          = _ADVPL_get_property(l_json_reference, "VALUE", "payload/ajustBxaAdtInteg["||l_ind||"]/valor")
       LET ma_ajust_bxa_adt_integ[l_ind].num_ad_nf_orig = _ADVPL_get_property(l_json_reference, "VALUE", "payload/ajustBxaAdtInteg["||l_ind||"]/numAdNfOrig")
       LET ma_ajust_bxa_adt_integ[l_ind].ser_nf         = _ADVPL_get_property(l_json_reference, "VALUE", "payload/ajustBxaAdtInteg["||l_ind||"]/serNf")
       LET ma_ajust_bxa_adt_integ[l_ind].ssr_nf         = _ADVPL_get_property(l_json_reference, "VALUE", "payload/ajustBxaAdtInteg["||l_ind||"]/ssrNF")
       LET ma_ajust_bxa_adt_integ[l_ind].cod_fornecedor = _ADVPL_get_property(l_json_reference, "VALUE", "payload/ajustBxaAdtInteg["||l_ind||"]/codFornecedor")
       LET ma_ajust_bxa_adt_integ[l_ind].dat_mov        = _ADVPL_get_property(l_json_reference, "VALUE", "payload/ajustBxaAdtInteg["||l_ind||"]/datMov")
       
       CALL CONOUT("------------------- Exibindo os valores --------------------")
       CALL CONOUT(ma_ajust_bxa_adt_integ[l_ind].cod_tip_val)
       CALL CONOUT(ma_ajust_bxa_adt_integ[l_ind].valor)
       CALL CONOUT(ma_ajust_bxa_adt_integ[l_ind].num_ad_nf_orig)
       CALL CONOUT(ma_ajust_bxa_adt_integ[l_ind].ser_nf)
       CALL CONOUT(ma_ajust_bxa_adt_integ[l_ind].ssr_nf)
       CALL CONOUT(ma_ajust_bxa_adt_integ[l_ind].cod_fornecedor)
       CALL CONOUT(ma_ajust_bxa_adt_integ[l_ind].dat_mov)
   
   END FOR


09. Exemplo de leitura dos filtros da requisição (QueryParam e PathParam)


EXEMPLO DA LEITURA DOS FILTROS QueryParam E PathParam ENVIADOS NO JSON DA REQUISIÇÃO

Carregar QueryParam e PathParam
#--------------------------------------------------#
 FUNCTION vdp_v1_clientes_nacionais(l_json_reference)
#--------------------------------------------------#
    DEFINE l_json_reference VARCHAR(10)
    DEFINE l_logix_response VARCHAR(10)
	DEFINE l_query_param CHAR(200)
	DEFINE l_path_param  CHAR(200)
	DEFINE l_json_retorno CHAR(1000)
	
    # Carrega o valor da primeira entrada do Query Param #--# 
    # Exemplo de QueryParams ?order=dimensao&cliente=1234: '[["order","dimensao"],["cliente","1234"]]'
    # No exemplo abaixo, a busca acontece no primeiro QueryParam (order) buscando o nome dele (order) 
	LET l_query_param = _ADVPL_get_property(l_json_reference,"VALUE","queryparams[1][1]") 

    # Neste exemplo, a busca acontece no primeiro QueryParam (order) buscando o valor dele (dimensao) 
	LET l_query_param = _ADVPL_get_property(l_json_reference,"VALUE","queryparams[1][2]") 

    # No exemplo abaixo, a busca acontece no segundo QueryParam (cliente) buscando o nome dele (cliente) 
	LET l_query_param = _ADVPL_get_property(l_json_reference,"VALUE","queryparams[2][1]") 

    # Neste exemplo, a busca acontece no primeiro QueryParam (cliente) buscando o valor dele (1234) 
	LET l_query_param = _ADVPL_get_property(l_json_reference,"VALUE","queryparams[2][2]")
	
	# Carrega o primeiro Path Param 
    # Exemplo: '{"pathparams":["path1","path2"]}'
	LET l_path_param = _ADVPL_get_property(l_json_reference,"VALUE","pathparams[1]") 
	
	LET l_json_retorno = '{"queryParam":"',LOG_alltrim(l_query_param),'","pathParam":"',LOG_alltrim(l_path_param),'"}'
	
	# Executar a regra de negócio neste ponto 
	LET l_logix_response = _ADVPL_create_component(NULL,"LRestLogixResponse")
    CALL _ADVPL_set_property(l_logix_response,"PAYLOAD",l_json_retorno,"payload")
    CALL _ADVPL_set_property(l_logix_response,"STATUS",'200')
    
	RETURN _ADVPL_get_property(l_logix_response,"GENERATE")
END FUNCTION