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.
Informações |
---|
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.
Aviso |
---|
|
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:
Informações |
---|
|
Versão / Release | Funcionalidade |
---|
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. |
|
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
Aviso |
---|
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.
Informações |
---|
|
Os programas 4GL disponibilizados, deverão seguir o padrão de localização abaixo: ![](/download/attachments/389550568/image2018-10-10_16-4-43.png?version=1&modificationDate=1539198283157&api=v2)
Exemplos: Objeto de Negócio | Função de roteamento |
---|
\suprimentos\obrigacoes_fiscais\api\v1\transportadoras.4gl | obf_v1_transportadoras | \suprimentos\suprimentos\api\v1\estoque.4gl | sup_v1_estoque | \adm_producao\manufatura\api\v1\apontamento_horas.4gl | man_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:
Informações |
---|
![](/download/attachments/389550568/image2018-10-10_16-4-14.png?version=1&modificationDate=1539198254163&api=v2)
|
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ção | Requisiçã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
|
Nota |
---|
title | 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
Expandir |
---|
title | Clique para expandir e ver o exemplo... |
---|
|
Bloco de código |
---|
language | ruby |
---|
theme | Confluence |
---|
| 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
Dica |
---|
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
Informaçõ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=* |
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
Bloco de código |
---|
language | js |
---|
theme | Confluence |
---|
|
{
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
Expandir |
---|
title | Clique para expandir e ver o exemplo... |
---|
|
Bloco de código |
---|
language | ruby |
---|
theme | Confluence |
---|
title | Criação de Response |
---|
linenumbers | true |
---|
| #-----------------------------------------------#
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>)
Expandir |
---|
title | Clique para expandir e ver o exemplo... |
---|
|
Bloco de código |
---|
language | ruby |
---|
theme | Confluence |
---|
title | Carregando um Array de Record |
---|
linenumbers | true |
---|
| 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
Expandir |
---|
title | Clique para expandir e ver o exemplo... |
---|
|
Bloco de código |
---|
language | ruby |
---|
theme | Confluence |
---|
title | 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 |
|