Utilizando a API RestFul dos modelos de dados do ERP Protheus

Após algum tempo sem realizar postagens devido as correrias do dia-a-dia, voltamos a falar sobre o tema webservice REST no ERP Protheus.

Sugerimos que vocês (nosso público) fizessem-nos solicitações de postagens que achem interessantes. Seguindo a maioria das solicitações, nessa postagem iremos realizar uma abordagem muito interessante… A utilização do webservice REST com o modelo de dados (vulgo MVC) do ERP Protheus.

Para que consiga acompanhar o exemplo da postagem, sugiro que confira a postagem que a Ju escreveu no link que será disponibilizado logo abaixo. Pois será com base nessa publicação que daremos continuidade.

Link: https://mvcadvpl.wordpress.com/2017/04/10/hello-word/

 

Pessoas, sugiro que prestem bastante atenção nos detalhes da escrita do POST, pois é uma funcionalidade extremamente útil para nós desenvolvedores e que demanda de apenas uma única linha de código …

É isso mesmo, você não leu errado, teremos apenas uma linha de código para ativarmos a funcionalidade.

Então … bora lá!

Premissas:

  • Possuir um ambiente parametrizado para a utilização de webservice REST (conforme demonstrado na postagem xxx);
  • Possuir conhecimento básico em MVC;
  • Conforme citado acima, irei utilizar um programa em MVC que a Ju desenvolve, logo, para conseguir acompanhar essa postagem, também é uma premissa que tenha esse projeto compilado em seu ambiente.

Após as premissas acimas estiverem “ok”, vamos criar o nosso programa que publicará “on-air” a nossa API baseada em um modelo de dados, abaixo segue o código:

#Include ‘Protheus.ch’

#Include ‘Parmtype.ch’

#Include “FWMVCDEF.CH”

PUBLISH USER MODEL REST NAME Aluno SOURCE CMVC_01

Eu menti, quando disse acima que teríamos 1 única linha de código, havia me esquecido dos “headers files” ou mais conhecidos como “.ch”.

Tirando a parte dos includes, onde creio que a maioria já conheça ao menos um pouco de sua funcionalidade, vamos entender o trecho abaixo.

PUBLISH USER MODEL REST NAME <nome> source <fonte> RESOURCE OBJECT <objeto>

Esse é o trecho que irá disponibilizar a nossa API “on-air”.

Entendendo a diretiva de compilação:

Logo após o comando PUBLISH USER MODEL REST NAME temos a “tag” <nome>, essa tag deverá ser preenchida com o nome da sua API, por exemplo:

O nosso modelo de dados é responsável por realizar um CRUD de Alunos, logo o nome da minha API será “Aluno”.

Antes de entendermos os próximos trechos da diretiva de compilação, é importante saber que temos 2 formas diferentes de configurar a nossa API.

  • A primeira é possuir o modelo de dados em um único fonte (prw) e a publicação em outro;
  • A segunda é possuir tanto o modelo de dados, quanto a publicação no mesmo fonte (prw).

A primeira forma é a que iremos utilizar neste exemplo, onde, o nosso modelo de dados está em um programa diferente do programa de publicação da API, para que a API seja publicada dessa maneira, as diretivas de compilação devem ser aplicadas da seguinte forma:

PUBLISH USER MODEL REST NAME <nome> source <fonte>

Conforme visto acima, iremos substituir a tag <nome>, pelo nome da nossa API e adicionamos o seguinte o trecho:

source <fonte>

A tag <fonte>, deverá ser substituída pelo nome do programa onde está o modelo de dados, no nosso caso é o CMVC_01.

A segunda forma é quando possuímos o modelo e a publicação no mesmo fonte. Nesse caso a diretiva de compilação deve ser aplicada da seguinte maneira

PUBLISH USER MODEL REST NAME <nome> RESOURCE OBJECT <objeto>

Conforme visto acima, iremos substituir a tag <nome>, pelo nome da nossa API e adicionamos o seguinte temos o trecho:

RESOURCE OBJECT <objeto>

A tag <objeto> deverá ser substituída pelo objeto da model.

Na TDN temos um exemplo bem demonstrado desse caso, por isso não irei entrar em detalhes (TDN Api RestFul).

Após compilado no RPO, vamos testar a utilização da nossa funcionalidade.

Para API’s do modelo de dados do ERP, o link de acesso fica da seguinte maneira:

<servidor:porta>/<URI>/FwModel/<nome_da_API>/<PK>

Entendendo a URL acima:

  • <servidor:porta> : Servidor + porta no qual está configurador o servidor HTTP do webservice REST.
  • <URI> : URI configurada no ini do AppServer do webservice REST dentro da seção HTTPURI:
  • FwModel: Default
  • <nome_da_API>: Nome da API definido na tag <nome> na diretiva de compilação de publicação do REST.
  • <PK>: Primary Key, para realização de filtros em operações do tipo GET, PUT, DELETE.

 

ATENÇÃO!!!

  • A chave PK deverá ser informada no formato ENCODE64.
  • A chave PK deverá ser a chave da tabela principal do modelo

 

Agora vamos testar a nossa aplicação:

No meu caso, o link para consumo da API ficou o seguinte:

http://localhost:8012/rest/fwmodel/ALUNO

E caso você tenha acompanhado as outras postagens, creio que o seu esteja parecido.

Através da ferramenta POSTMAN, se fizermos um GET nessa URL, teremos o retorno de todos os “Alunos” cadastrados na nossa base de dados:

IMG_1

Conforme a imagem acima, temos diversos registros na base de dados. Podemos conferir através da tag total.

Segue abaixo um exemplo de uma requisição GET de um registro específico, ou seja, informando a PK:

IMG_2

Podemos ver que só foi retornado o registro de parâmetro informado na URL.

Aparentemente é um código bem estranho de diferenciar, mas utilizando alguns sites da internet ou até mesmo funções do framework ADVPL, conseguimos gerar a chave para execução de testes.

Para realizarmos a inclusão de um novo registro utilizando a nossa API que está referenciando um modelo de dados existente no ERP, basta:

  • Utilizar a mesma estrutura do JSON retornado na requisição GET;
  • Apagar a PK da URL;
  • Alterar os dados da requisição para os dados desejados
  • Excluir a tag pk do body
  • Modificar a tag operation para o conteúdo 3.

Abaixo o exemplo de uma requisição do tipo POST, porém com dados de uma PK existente:

IMG_3

Repare que é retornado um erro de validação, pois já existe algum registro com aquela informação.

O ponto que nos chama atenção nesse caso é que não escrevemos nenhuma linha de código para validação, o retorno está vindo direto da rotina que referenciamos quando montamos o nosso programa de publicação da API.

Abaixo irei alterar o body de requisição, para realizarmos a inclusão do registro no ERP Protheus:

IMG_4

Repare que a execução do método foi concluída com sucesso. Para conferir, vamos acessar diretamente na tela do ERP:

IMG_5

Abaixo um exemplo da execução do método PUT, onde para sua execução:

  • Foi informado a PK para posicionamento no registro através da URL;
  • Alterado a tag operation para o conteúdo 4;
  • Alterado o conteúdo no body de requisição para o novo conteúdo que desejamos que seja gravado no ERP:

IMG_6

No exemplo acima, foi alterado o registro incluído anteriormente. Ao executar a requisição acima no POSTMAN, deverá ser atualizado o registro no ERP:

IMG_7

Por fim… A execução do método DELETE para realizar a exclusão de registros:

Para execução do método DELETE, basta enviar uma requisição do tipo DELETE e informar na URL a PK para posicionamento do registro:

IMG_8

Se conferirmos no ERP, o registro deverá estar com o status de excluído:

IMG_9

Pessoal… Atenção para alguns detalhes:

Por ser uma funcionalidade relativamente nova, a API RestFul do ERP Protheus ainda não possui uma documentação bem exemplificada, logo, todas as conclusões e testes apresentados acima foram desenvolvidos por conta própria na base da “tentativa e erro”. Podendo existir maneiras mais simples para execução dos procedimentos descritos acima.

Espero que a postagem seja útil para vocês!

Victor Andrade.

Anúncios

Manipulando Dados do Model

Muitos desenvolvedores que iniciam o uso do MVC ficam na duvida de como manipular dados sem utilizar o aCols, já que por padrão o MVC não cria as variáveis aHeader e aCols para o grid. Nesse post vou mostrar como obter e atribuir valor no modelo de dados.

Obtendo Dados

Método getValue()

Para obter o conteúdo de um campo do modelo de dados você vai precisar saber o nome do campo e também o sub modelo onde esse campo foi criado, com posse dessas duas informações você vai fazer uso do método GetValue do objeto de model (Classe FWFormModel)

O método getValue recebe dois parâmetros:

  1. ID do submodelo (aquele ID que você define quando usa addField ou addGrid)
  2. ID do campo

O tipo de conteúdo retornado depende do tipo do campo, se o campo é carácter o conteúdo será carácter, se o campo é logico o conteúdo será logico e assim por diante.

cNome := oModel:GetValue("SA1MASTER","A1_NOME")

Existe uma variação do uso desse método, eu mostrei acima o método usando o objeto do Model, mas podemos usar também diretamente no objeto de Field ou Grid, a diferença é que quando é usado diretamente no componente passamos apenas um parâmetro, com o ID do campo, não é necessário passar o ID do submodelo, pois o objeto já é o sub modelo em si.

oField := oModel:GetModel("SA1MASTER")

cNome := oField:GetValue("A1_NOME")

Existe duas situações onde é indicado usar essa variação:

  1. Você escreveu uma validação que recebe por parâmetro um objeto do tipo FWFormFieldsModel ou FWFormGridModel.
  2. Você vai fazer varias manipulações de um mesmo modelo.

Nesse caso obter o sub modelo (usando o metodo GetModel() ) e então trabalhar as manipulações diretamente no objeto é o mais indicado, pois internamente o método getValue do FWFormModel localiza o sub modelo passado no primeiro parâmetro e depois procura o conteúdo do campo, acessando diretamente o submodelo você vai executar o método mais rapidamente já que vai economizar uma instrução de busca para cada execução do método.

Função FWFldGet

Além do método apresentado existe a função FWFldGet que também tem por objetivo obter o conteúdo de um campo do Field ou Grid, a função recebe alguns parâmetros e está documentada aqui.

O uso dessa função é recomendado somente em validações chamadas diretamente dentro do SX3, isso porque a função recebe somente o ID do campo que precisa ser avaliado e então a função percorre todos os sub modelos até encontrar o campo e retornar o conteúdo.

Se o modelo possuir um ou dois sub modelos, não há tanto problema, mas imagine um modelo com 12 grids e o campo está justamente no ultimo grid, a função vai demorar mais tempo do que um getValue já que o método iria buscar o campo exatamente no sub modelo passado como parâmetro e a função vai percorrer todos os sub modelo até chegar no último e encontrar o campo.

Devemos sempre pensar em performance quando criamos nosso código, então use essa função com cautela.

Atribuindo Dados

Método SetValue()

O método setValue atribui conteúdo para um campo dentro do modelo de dados, disparando todas as validações e gatilhos que estiverem definidas para o campo que foi modificado.

O método é do objeto de model (classe FWFormModel) e recebe três parâmetros:

  1. ID do submodelo
  2. ID do campo
  3. Conteudo

Se a atribuição de valor for bem sucedida o método retorna verdadeiro, caso alguma validação não esteja correta o método retorna falso.

lOK := oModel:SetValue("SA1MASTER","A1_NOME","TESTE")

O SetValue também possui uma opção de ser utilizado diretamente no objeto de Field/Grid. Nesse caso o método recebe somente dois parâmetros:

  1. ID do campo
  2. Conteúdo
oField := oModel:GetModel("SA1MASTER")

lOk := oField:SetValue("A1_NOME","TESTE")

Função FWFldPut

A atribuição de valor também possui uma função que é a FWFldPut, ela possui o mesmo mecanismo que a FWFldGet, ou seja, busca o campo em todos os sub modelos até encontrar uma referencia.

Deve ser usada em casos especificos sempre pensando em performance.

Método LoadValue()

Existe uma última opção para atribuir valor no modelo que é o método LoadValue. Esse método não dispara as validações do campo e nem os gatilhos, mas muda o estado do modelo para modificado. É útil quando temos certeza do valor que deve ser atribuído ao campo e não é precisamos de validação e gatilho.

O método pode ser chamado diretamente no Model ou Field/Grid, recebe exatamente os mesmos parametros que o setValue.

//Metodo do modelo de dados

lOK:= oModel:LoadValue("SA1MASTER","A1_NOME","TESTE")


//Método do sub modelo

oField := oModel:GetModel("SA1MASTER")

lOK := oField:LoadValue("A1_NOME","TESTE")

 

É isso pessoal, espero o conteudo seja util.

Até a proxima!

[]s

 

MVC sem Dicionário de Dados

Eu já falei em algum outro texto que o MVC trabalha com estrutura de dados independente de dicionário. Na teoria isso é lindo, mas na pratica surgem muitas duvidas. Essa semana um amigo me questionou sobre o assunto e a conversa deu origem a esse post, espero que seja útil pra muitos Devs.

Quando falamos de estrutura de dados no MVC enxergamos automaticamente o Dicionário de Dados e porquê isso? Porque a estrutura tem campos, índices, gatilhos, pastas e grupos, igualzinho o Dicionário com o SX3, SX2, SX7 e afins. Todavia quando eu falo que a estrutura é independente de dicionário, eu quero dizer que:

  • Você pode criar campos que não existem no dicionario, como por exemplo campos de legenda, botão, imagem, entre outros;
  • Você pode criar uma estrutura inteiramente independente de dicionário de dados para ser utilizada no seu FormField ou FormGrid.

Esse post tem por objetivo mostrar a segunda opção, que é um modelo de dados totalmente independente de dicionário.

Mão na Massa!

O exemplo que vou usar aqui é uma rotina MVC para copiar um arquivo de uma pasta para outra, sim, normalmente um MVC é usado em cadastros, mas minha criatividade quis fazer um programa para realizar a copia do arquivo.

Meu programa vai ser bem simples, apenas com um FormField, não teremos grid aqui, mas a estrutura é igual para os dois componentes, então se você quiser um grid, vai conseguir chegar no seu objetivo se entender o funcionamento.

A estrutura de dados no Model é um objeto do tipo FWFormModelStruct, no componente é possível criar campo, gatilho e índice, a documentação de cada método você encontra no TDN nesse link.  No meu exemplo vou criar apenas 4 campos, da seguinte forma:

static function getModelStruct()
Local oStruct := FWFormModelStruct():New()
 
 oStruct:AddField('Arquivo Origem','Arquivo Origem' , 'ARQ', 'C', 50, 0, , , {}, .T., , .F., .F., .F., , )
 oStruct:AddField('Carregar','Carregar' , 'LOAD', 'BT', 1, 0, { |oMdl| getArq(oMdl), .T. }, , {}, .F., , .F., .F., .F., , )
 oStruct:AddField('Caminho de Destino','Caminho de Destino' , 'DEST', 'C', 50, 0, , , {}, .T., , .F., .F., .F., , )
 oStruct:AddField('Selecionar','Selecionar' , 'LOAD2', 'BT', 1, 0, { |oMdl| getDir(oMdl), .T. }, , {}, .F., , .F., .F., .F., , )
 
return oStruct

 

Para mostrar o formulário na View, vai ser necessário criar uma estrutura de dados também, a estrutura da View permite criar campos, pastas e grupos, se quiser se aventurar pelas opções dê uma olhada nesse link que encontrá toda a documentação da classe FWFormViewStruct.

Detalhe importante a ser considerado, o ID do campo na View e no Model devem ser iguais, por exemplo, no model meus campos foram identificados como “ARQ”, “LOAD”, “DEST” e “LOAD2”, veja abaixo que os IDs são iguais para a estrutura da View.

static function getViewStruct()
Local oStruct := FWFormViewStruct():New()

oStruct:AddField( 'ARQ','1','Arquivo Origem','Arquivo Origem',, 'Get' ,,,,.F.,,,,,,,, )
 oStruct:AddField( 'LOAD','2','Carregar','Carregar',, 'BT' ,,,,,,,,,,,, )
 oStruct:AddField( 'DEST','3','Destino','Destino',, 'Get' ,,,,.F.,,,,,,,, )
 oStruct:AddField( 'LOAD2','4','Selecionar','Selecionar',, 'BT' ,,,,,,,,,,,, )

return oStruct

Pronto, com esses dois objetos você consegue criar uma estrutura de dados independe de dicionário, no meu exemplo uso a estrutura em um objeto do tipo FormField, mas nada impede de você usar em um grid.

 

Mas é só isso? Fim?

É.. quase. Acompanhe meu raciocínio lógico.

Quando você cria uma estrutura baseada em dicionário, o MVC sabe qual tabela deve olhar para carregar os dados e também para gravar, exemplo, se você usa no MVC a tabela SA1, o MVC sabe que os dados virão da tabela SA1 e sabe que a gravação deve ocorrer na tabela SA1.

Quando você decide usar uma estrutura sem dicionário, como o MVC sabe de onde carregar e onde gravar os dados? A resposta é que o MVC não sabe.

Se a sua estrutura de dados não é baseada em uma tabela conhecida no dicionario, então é necessário informar para o MVC deonde/como os dados são carregados e como/onde deve ser feita a gravação.

Fica calmo(a) que isso não é a coisa mais complicada do mundo.

Para definir o Load você vai usar o 6º parametro do método AddFields (se for um Field)  ou o 8º parametro do método AddGrid (se for um Grid). O parâmetro deve enviar para o MVC um bloco de código que retorne um array com os dados que devem ser carregados para o modelo (isso ocorre em qualquer operação diferente de inclusão).

Os dados devem ser colocados exatamente na ordem dos campos da estrutura de dados, ou seja, se a sua estrutura tem o campo A, B e C (nessa ordem), os dados devem corresponder aos campos A, B e C, exatamente nessa ordem.

Exemplo de função com o FormField:

bLoad := {|oFieldModel, lCopy| loadField(oFieldModel, lCopy)}

oModel:addFields('ZA1MASTER',,oStruZA1,bPre,bPos,bLoad)


Static Function loadField(oFieldModel, lCopy)
Local aLoad := {}

   aAdd(aLoad, {xFilial("ZA1"), "000001", "Musica 1", Date(), "R"}) //dados
   aAdd(aLoad, 1) //recno
      
Return aLoad
Exemplo de Load com o FormGrid:

bLoad := {|oGridModel, lCopy| loadGrid(oGridModel, lCopy)}

oModel:AddGrid( 'ZA2DETAIL', 'ZA1MASTER', oStruZA2,, , , ,bLoad)

Static Function loadGrid(oGridModel, lCopy)
Local aLoad := {}

   aAdd(aLoad,{0,{xFilial("ZA2"), "000001", "01", "000100","AUTOR1","AUTOR"}})
   aAdd(aLoad,{0,{xFilial("ZA2"), "000001", "02", "000102","AUTOR2","AUTOR"}})
   aAdd(aLoad,{0,{xFilial("ZA2"), "000001", "03", "000104","AUTOR3","AUTOR"}})
   aAdd(aLoad,{0,{xFilial("ZA2"), "000001", "04", "000105","AUTOR4","AUTOR"}})
   aAdd(aLoad,{0,{xFilial("ZA2"), "000001", "05", "000106","AUTOR5","AUTOR"}})

Return aLoad

Se quiser saber mais sobre os métodos AddField e AddGrid acesse esse link.

Agora só falta o Commit, que é a parte do código responsável pela gravação dos dados do modelo. O Commit é um bloco passado no  4º parâmetro do método New do Model e é possivel substituir o bloco de Commit para realizar procedimentos antes ou depois da gravação do MVC. Se o seu modelo é gravado parte por você e parte pelo MVC, não esqueça de chamar a função FWFormCommit, caso contrario, sua gravação não vai funcionar corretamente.

Coloquei no gitHub o exemplo completo do programa sem dicionário de dados, se quiser acessar é só olhar aqui.

Aceito sugestões de temas para os próximos posts!

[]s Juliane

 

 

 

 

Entendo os métodos PUT, POST e UPDATE

Apenas corrigindo o título do blog, os métodos são:

GET, PUT e POST

Como vimos na primeira postagem sobre WS REST, em um serviço podemos ter 4 métodos dos quais são POST, UPDATE, DELETE e GET, cada um desses métodos retorna um código HTTP sendo de sucesso ou erro.

Abaixo iremos entender o funcionamento dos métodos PUT, POST e UPDATE e faremos um programinha para ver como isso funciona no ADVPL.

Na explicação do método, irei focar na explicação apenas do que se diz respeito ao REST, o restante é o nosso conhecido ADVPL. Porém caso tenha algum tipo de dúvida, PERGUNTE

 

GET:

O método GET é responsável por retornar ao cliente chamador alguma coisa qualquer, como por exemplo uma lista de clientes cadastrados.

Vamos ver abaixo um exemplo de método get:

WSMETHOD GET WSSERVICE CLIENTES

Local aArea        := GetArea()

Local cNextAlias   := GetNextAlias()

Local oCliente    := CLIENTES():New() // –> Objeto da classe cliente

Local oResponse    := FULL_CLIENTES():New() // –> Objeto que será serializado

Local cJSON       := “”

Local lRet        := .T.

 

BeginSQL Alias cNextAlias

SELECT A1_COD, A1_LOJA, A1_NOME, A1_END, A1_CGC, A1_CEP, A1_DDD, A1_TEL

FROM %table:SA1% SA1

WHERE SA1.%notdel%

EndSQL

(cNextAlias)->( DbGoTop() )

If (cNextAlias)->( !Eof() )

While (cNextAlias)->( !Eof() )

oCliente:SetCodigo( AllTrim((cNextAlias)->A1_COD ))

oCliente:SetLoja(       AllTrim((cNextAlias)->A1_LOJA))

oCliente:SetNome(       AllTrim((cNextAlias)->A1_NOME))

oCliente:SetCGC( AllTrim((cNextAlias)->A1_CGC ))

oCliente:SetCEP( AllTrim((cNextAlias)->A1_CEP ))

oCliente:SetEnd( AllTrim((cNextAlias)->A1_END ))

oCliente:SetDDD( AllTrim((cNextAlias)->A1_DDD ))

oCliente:SetTel(  AllTrim((cNextAlias)->A1_TEL ))

oResponse:Add(oCliente)

(cNextAlias)->( DbSkip() )

EndDo

cJSON := FWJsonSerialize(oResponse, .T., .T.,,.F.)

::SetResponse(cJSON)

Else

SetRestFault(400, “SA1 Empty”)

lRet := .F.

EndIf

RestArea(aArea)

Return(lRet)

Explicação do trecho de código acima:

Como vimos na primeira postagem, para serialização de um objeto JSON, precisamos passar um objeto para a função FWJSONSerialize, para isso nas linhas 5 e 6 temos a declaração de duas variáveis que são objetos das classes CLIENTES e FULL_CLIENTES.

  • CLIENTES é onde armazenamos os atributos do cliente.
  • FULL_CLIENTES é onde armazenamos uma lista dos objetos da classe CLIENTES

Logo a seguir fazemos uma query na tabela de clientes (SA1).

Na linha 19 temos a condição (cNextAlias)->( !Eof() ) , nesse momento eu verifico se a minha query retornou algum resultado.

Retorno da condicional positivo:

Caso tenha retornado então iremos alimentar os objetos como podemos ver o trecho de código entre as linhas 21 e 35.

Entre as linhas 23 e 30 alimentamos o objeto oCliente com os dados do cliente.

Na linha 32 adicionamos o objeto cliente já alimentado, na lista de clientes oResponse pois será essa lista que iremos serializar.

Após sair do loop onde alimentamos os objetos, utilizamos o seguinte comando para transformar a nossa lista de clientes (oResponse) em uma string JSON serializada e na sequência utilizamos o comando SetResponse para enviar a string ao cliente.

cJSON := FWJsonSerialize(oResponse, .T., .T.,,.F.)

::SetResponse(cJSON)

Retorno da condicional negativo:

Caso minha query não retorne nenhum registro então iremos utilizar a função SetRestFault para retornar um código HTTP de erro e uma mensagem.

SetRestFault(400, “SA1 Empty”)

Exemplo de retorno com o resultado negativo da condicional (sem dados no SA1):

1

Exemplo de retorno com o resultado positivo da condicional (com dados no SA1):

2

POST:

O método POST é responsável por incluir ou processar algum tipo de informação enviada pelo client.

Vamos ver abaixo um exemplo de método post:

Iremos receber um JSON com dados de um cliente e realizar a inclusão do mesmo caso não exista na base de dados.

Estrutura do JSON que vamos receber:

1

WSMETHOD POST WSRECEIVE RECEIVE WSSERVICE CLIENTES

Local cJSON        := Self:GetContent() // Pega a string do JSON

Local oParseJSON := Nil

Local aDadosCli   := {} //–> Array para ExecAuto do MATA030

Local cFileLog   := “”

Local cJsonRet   := “”

Local cArqLog     := “”

Local cErro       := “”

Local cCodSA1     := “”

Local lRet        := .T.

Private lMsErroAuto := .F.

Private lMsHelpAuto := .F.

// –> Cria o diretório para salvar os arquivos de log

If !ExistDir(“\log_cli”)

MakeDir(“\log_cli”)

EndIf

// –> Deserializa a string JSON

FWJsonDeserialize(cJson, @oParseJSON)

SA1->( DbSetOrder(3) )

If !(SA1->( DbSeek( xFilial(“SA1”) + oParseJSON:CLIENTE:CGC ) ))

cCodSA1 := GetNewCod()

Aadd(aDadosCli, {“A1_FILIAL”  , xFilial(“SA1”)  , Nil} )

Aadd(aDadosCli, {“A1_COD”     , cCodSA1   , Nil} )

Aadd(aDadosCli, {“A1_LOJA”    , “01”      , Nil} )

Aadd(aDadosCli, {“A1_CGC”     , oParseJSON:CLIENTE:CGC  , Nil} )

Aadd(aDadosCli, {“A1_NOME”    , oParseJSON:CLIENTE:NOME , Nil} )

Aadd(aDadosCli, {“A1_NREDUZ”  , oParseJSON:CLIENTE:NOME , Nil} )

Aadd(aDadosCli, {“A1_END”     , oParseJSON:CLIENTE:ENDERECO ,Nil}

Aadd(aDadosCli, {“A1_PESSOA”  , Iif(Len(oParseJSON:CLIENTE:CGC)== 11, “F”, “J”)          , Nil} )

Aadd(aDadosCli, {“A1_CEP”  , oParseJSON:CLIENTE:CEP    , Nil} )

Aadd(aDadosCli, {“A1_TIPO” , “F”                       , Nil} )

Aadd(aDadosCli, {“A1_EST”  , oParseJSON:CLIENTE:ESTADO , Nil} )

Aadd(aDadosCli, {“A1_MUN”  , oParseJSON:CLIENTE:MUNICIPIO,Nil} )

Aadd(aDadosCli, {“A1_TEL”  , oParseJSON:CLIENTE:TELEFONE, Nil} )

MsExecAuto({|x,y| MATA030(x,y)}, aDadosCli, 3)

If lMsErroAuto

cArqLog := oParseJSON:CLIENTE:CGC + ” – ” +SubStr(Time(),1,5 ) + “.log”

RollBackSX8()

cErro := MostraErro(“\log_cli”, cArqLog)

cErro := TrataErro(cErro) // –> Trata o erro para devolver para o client.

SetRestFault(400, cErro)

lRet := .F.

Else

ConfirmSX8()

cJSONRet := ‘{“cod_cli”:”‘ + SA1->A1_COD + ‘”‘;

+ ‘,”loja”:”‘  + SA1->A1_LOJA      + ‘”‘;

+ ‘,”msg”:”‘  + “Sucesso”          + ‘”‘;

+’}’

::SetResponse( cJSONRet )

EndIf

Else

SetRestFault(400, “Cliente já cadastrado: ” + SA1->A1_COD + ” – ” + SA1->A1_LOJA)

lRet := .F.

EndIf

Return(lRet)

Explicação do trecho de código acima:

Linha 3 -> Pega o JSON recebido e armazena na variável cJson

Linha 19 -> Deserializa Json recebido utilizando a função FWJsonDeserialize e disponibiliza os dados do mesmo no objeto oParseJSON.

Na linha 22 temos uma condicional que verifica se o CPF/CNPJ recebido já consta em nossa base de dados.

Retorno Negativo da condicional:

Caso o CGC informado não exista na nossa base de dados, então vamos realizar a inclusão do cliente usando ExecAuto.

Montamos o array e chamamos a função MsExecAuto.

Retorno para o Client:

  • Se no MsExecAuto der algum tipo de inconsistência então retornamos a mensagem de inconsistência para o cliente utilizando a função SetRestFault como podemos ver na linha 51.
  • Caso contrário eu devolvo um JSON com o código e loja do cliente que acabou de ser incluído, como podemos ver na linha 59.
    • Repare que na linha 59 eu estou utilizando o comando SetResponse utilizando uma string que montei “na unha”, como podemos ver no seguinte trecho:

 

cJSONRet := ‘{“cod_cli”:”‘ + SA1->A1_COD + ‘”‘;

+ ‘,”loja”:”‘  + SA1->A1_LOJA      + ‘”‘;

+ ‘,”msg”:”‘  + “Sucesso”          + ‘”‘;

+’}’

Sim … Podemos montar o nosso JSON “na unha”, apesar de ser muito mais trabalhoso.

Exemplo de retorno com erro do execauto:

1

Exemplo de retorno com um cliente já existente na base de dados:

2

Exemplo de retorno com sucesso na inclusão de um cliente:

3

PUT:

O método PUT é responsável por alterar algum tipo de informação enviada pelo client.

Vamos ver abaixo um exemplo de método put:

Iremos receber um JSON com dados de um cliente e realizar a alteração do mesmo caso exista na base de dados.

Neste exemplo teremos parâmetros recebidos via URL que no caso será o CGC (cpf ou cnpj) do cliente que queremos realizar a alteração e o conteúdo via JSON que será recebido via POST-Request (no corpo da requisição HTTP).

Estrutura da JSON que iremos receber:

1

WSMETHOD PUT WSRECEIVE RECEIVE WSSERVICE CLIENTES

Local cJSON := Self:GetContent() // –> Pega a string do JSON

Local cCGC  := Self:CGC // –> Pega o parâmetro recebido pela URÇ

Local lRet  := .T.

Local oParseJSON := Nil

Local aDadosCli   := {} //–> Array para ExecAuto do MATA030

Local cJsonRet   := “”

Local cArqLog     := “”

Local cErro       := “”

Private lMsErroAuto := .F.

If !ExistDir(“\log_cli”)

MakeDir(“\log_cli”)

EndIf

::SetContentType(“application/json”)

// –> Deserializa a string JSON

FWJsonDeserialize(cJson, @oParseJSON)

SA1->( DbSetOrder(3) )

If (SA1->( DbSeek( xFilial(“SA1”) + cCGC ) ))

   Aadd( aDadosCli, {“A1_NOME”, oParseJSON:CLIENTE:NOME     , Nil } )

Aadd( aDadosCli, {“A1_END” , oParseJSON:CLIENTE:ENDERECO , Nil } )

MsExecAuto({|x,y| MATA030(x,y)}, aDadosCli, 4)

If lMsErroAuto

cArqLog := oParseJSON:CLIENTE:CGC + ” – ” + SubStr( Time(),1,5 ) + “.log”

cErro := MostraErro(“\log_cli”, cArqLog)

cErro := TrataErro(cErro) // –> Trata o erro para devolver para o client.

SetRestFault(400, cErro)

lRet := .F.

Else

cJSONRet := ‘{“cod_cli”:”‘ + SA1->A1_COD + ‘”‘;

+ ‘,”loja”:”‘  + SA1->A1_LOJA      + ‘”‘;

+ ‘,”msg”:”‘   + “Alterado”   + ‘”‘;

+’}’

::SetResponse( cJSONRet )

EndIf

Else

SetRestFault(400, “Cliente não encontrado.”)

lRet := .F.

EndIf

Return(lRet)

O único ponto de atenção que deveremos ter nesse método é a linha 4, pois é ali que recebemos o CGC do cliente informado através da URL no qual iremos modificar.

Todo o restante dos comandos, já está explicado nos métodos POST e GET.

Exemplo de requisição efetuada com sucesso:

1

Exemplo de requisição efetuada com erro:

2

Link do GitHub:

https://github.com/VctrAndrade/RestAdvplPost02

Bom pessoal, espero que gostem e nos envie sugestões também 🙂

Abraços!

Entendendo o Grid do MVC

Quando falamos de MVC tem dois componente muito importantes que você precisa conhecer muito bem: o Field e o Grid. O field foi apresentado no post Hello Word e agora eu quero te apresentar o Grid.

O Grid é um componente que permite a manipulação de vários registros de uma única vez,  pra ficar mais claro, sempre que você possui um relacionamento do tipo 1:N a tabela que possui N registros será representada por um grid no MVC.

O Grid existe no Model e também existe na View, no Model ele é um componente capaz de armazenar em memória vários registros com várias colunas e na View ele exibe um browse com os registros armazenados no Model. Se você já fez uma GetDados em AdvPL, o grid é a representação da GetDados. Independente se você conhece uma GetDados ou não, o grid na View tem a seguinte aparência (na versão 12 do Protheus):

grid

Agora vamos para a parte divertida do post, colocar a mão na massa.

 

Criando o Grid no Model

Eu falei que o grid representa o “N” do relacionamento 1:N, logo se o grid é o “N”, então eu preciso relacionar o grid com um componente que represente o “1” do relacionamento, com isso eu quero te dizer que um grid sempre precisa de um componente pai, ou como falamos no dia a dia, um Owner.

O Owner do grid pode ser um Field ou outro Grid, dependendo da modelagem da sua aplicação.

Para criar o grid usamos o método AddGrid do Model, esse método recebe alguns parâmetros, mas o importante nesse momento são os três primeiros:

1. cID : Identificação do grid no model. O MVC usa ID para todos os componentes e esses IDs são usados em diversos momentos. Não há uma regra para compor o ID, mas é convencionado usar Alias da Tabela+”DETAIL”, ou seja, se for um grid para a tabela SC6, o ID seria “SC6DETAIL“. Claro que nada impede você de chamar seu ID de “BOLINHA”, você quem decide qual nome será dado.

2. cOwner : ID do componente pai, esse parâmetro define quem será o Owner do grid. O ID passado deve ser igual ao ID que foi usado na criação do field ou do grid pai. Falei na linha de cima que vamos usar os IDs em diversos momentos, esse é um deles.

3. oStruct: Estrutura de Dados do tipo FWFormModelStruct, essa estrutura pode ser criada com a função FWFormStruct igual ao que foi mostrado para a estrutura do Field no outro post.

Exemplo:

oModel:AddGrid("ZB6DETAIL", "ZB5MASTER", FWFormStruct(1,"ZB6"))

 

Opa! Ta pronto o grid então? Quase!

O grid tem um relacionamento com o Owner e é preciso definir para o MVC como esse relacionamento funciona, ou seja, quais campos são as chaves do relacionamento. Baseado nesse relacionamento o MVC fará a gravação das chaves e numa operação diferente de inclusão, usará o relacionamento para obter os registros que devem ser carregados para o Model. Isso significa que se sua aplicação está com problemas na gravação dos campos chaves, reveja o relacionamento, grandes chances do problema estar aí.

Para definir o relacionamento usamos o método SetRelation do model, ele recebe três parâmetros:

1. cID: ID do grid que estamos definindo o relacionamento

2. aRelation: Array de relacionamento dos campos. Esse array deve conter duas colunas, a primeira é o ID do campo do grid e a segunda o ID do campo do Owner que o grid está relacionado
3. cIndexKey: Chave de índice que deverá ser usada para ordenar os dados no grid

Exemplo:

oModel:SetRelation(“DETAILZB6”, ; 
                  {{“ZB6_FILIAL”,xFilial(“ZB6”)},; 
                  {“ZB6_CODTUR”,”ZB5_CODTUR”  }}, ; 
                  ZB6->(IndexKey(1)))

Agora sim, com esses dois métodos você tem um grid no Model.

Criando o Grid na View

Na View o grid é um componente de formulário, ele é exibido na tela como uma tabela com varias colunas, cada linha da tabela é um registro e cada coluna um campo da estrutura de dados. O usuário adiciona quantos registros ele desejar.

O Grid na View é componente de formulário que está relacionado com um grid do Model, como os dados e o controle do relacionamento estão no Model, na View não é necessário relacionar o Grid com um um Owner, na View vamos criar apenas a interface para exibir os dados do Model.

Para criar o componente usamos o método AddGrid da View, ele recebe três parâmetros:

1. cID: Identificação do formulário do grid na View

2. oStruct: Estrutura de Dados do tipo FWFormViewStruct, essa estrutura pode ser criada com a função FWFormStruct igual ao que foi mostrado para a estrutura do Field no outro post.

3. cIDModel: ID do grid do Model que se relacionará com a View, esse ID é o ID do grid que criamos no Model, nesse parâmetro definimos qual componente do Model conterá os dados que serão exibidos na interface gráfica do grid

Exemplo:

oView:AddGrid('FORM_ALUNOS' , FWFormStruct(2,"ZB6","ZB6DETAIL")

Todo formulário na View precisa de um box para ser exibido na tela, no exemplo do Hello Word tínhamos apenas um box que ocupava 100% do espaço da tela e exibia o Field. Quando temos mais de um formulário, então precisamos de mais boxes, um para cada formulário. O box é criado por percentual, então se um box ocupa 100%, dois boxes precisam ser divididos para que, somados ocupem 100%. Então, um pode ter 20 e outro 80, um 30 e o outro 70, um 50 e o outro 50, depende da sua necessidade, o importante é que no total, a soma do percentual dos boxes seja igual a 100.

Lembrando que o método para criar box horizontal é o createHorizontalBox:

oView:createHorizontalBox(cIdBox, nPercentual)

E o método para acoplar o formulário no box é o setOwnerView:

oView:SetOwnerView(cIDFormulario,cIDBox)

Futuramente quero criar um post apenas para falar de box, acho que esse é um tema interessante e que gera algumas duvidas.

 

Pronto, com esses métodos você consegue criar um grid para uma aplicação. Não existe limite de grids para seu programa, já vi um com 12 grids, o importante é sempre que criar um grid, criar um relacionamento para ele.

No próximo post pretendo abordar as validações que o grid e o field permitem fazer e como usar os métodos getValue e setValue para obter e atribuir valor ao modelo de dados.

Deixei no gitHub um fonte completo com grid, no exemplo tem um Field representando a tabela de Turmas e um grid representando a tabela de Alunos, você pode obter o exemplo aqui.

Espero que o conteúdo seja útil!

[]s, Juliane

Seu primeiro Webservice REST no ERP Protheus

  – O que é REST?

A web é amplamente utilizada e reconhecida principalmente por sua arquitetura robusta, escalável e tolerante a falhas. Quem sustenta esses fatores e lhe dá todo este poder é o protocolo HTTP (o protocolo HTTP é utilizado, em regra, quando se deseja evitar que a informação transmitida entre o cliente e o servidor seja visualizada por terceiros, como, por exemplo, no caso de compras online.). Atualmente, muitas vezes necessitamos integrar aplicações em ambientes totalmente diferentes e os WebServices são uma das maneiras mais comuns e fáceis de integrar os diferentes sistemas. Este post mostrará um pouco de um modelo de WebService chamado REST.

Representational State Transfer ou somente REST, é cada vez mais usado como alternativa ao “já antigo” SOAP onde que a principal crítica a esta é a burocracia, algo que REST possui em uma escala muito menor.
REST é baseado no design do protocolo HTTP, que já possui diversos mecanismos embutidos para representar recursos como código de status, representação de tipos de conteúdo, cabeçalhos, etc.

O principal nesta arquitetura são as URLs do sistema e os resources (resource é um recurso, entidade). Ele aproveita os métodos HTTP para se comunicar, que são:

GET: Solicita a representação de um determinado recurso. É definido como um método seguro e não deve ser usado para disparar uma ação (remover um usuário, por exemplo);

POST: As informações enviadas no corpo (body) da requisição são utilizadas para criar um novo recurso. Também é responsável por fazer processamentos que não são diretamente relacionados a um recurso.

DELETE: Remove um recurso.

PUT: Atualiza um recurso na URI especificada. Caso o recurso não exista, ele pode criar um. A principal diferença entre POST e PUT é que o primeiro pode lidar não somente com recursos, mas também pode fazer processamento de informações.

img-1

Agora que já entendemos um pouco sobre o que é um webservice rest, “vamos por a mão na massa” e desenvolver uma aplicação “Hello Word REST” como diz a Ju rs.

O primeiro passo para criarmos um serviço REST é configurar o Protheus como um servidor HTTP REST. Para isso vamos manipular o arquivo .ini do AppServer, conforme exemplo abaixo:

A função HTTP_START que prepara o AppServer como servidor HTTP para REST, necessitando ser configurada na seção ONSTART:

 

[GENERAL]

MAXSTRINGSIZE=10

 [ONSTART]

JOBS=HTTPJOB

REFRESHRATE=120

 

[HTTPJOB]

MAIN=HTTP_START

ENVIRONMENT=P12LOCAL

Habilitando o servidor HTTP para REST:

[HTTPV11]

Enable=1

Sockets=HTTPREST

Obs: A chave Sockets referencia a(s) seção(ões) com a configuração de porta e URL que a mesma irá atender, permitindo que o Application Server seja configurado com mais de uma porta HTTP do REST:

[HTTPV11]

Enable=1

Sockets=HTTPREST,HTTPREST2

Configuração do(s) socket(s) definidos na seção HTTPV11:

[HTTPREST]

Port=8080

IPsBind=

URIs=HTTPURI

Security=0


Parametro
Descrição
Port Porta HTTP
IPsBind Indica os IPs que serão atendidos por essa porta, se não informado atenderá qualquer IP / DNS associado ao servidor
URIs Seções com configuração de URL e ambiente (pelo menos uma seção)
Security Indica se a autenticação de requisição esta habilitada
Observação
O HTTP do REST verifica existência do campo Authorization no HEADER da requisição, porém a autorização é realizada pelo framework do produto que está/ utilizando o protocolo
Para facilitar o desenvolvimento e testes pode-se configurar a chave Security com valor 0 (zero) para desabilitar a autenticação

A chave URIs referência a(s) seção(ões) com a configuração da URL que a porta irá atender, permite também que seja configurado com mais de uma URL:

[HTTPREST]

Port=8012

IPsBind=

URIs=HTTPURI,HTTPURI2

Security=1

Configuração da(s) seções URIs:

[HTTPURI]

URL=/rest

PrepareIn=99,01

Instances=1,1

Parametro Descrição
URL Indica o endereço que será atendido
Nesse exemplo http://localhost:8012/rest
Preparein Informações de empresa e filial para preparação do ambiente das working threads
Instances Configuração de inicialização de working threads
CorsEnable Indica se habilita o CORS
Observaçoes
A chave CORS é utilizada para permitir que páginas WEB de diferentes domínio do qual o servidor HTTP do Protheus está alocado, consigam realizar a requisição na nossa URI.

Essa parte configuração não temos muito como fugir do que está descrito no TDN, está bem documentado por parte da TOTVS.

Após a configuração, o appserver.ini deverá ficar da seguinte maneira:

img2

Após a adição das seções no .ini, deve reiniciar o appserver e podemos ver no log que está no ar nosso servidor http/rest:

img0

Com nosso servidor web no ar, vamos acessar via navegador a URI que configuramos no .ini, para ter 100% de certeza que estamos prontos para “por a mão na massa”. Para acessar nossa URI, devemos acessar o caminho definido via navegador. Nesse caso, (exemplo acima) a URL que vamos acessar para testar nossa URI é a seguinte:

http://localhost:8012/rest

Deverá ser exibida a seguinte tela:

img1

Onde nada mais é que uma página WEB contendo todas as API’s rest que está disponível no ERP Protheus.

“Pondo a mão na massa”

A nossa primeira API rest do advpl, vamos fazer um programa para retornar a descrição, unidade de medida e o status de um determinado produto.

Passo 01: Includes

Para criar um webservice REST no Protheus, precisamos da utilização de um include específico, esse include possui alguns DEFINES que facilita muito o desenvolvimento da nossa funcionalidade. Esse include é o RestFul.CH.

#Include 'Protheus.ch'
#Include 'FWMVCDEF.ch'
#Include 'RestFul.CH'

User Function EREST_01()
Return

Acima definimos uma User Function, porém ela serve apenas com “Dummy Function” (Função fantasma) ou seja, ela serve apenas para reservarmos o nome do nosso “.prw” dentro do repositório, é claro que nada te impede de utilizar essa “Dummy function” como uma função de processamento chamada no nosso programa ADVPL.

 

Passo 02: Definição da estrutura do WebService

Para criarmos um webservice, precisamos definir a estrutura do mesmo, ou seja, os atributos e métodos que iremos disponibilizar.

Nesse nosso primeiro exemplo, como citado acima, vamos falar apenas do método GET (método responsável por retornar informações, se estivéssemos tratando de um CRUD esse “cara” seria o Ready).

WSRESTFUL PRODUTOS DESCRIPTION "Serviço REST para manipulação de Produtos"

WSDATA CODPRODUTO As String

WSMETHOD GET DESCRIPTION "Retorna o produto informado na URL" WSSYNTAX "/PRODUTOS || /PRODUTOS/{}"

END WSRESTFUL

Entendendo o trecho de código acima:

Linha 1:

  • WSRESTFUL -> Início da declaração da estrutura do Webservice;
  • PRODUTOS -> Nome do webservice
  • DESCRIPTION -> Descrição do serviço

Logo após definirmos a criação do webservice, deveremos definir quais atributos o mesmo terá.

Linha 3:

  • WSDATA -> Definição dos atributos

No nosso caso, teremos um único atributo que é o CODPRODUTO e esse atributo é do tipo string, porém ele poderia ser:

  • Float
  • Boolean
  • String
  • Array
  • Integer

Linha 5:

  • WSMETHOD -> Definição de um método
  • GET -> Define qual o tipo do método que estamos criando, podendo ser
    • GET
    • POST
    • UPDATE
    • DELETE
  • DESCRIPTION -> Descrição do método.
  • SYNTAX -> Sintaxe HTTP da chamada REST. Esta informação é utilizada na documentação do REST.

Linha 6:

  • END WSRESTFUL -> Encerra a declaração da estrutura do webservice.

Passo 03: Definição do método.

A definição do método é o passo onde vamos desenvolver toda a regra de negócio do método. Como nosso método é um GET, deveremos devolver um JSON para a aplicação CLIENT. Para a criação do JSON que vamos devolver para o Client, iremos utilizar a função FWJsonSerialize, veremos mais a frente sua utilização.

WSMETHOD GET WSRECEIVE CODPRODUTO WSSERVICE PRODUTOS
Local cCodProd := Self:CODPRODUTO
Local aArea    := GetArea()
Local oObjProd := Nil
Local cStatus  := ""
Local cJson    := ""

::SetContentType("application/json")

DbSelectArea("SB1")
SB1->( DbSetOrder(1) )
If SB1->( DbSeek( xFilial("SB1") + cCodProd ) )
    cStatus  := Iif( SB1->B1_MSBLQL == "1", "Sim", "Nao" )
    oObjProd := Produtos():New(SB1->B1_DESC, SB1->B1_UM, cStatus)
EndIf

cJson := FWJsonSerialize(oObjProd)

::SetResponse(cJson)

RestArea(aArea)
Return(.T.)

Entendendo o trecho de código acima:

Linha 1:

  • WsMethod -> Indica a iniciação de escrita do método
  • GET -> Tipo do método (Podendo ser GET, POST, UPDATE e DELETE).
  • WSRECEIVE -> Indica os parâmetros que iremos receber, no nosso caso, iremos receber o código de produtos via URL (Request Get), mas poderíamos receber também um JSON Object (Request POST).
  • WSSERVICE -> Indica o nome do webservice ao qual esse método pertence, no nosso caso o nome é PRODUTOS.

Abaixo temos a declaração das variáveis que iremos utilizar, onde no nosso caso, temos 2 que requer nossa atenção, que são as seguintes:

  • cCodProd -> Armazena o código do produto informado na URL.
  • oObjProd -> Classe que iremos criar para serializar o nosso json;

Linha 9:

Definição do tipo de retorno:

::SetContentType(“application/json”)

Após a definição do tipo de retorno, possuímos um pequeno trecho de código que nos serve para posicionar no produto que recebemos pela URL e iremos serializar o mesmo.

Para serializar o nosso registro JSON, devemos criar uma classe com os atributos que iremos retornar e alimentar os atributos com os valores desejados.

Para mais informações acesse a documentação da função na TDN:

http://tdn.totvs.com/display/public/mp/FWJsonSerialize+-+Serializa+qualquer+tipo+de+dado+no+formato+JSON

Linha 18:

Após alimentarmos o objeto criado com os atributos que queremos retornar, então devemos utilizar a função FWJsonSerialize que retorna a string do objeto passado como parâmetro.

cJson := FWJsonSerialize(oObjProd)

Linha 20:

Seta a resposta para a aplicação Client

::SetResponse(cJson)

Agora vamos ver a nossa aplicação na prática:

Feito toda a codificação do nosso exemplo e compilado no RPO, vamos verificar se o nosso WS Rest está disponível para utilização através do nossa URI:

img2

Agora iremos realizar o “consumo” da API Rest criada através da ferramenta Postman ou no nosso caso como se trata de uma API aberta e sem nenhum tipo de autenticação, também pode ser chamado direto pela URL no navegador:

Teste pelo Postman:

img3

Teste pelo Navegador:

img

Esse é um exemplo muito simples do que podemos fazer com webservices REST, nos próximos Posts vamos nos aprofundar cada vez mais.

Fontes no GitHub:

https://github.com/VctrAndrade/RestAdvplPost01

EREST_01.prw -> Serviço JSON.

EREST_01A.prw -> Classe de produto a ser serializado.

—————————————————————————————————————————————-

Obrigado Galera e espero que tenham gostado.

Victor Andrade

 

Referências:

http://www.matera.com/br/2012/10/22/como-funciona-um-webservice-rest/

http://tdn.totvs.com/display/home/TDN+-+TOTVS+Developer+Network

Hello Word

Aplicações Hello Word são comuns na primeira vez que você faz um programa em determinada linguagem, desenhei um “Hello Word” para MVC.

Vou usar como base para esse programa uma tabela customizada, para te ajudar eu criei um GitHub e coloquei nele o update contendo essa e outras tabelas, se quiser é só fazer o download aqui GitHub e aplicar no seu ambiente.

Passo 01: Includes

O MVC possui um include especifico, nele temos vários defines uteis, sempre que escrevo um programa em MVC uso esse include, o nome é FWMVCDef.CH

Coloque no seu fonte esse include, seu fonte deve ficar assim:

#Include 'Protheus.ch'
#Include 'FWMVCDEF.ch'

User Function CMVC_01()
Return

 

Passo 02:  Browse

Eu já disse que o Browse não é obrigatório, mas vou criar ele nesse exemplo, para que as ações principais do usuário ocorram a partir desse componente.

O foco aqui não é o Browse, então vamos criar um simples, baseado em dicionário de dados e tendo como tabela principal o alias ZB2, referente a Alunos.

User Function CMVC_01()
Local oBrowse
 oBrowse := FWMBrowse():New()
 oBrowse:SetAlias('ZB2')
 oBrowse:SetDescription('Cadastro de Alunos')
 oBrowse:Activate() 
Return

Passo 03: Menu

No menu vamos criar quais ações serão disponibilizadas para o usuário no Browse de Alunos.  Como ja disse anteriomente, o menu é definido dentro da função MenuDef do seu fonte e além disso a função deve sempre retornar um array com as ações a serem disponibilizadas.

O array deve possuir a seguinte estrutura:

  • Coluna 1: Título a ser exibido no botão do browse
  • Coluna 2: Nome da função que será executada quando o usuario clicar no botão
  • Coluna 3: Reservado
  • Coluna 4: Número da operação
  • Coluna 5: Reservado
  • Coluna 6: Reservado

Sendo que, a coluna 4 deve ser preenchida com o número ou o DEFINE de uma das operações abaixo:

  • 1 ou OP_PESQUISAR: Pesquisar
  • 2 ou OP_VISUALIZAR: Visualizar
  • 3 ou OP_INCLUIR: Incluir
  • 4 ou OP_ALTERAR: Alterar
  • 5 ou OP_EXCLUIR: Excluir
  • 8 ou OP_IMPRIMIR: Imprimir
  • 9 ou OP_COPIA : Copia

 

Static Function MenuDef()
Local aRotina := {}

aAdd( aRotina, { 'Visualizar', 'VIEWDEF.CMVC_01', 0, 2, 0, NIL } ) 
aAdd( aRotina, { 'Incluir'   , 'VIEWDEF.CMVC_01', 0, 3, 0, NIL } )
aAdd( aRotina, { 'Alterar'   , 'VIEWDEF.CMVC_01', 0, 4, 0, NIL } )
aAdd( aRotina, { 'Excluir'   , 'VIEWDEF.CMVC_01', 0, 5, 0, NIL } )
aAdd( aRotina, { 'Imprimir'  , 'VIEWDEF.CMVC_01', 0, 8, 0, NIL } )
aAdd( aRotina, { 'Copiar'    , 'VIEWDEF.CMVC_01', 0, 9, 0, NIL } ) 

Return aRotina

Notou que no exemplo todas as ações chamam a função VIEWDEF.CMVC_01 ?

O Browse é preparado para entender que essa instrução significa que você vai executar a View (MVC) do fonte CMVC_01 quando aquela operação for executada, ou seja, se o seu fonte se chama Zezinho.PRW a sua chamada será VIEWDEF.ZEZINHO.

Passo 04: Model

Como já foi dito, o model é o responsável pela regra de negócio e a função ModelDef é onde o model é definido. A função ModelDef deve sempre retornar o objeto do Model.

O Model é um objeto, logo para cria-lo é necessário instanciarmos uma classe, o framework do AdvPL disponibiliza duas classes para o model, a classe FWFormModel e a classe MPFormModel.

A FWFormModel é pouco usada, ela deve ser utilizada quando sua aplicação independe totalmente de dicionário de dados e qualquer outro suporte do nível do Protheus.

A MPFormModel realiza o tratamento da função Help, cria variáveis de memória e disponibiliza funções para persistência em campos existentes no dicionário de dados, ela é utilizada em todas as aplicações do Protheus.

Para criar o model, vamos instanciar a classe MPFormModel, ela possui 5 parâmetros:

  1. cIDModel : ID do Modelo (Obrigatório). O MVC trabalha com Identificadores, todo componente de Model ou View tem um ID.
  2. bPreValidacao: Bloco de código de pré validação do modelo (Opcional)
  3. bPosValidacao: Bloco de código de pós validação do modelo (Opcional)
  4. bCommit: Bloco de persistência dos dados, é preenchido automaticamente, devendo ser preenchido somente quando se deseja alterar a persistência dos dados
  5. bCancel: Bloco de persistência dos dados, é preenchido automaticamente, devendo ser preenchido somente quando se deseja alterar a persistência dos dado

Nesse momento vamos usar apenas o primeiro parâmetro, os outros 4 ficam pra um outro artigo.

Static Function ModelDef()
Local oModel

      oModel := MPFormModel():New("MD_ALUNO")

Return oModel

 

O ID do modelo NUNCA pode ser igual ao nome da sua função principal caso a sua função seja uma User Function, pois esse ID é usado para criar os pontos de entrada da aplicação. Se a sua aplicação e seu ID tem o mesmo nome, não vai ser possível criar a User Function para os pontos de entrada.

Passo 05: View

A View é a camada responsável pela interface gráfica e interação com o seu usuário. A View também é um objeto, que deve ser definido dentro da função ViewDef e por sua vez a função deve retornar o objeto de View.

A View é uma classe e para criar o objeto devemos instanciar a classe FwFormView, o método New não recebe nenhum parâmetro.

Static Function ViewDef()
Local oView

      oView := FWFormView():New() 

Return oView

 

Associando a View com um Model

A View é somente a “casca” da aplicação, ela precisa de dados para exibir na tela, esses dados estão dentro do Model, por esse motivo uma View sempre precisa ser associada a um modelo.

Essa associação ocorre através do método SetModel, que recebe como parâmetro o objeto do Model. O objeto de model pode ser criado de duas formas:

  • Se o Model que você vai usar está definido no mesmo fonte que a sua View, simplesmente chame a função ModelDef(), que sempre retorna o objeto de Model;
  • Entretanto, se o Model que você vai usar está definido em outro fonte, use a função FWLoadModel para carregar o modelo desejado.

 

Static Function ViewDef()
Local oView
Local oModel := ModelDef()

      oView := FWFormView():New() 
      oView:SetModel(oModel)

Return oView

 

Passo 06: Criando um FormField no Model

O MVC possui vários componentes, onde cada um tem a sua responsabilidade. O Field é um componente que tem como objetivo permitir a manipulação de um registro de dado, quando criado no Model e permitir a exibição de um registro ao usuário, quando criado na View.

Não gosto de comparações, mas se ajudar, ele funciona como a Enchoice.

Criando uma Estrutura de Dados básica

O MVC trabalha com Estrutura de Dados, um componente do tipo Field, precisa de uma estrutura de dados para definir qual tabela e campos serão manipulados pelo componente. A estrutura fornece:

  • Campos
  • Gatilhos
  • Validações
  • E outros dados importantes

Como já foi dito, o MVC não depende de Dicionário de Dados, todavia é possível criar uma Estrutura de Dados baseada em um dicionário. Sempre que você vai criar uma aplicação para o Protheus é interessante utilizar essa funcionalidade, pois dessa forma você não precisa criar a estrutura “do zero”, você realiza o carregamento e se preciso altera a estrutura para atender a sua necessidade.

Para criar uma Estrutura baseada em um Dicionário é necessário usar a função FWFormStruct, ela retorna um objeto de estrutura (FWFormModelStruct) baseado em dois parâmetros que recebe:

  1. nTipo: Indica o tipo de estrutura que você quer criar, use 1 quando é uma estrutura para o Field de um Model e 2 quando é uma estrutura para o Field de uma View
  2. cAlias: Indica o nome da tabela do Dicionário de Dados que será usado como referência para a estrutura
 oStrZB2 := FWFormStruct(1, “ZB2”)

O objeto oStrZB2 conterá uma tabela com os dados, campos, gatilhos e índices da tabela ZB2 do Dicionário de Dados do Protheus.

Criando o componente Field

O componente existe na View e no Model, primeiramente criaremos no model e logo depois na View.

Agora que já sabemos como criar uma estrutura de dados para o Field, vamos criar o componente em si, para isso é necessário usar o método AddFields do componente Model, esse método recebe vários parâmetros, mas agora vamos usar apenas dois, o primeiro e o terceiro parâmetro:

  1. ID do componente Field
  2. ID do Owner, não vamos usar agora
  3. Objeto de estrutura de dados para o Field
Static Function ModelDef()
Local oModel
Local oStruZB2 := FWFormStruct(1,"ZB2")

 oModel := MPFormModel():New("MD_ALUNO") 
 oModel:addFields('MASTERZB2',,oStruZB2)
 
Return oModel

 

Passo 07: Criando um FormField no View

Na View o Field é representado por um formulário, esse formulário mostra na tela os campos presentes na estrutura de dados do Field e o componente permite que o usuário visualize e edite/inclua/visualize os dados do registro.

Criando a estrutura de dados básica

A Estrutura de Dados da View descreve os campos que serão exibidos na tela para o usuário, por esse motivo a estrutura contém informações de interface gráfica, como por exemplo:

  • Picture
  • Consulta Padrão
  • Pastas
  • Grupos

Assim como no Model podemos criar uma estrutura baseada em um Dicionário de Dados, na View isso também é possível, basta usar a função FWFormStruct passando no primeiro parâmetro o tipo 2.

oStrZB2 := FWFormStruct(2, “ZB2”)

A função FWFormStruct retornará um objeto do tipo FWFormViewStruct.

Criando o Field

Já sabemos como criar a estrutura de dados para o formulário, agora podemos criar o componente na View. A View precisa de um Model, já que toda a definição da regra da aplicação e também os dados ficam nele.

Cada formulário da View precisa estar relacionado com um componente do Model e os componentes precisam ser do mesmo tipo.  Logo se o Model tem um Field com a estrutura da tabela Alunos (ZB2), então a View que vai usar esse Model também precisa de um Field com a estrutura da mesma tabela ZB2 e precisa relacionar o Field da View com o Field do Model, esse relacionamento se dá através dos IDs dos componentes.

Para criar o Field usamos um método da View chamado AddField, que recebe alguns parâmetros, mas agora veremos os 3 primeiros:

  1. IDView: Representa a identificação que você vai dar para o seu formulário
  2. oStruct: Objeto de Estrutura de Dados do tipo FWFormViewStruct
  3. IDModel: ID do Field que está no Model
oView:AddField(cIDView,oStruct,cIDModel)

Recomendo que seja usado IDs diferentes para o componente na View e no Model, pois dessa forma é mais fácil identificar os componentes, exemplo:

oView:AddField(“FORM_ALUNO”,oStruct,”MASTERZB2”)

Nesse caso de cima, sabemos que o identificador “FORM_ALUNO” é referente ao formulário Field da View e o identificador “MASTERZB2” faz referência ao componente Field do Model.

 

Box: O componente que vai dividir a sua tela em partes

A View possui alguns componentes de formulários (como o Field que estamos vendo), mas esses componentes precisam ser alocados em painéis dentro da View, apenas cria-los não faz com que sejam mostrados na tela.

Até agora só criamos um field, mas imagine uma View onde você tenha 10 formulários, você precisa criar painéis para dividir a tela e alocar esses formulários. Esses painéis na View são representados por dois componentes, o HorizontalBox e o VerticalBox.

O componente HorizontalBox cria painéis na horizontal e o VerticalBox cria painéis na vertical, os dois componentes trabalham com percentual, você define um ID para cada painel e qual o percentual da tela que ele deve ocupar.

Logo, se você precisa exibir apenas um formulário, você precisa criar um box de 100% da tela. Se você precisa exibir dois formulários, então você cria dois boxes e a soma do percentual dos dois tem que ser 100 e assim por diante, a soma dos boxes de um mesmo tipo deve ser sempre igual a 100 por cento.

O método CreateHorizontalBox recebe vários parâmetros, mas vamos nos atentar para os dois primeiros:

  1. cIDBox : Identificação do painel
  2. nPerc: Percentual da tela que o painel deve ocupar
oModel:CreateHorizontalBox(cID,nPerc)

No momento nós já criamos o formulário e o box, agora vamos alocar o formulário no box que criamos, isso é feito através do método SetOwnerView, que recebe dois parâmetros:

  1. ID do formulário
  2. ID do box
oView:SetOwnerView(cIDFORM,cIDBOX)

 

Se seguiu tudo que eu falei, a sua ViewDef deve ficar assim:

Static Function ViewDef()
Local oModel := ModelDef()
Local oView
Local oStrZB2:= FWFormStruct(2, 'ZB2')
 
 oView := FWFormView():New()
 oView:SetModel(oModel)
 oView:AddField('FORM_ALUNO' , oStrZB2,'MASTERZB2' ) 
 oView:CreateHorizontalBox( 'BOX_FORM_ALUNO', 100)
 oView:SetOwnerView('FORM_ALUNO','BOX_FORM_ALUNO')
 
Return oView

Passo 08: Fim!

Se seguiu tudo que falei, seu Hello Word deve te ficado assim:

#Include 'Protheus.ch'
#Include 'FWMVCDEF.ch'

User Function CMVC_01()
Local oBrowse

 oBrowse := FWMBrowse():New()
 oBrowse:SetAlias('ZB2')
 oBrowse:SetDescription('Cadastro de Alunos')
 oBrowse:Activate()
Return

Static Function MenuDef()
Local aRotina := {}

aAdd( aRotina, { 'Visualizar', 'VIEWDEF.CMVC_01', 0, 2, 0, NIL } ) 
aAdd( aRotina, { 'Incluir' , 'VIEWDEF.CMVC_01', 0, 3, 0, NIL } )
aAdd( aRotina, { 'Alterar' , 'VIEWDEF.CMVC_01', 0, 4, 0, NIL } )
aAdd( aRotina, { 'Excluir' , 'VIEWDEF.CMVC_01', 0, 5, 0, NIL } )
aAdd( aRotina, { 'Imprimir' , 'VIEWDEF.CMVC_01', 0, 8, 0, NIL } )
aAdd( aRotina, { 'Copiar' , 'VIEWDEF.CMVC_01', 0, 9, 0, NIL } ) 

Return aRotina

Static Function ModelDef()
Local oModel
Local oStruZB2 := FWFormStruct(1,"ZB2")

 oModel := MPFormModel():New("MD_ALUNO") 
 oModel:addFields('MASTERZB2',,oStruZB2)
 
Return oModel

Static Function ViewDef()
Local oModel := ModelDef()
Local oView
Local oStrZB2:= FWFormStruct(2, 'ZB2')
 
 oView := FWFormView():New()
 oView:SetModel(oModel)
 oView:AddField('FORM_ALUNO' , oStrZB2,'MASTERZB2' ) 
 oView:CreateHorizontalBox( 'BOX_FORM_ALUNO', 100)
 oView:SetOwnerView('FORM_ALUNO','BOX_FORM_ALUNO')
 
Return oView

 

Compile o fonte, coloque a função no menu e execute sua primeira aplicação em MVC.

Estrutura básica de um fonte MVC

Já sabemos que o MVC é uma arquitetura e cada linguagem implementa ela da melhor forma possível, vou mostrar nesse post quais os elementos essenciais para um programa escrito usando MVC.

Diferente de outras linguagens, no AdvPL o desenvolvedor manipula apenas os componentes de View e Model, deixando o Controller na responsabilidade do framework.

Escrevendo sua aplicação em MVC as funcionalidades abaixo serão nativas para seu programa:

  • Pontos de Entrada;
  • WebService;
  • Independência de dicionário de dados;
  • Possibilidade de usar para rotina automática.

Quando falamos de MVC penso em 3 componentes que você precisa compreender: Model, View e Estrutura de Dados.

Model

O Model é o componente responsável pela regra de negócio. Validações? Definição de como os componentes se relacionam? Persistência dos dados? Tudo isso é responsabilidade do Model.

No AdvPL o componente model é sempre definido em uma função estática chamada ModelDef, “Def” é justamente a abreviatura de “Definição”, logo “ModelDef” significa definição do modelo de dados.

Estou frisando a palavra definição porque uma vez que o Model está definido você pode carregar o model da aplicação em qualquer momento e realizar a operação que desejar, como por exemplo uma inclusão de registro.

O Model trabalha sozinho, ele não precisa de uma interface gráfica. Podemos usar o model em webservice, em rotina automática e até usar o model da aplicação X para ser a base do model da aplicação Y.

View

A View é a responsável pela interface gráfica e comunicação com o usuário. Quer fazer um gráfico? Precisa criar pastas para dividir a tela? Quer fazer uma validação para impedir que a tela abra? Quer colocar uma ação para quando a tela fechar? Tudo isso é configurado na View, toda ação que envolver interface gráfica fica na View.

No AdvPL o componente View é sempre definido em uma função chamada ViewDef, novamente “Def” é justamente a abreviatura de “Definição”, logo “ViewDef” significa definição da interface gráfica. A View não funciona sozinha, ela é apenas a “casca” de apresentação dos dados para o usuário, os dados reais estão no componente Model, logo para uma View existir ela obrigatoriamente precisa estar associada a um Model.

Estrutura de Dados

O MVC é independente de dicionário de dados do Protheus, dessa forma ele pode funcionar com diversas aplicações escritas em AdvPL, para isso se tornar possível foi criado o componente de Estrutura de Dados.

Estrutura de Dados é um componente que representa uma tabela, ele possui campos, validações de campos, tabela, chave primária, pastas, inicializador padrão, entro outros. Quer dizer então que a Estrutura de Dados é igual ao Dicionário de Dados? Não! Ela tem a mesma estrutura, todavia os dados para compor ela podem vir de um dicionário de dados ou podem vir de um tabela temporária, ou de qualquer outro lugar que o desenvolvedor deseje.

A Estrutura de Dados é obrigatória para o MVC, cabe ao desenvolvedor decidir se vai usar como base o dicionário de dados ou não. Além disso, a Estrutura de Dados é dividida em Model e View. A Estrutura do Model possui todas as características de campo que são relacionadas a regra de negócio (como validação, inicializador padrão, etc) e a Estrutura da View possui as características relacionadas a interface gráfica (como consulta padrão, pasta, agrupamento)

Importante!

As funções ModelDef, ViewDef e MenuDef precisam obrigatoriamente ser estáticas e devem ser únicas dentro de um fonte, pois o Controller procura por elas para criar a aplicação. Caso as funções sejam de outro tipo o seu programa não funcionará corretamente.

Ok, mas e o browse?

Porque eu não falei sobre browse já que estamos falando do essencial para uma aplicação MVC? O componente browse é genérico, gosto de descreve-lo como  “Um componente gráfico que exibe registros na tela e possibilita ações para o usuário, podendo os dados serem baseados em um array, tabela ou arquivo temporário”.

O browse não faz parte do MVC, ele é um componente usado com AxCadastro, Modelo 1, Modelo 3, programas que não tem por objetivo realizar um CRUD e também é usado pelo MVC.

Como a maioria dos programas escritos em MVC tem por finalidade um CRUD, encontramos o browse na maioria dos fontes, mas que fique claro, ele não é obrigatório. Você é totalmente livre pra escrever uma aplicação MVC sem browse, se isso for aderente ao seu objetivo.

Então.. o menu também não é essencial né?

O Menu não é obrigatório para uma aplicação, mas se você criar um browse, ele passa a ser. No menu você vai definir quais ações estarão disponíveis para sua aplicação, por exemplo, pode definir que é possível incluir e alterar um registro, entre outros.

No AdvPL o Menu é definido pela função MenuDef, que deve ser estática e deve existir somente uma função MenuDef por fonte. A função MenuDef é usada pelo Browse para exibir ao usuário as ações disponíveis para a aplicação.

Em resumo o seu fonte MVC , na maioria das vezes, vai possuir a estrutura abaixo:

User Function SeuFonteMVC()
 //Criação do browse
Return

Static Function MenuDef()
 // Definição das ações para o browse
Return

Static Function ModelDef()
 // Criação do Model
 // Criação da estrutura de dados
Return

Static Function ViewDef()
 // Criação da View
 // Criação da estrutura de dados
Return

O que é o tal MVC?

O MVC é uma arquitetura de desenvolvimento que separa a responsabilidade do software em três partes distintas: Model, View e Controller.

O Model é composto pelas entidades e persistência de dados, ele é o coração da aplicação, é responsável todas as regras, validações e definições de como sua aplicação deve funcionar.

A View é responsável pela interface gráfica da aplicação, contem todas as janelas, botões e campos, o usuário sempre interage com a View.

O Controller faz a comunicação entre a View e o Model.

 

Vantagens do uso da Arquitetura

  1. Separa a lógica de negócios da interface gráfica;
  2. Possui re-usabilidade, pois um Model pode ser usado para varias Views;
  3. Reduz o esforço na manutenção do software, pois as alterações são efetuadas separadamente não afetando as outras camadas do sistema

 

O MVC possui um framework para ser utilizado no AdvPL, nos próximos posts veremos como criar uma aplicação em MVC.