sábado, 7 de outubro de 2017

Bootstrap Navbar

Nosso artigo Menu Recursivo foi o que trouxe a maior repercussão no Genexando ao longo dos anos, pois foram mais de 12 mil visualizações desde sua concepção em 2010, e ainda é procurado. Casos de sucesso, mais que comemorados, devem nos servir para apontar o interesse da nossa pequena comunidade, sendo assim, vamos revisitar o conceito do menu, agora com mais recursos.

O fato é que desde 2010, algumas coisas mudaram no mundo Genexus. Olhando nosso artigo, já ancião, chegamos à conclusão que ele já não se encaixa mais na nova geração de aplicações. O User Control JsCookMenu com certeza foi um sucesso nas nossas aplicações, agradecimentos ao Daniel Coellar, mas hoje, com o advento da responsividade, Bootstrap e outros, ficou no passado.

A pergunta é, como montar um menu para nossa aplicação? Creio que a pergunta já indicaria um caminho, pois, atualmente as aplicações se suportam sobre um tipo distinto de menu: Navbar, e neste sentido, temos um pequeno problema, pois Genexus não oferece um User Control que faça isso nativamente.


Este controle, ao ser apresentado em uma MasterPage, por exemplo, permitiria acessar às interfaces do sistema, por meio de uma barra superiora, visualmente bem formatada e visivel.

Bootstrap

Um caminho para se chegar ao Navbar é utilizar as classes nativas do Bootstrap, que disponibiliza um componente muito simples para se construir a barra (mais informações aqui), baseado em tags e classes do framework. É um mecanismo muito simples, porém envolve HTML e CSS.

Nem todos controles Bootstrap poderemos construir com a estratégia simplificada que estaremos apresentando neste artigo, porém, Navbar cabe perfeitamente, porque o que deveremos criar são textos com links, não havendo a necessidade de retornar alguma coisa ao Genexus ou mesmo controlar eventos.

A implementação de User Controls sempre será a melhor opção, e já oferecemos nossa contribuição à comunidade, pois os argumentos em seu benefício seriam a reutilização em projetos com facilidade, a possibilidade de comunicação e retorno de eventos. E o lado negativo dos controles é, em meu ponto de vista, a rigidez visual ou operacional implementada no código, que é muito difícil de ajustar. O fato é que para criar um User Control, precisaremos estudar um pouco, umas 500 páginas, e depois que se aprende poderemos dizer que é simples, mas para um contato inicial soa bastante complexa a quantidade de definições e detalhes.

Temos, por outro lado a possibilidade de implementar código HTML diretamente na nossa interface, e para isso somente precisamos de um único controle Textblock, definido como HTML. Poderíamos buscar outras metodologias mais ‘bonitinhas’, mas vamos simplificar um pouco aqui.

Desta forma, vamos, neste exemplo implementar um Navbar com Bootstrap e Textblock, e para incluir uma cerejinha no topo, GAM Menu.

GAM Menu

Outro recurso que nasceu foi o GAM Menu, um mecanismo que permite criar o menu do sistema com as permissões do próprio GAM. Isto foi implementado no Genexus 15. Aqui alguns links que tratam do assunto, inclusive um que liga o GAM Menu com o JsCookMenu (muito interessante!):
No GAM Menu deveremos criar uma lista de itens, associando a cada um deles o link correspondente à uma permissão do GAM. Isto se faz no GAMHome - Applications - Menu - Add

A permissão é uma referência a uma interface na Kb. Por exemplo build_Execute é a permissão ao objeto build (WebPanel), e cada item do Menu deverá apontar para uma permissão.


Não vou gastar muita energia explicando tudo isso, porque GAM é um dos temas mais documentados de Genexus, basta acessar ao Wiki e terá muita documentação à sua disposição.

O que deveremos fazer, neste exemplo, é acessar os itens definidos no nosso menu GAM, e para isso necessitaremos de algumas coisas, a primeira é o GUID da nossa aplicação, e também o GUID do nosso menu. 

Ambos os valores são gerados quando criamos um novo Menu e quando aplicamos o GAM à nossa Kb. Poderemos obter o ApplicationId via programação, mas também, se você não tiver muita paciência para programar o GAM, pegar o valor diretamente nas Preferences da Kb e no GAMHome, e fixar no código.


Via programação, o &ApplicationGUID pode ser obtido pela sessão do usuário conectado na aplicação e o &MenuGUID, a partir da GAMApplication identificada. O problema é se houver mais que um menu associado à aplicação, então teremos que alterar o item(1), para se apontar para o menu correto.

GAMDefinitions:
Rules:
parm(&ApplicationGUID, &MenuGUID);

Source:
&GAMSession = GAMSession.Get(&GAMErrors)
for &GAMApplication in  &GAMRepository.GetApplications(&GAMApplicationFilter, &GAMErrors)
 if &GAMSession.ApplicationId = &GAMApplication.Id
  exit
 endif
endfor
&ApplicationGUID = &GAMApplication.GUID

&GAMApplicationMenus = &GAMApplication.GetMenus(&GAMApplicationMenuFilter, &GAMErrors)
if &GAMApplicationMenus.Count>0
  &MenuGUID = &GAMApplicationMenus.Item(1).GUID.ToString()
endif

A seguir as variáveis utilizadas no programa.


Programamos esta operação em uma procedure que chamamos de GAMDefinitions. E esta é chamada antes de se obter o menu, com outra operação simples, veja a seguir.

GAMDefinitions(&ApplicationGUID, &MenuGUID)
&GAMMenuOptionList = GAMRepository.GetApplicationMenu(&ApplicationGUID, &MenuGUID, &GAMErrors)

Onde &GAMMenuOptionList é a lista de itens do menu, (do tipo GAMMenuOptionList). Neste objeto temos uma opção Nodes com a definição de todos os itens do menu.

Criando o NavBar

A criação do NavBar é um pouco estranha, porque teremos que definir uma estrutura HTML e incluir nesta nossos itens de Menu. Programei uma procedure chamada Navbar, com o seguinte conteúdo

Navbar:
Rules:
parm(&Menu);

Source:
//navbar
&Menu  = '<div class="container-fluid">'
&Menu += ' <nav class="navbar navbar-default navbar-fixed-top " role="navigation">'

// collapse menu, 
&Menu += '  <div class="navbar-header navbar-left pull-left" style="">'
&Menu += '   <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar">'
&Menu += '    <span class="icon-bar" ></span>'
&Menu += '    <span class="icon-bar" ></span>'
&Menu += '    <span class="icon-bar" ><span>'
&Menu += '   </button>'
&Menu += '   <a class="navbar-brand" href="#">logo</a>'
&Menu += '   <div class="navbar-brand">sistema</div>'
&Menu += '  </div>'

// barra do menu
&Menu += '  <div id="navbar" class="navbar-collapse collapse">'
do 'gammenu'
&Menu += '  </div>'

// encerramento
&Menu += ' </nav>'
&Menu += '</div>'

sub 'gammenu'
 GAMDefinitions(&ApplicationGUID, &MenuGUID)
 &GAMMenuOptionList = GAMRepository.GetApplicationMenu(&ApplicationGUID, &MenuGUID, &GAMErrors)
  NavbarItens(&GAMMenuOptionList, &Menu)
endsub

Em negrito destacamos a parte dinâmica do menu, em si, na qual estaremos agregando os itens obtidos do GAM Menu.

As variáveis da procedure são apresentas a seguir.

Os diversos itens do Navbar são alcançados por uma outra procedure chamada NavBarItens que deve ser recursiva. Observe que antes do endfor, colocamos a chamada recursiva para pegar os itens associados ao elemento corrente.

NavbarItens:
Rules:
parm(&GAMMenuOptionList, &Menu);

Source:
for &GAMMenuOption in &GAMMenuOptionList.Nodes
 &link = &GAMMenuOption.Link.ReplaceRegEx(' ','')
 &Menu += '   <ul class="nav navbar-nav">'
 &Menu += '   <li class="nav navbar-item"><a href="'+&link+'">'+&GAMMenuOption.Name+'</a></li>'
 &Menu += '   </ul>'
 MenuSubGAM(&GAMMenuOption, &Menu)
endfor

A procedure recebe a variável &Menu (Longvarchar) e a passa para a MenuSubGAM, e cada chamada irá complementar o Navbar com o item no menu. Abaixo as variáveis programadas.


MasterPage

Para utilizar essa maluquice toda, precisaremos apenas de um Textblock, marcado como HTML, na MasterPage da nossa Kb.

Event Start
   textblock1.caption = Navbar()
Endevent  

O resultado será a apresentação do primeiro Menu definido no GAMHome com todas as suas opções vinculadas no NavBar.

Restrições

  • Neste primeiro exemplo não estamos tratando os submenus, ou seja, Dropdown que venham a ser vinculados a certo item de menu. E não fizemos isto porque não fica muito claro no GAM a identificação dos níveis de menus. Aguardemos as cenas dos próximos capítulos.
  • Este exemplo funciona naturalmente no Genexus 15, se for aplicado ao Genexus Ev3 a operação somente funcionará em modo Smooth.

XPZ

Antes de importar o xpz é necessário aplicar o GAM na sua Kb e em seguida definir a partir do GAMHome um menu para a sua aplicação. Se você não aplicar o GAM e também não definir o menu, a importação do mesmo causará erro.

O xpz com os exemplos apresentados encontra-se aqui.

Após a importação é necessário que altere a master page do seu sistema para o objeto NavbarMasterPage.





sábado, 30 de setembro de 2017

Parm & Json

Tem muita gente que não é chegada em queijo. Não podemos negar que é um super alimento, e devido aos diversos tipos existentes no mundo, podemos dizer que sempre existe um tipo que combina muito bem com qualquer coisa.

Neste artigo vamos explorar um pouco dos nossos dotes culinários (inexistente, no meu caso) e desenvolver uma nova receita com dois ingredientes: Parm para passagem e recebimento de parâmetros, e JSON um mecanismo de registro de informação e também para transferência de dados.

Nosso problema surge quando desejamos transmitir um JSON para um serviço REST, pois quando combinamos: SDT, PARM, REST teremos um pouco de dificuldade para se atingir o ponto ideal do prato.

Veremos que da combinação desses dois ingredientes simplesmente temos “o nascer de uma nova culinária internacional”, rs.

Parm

O primeiro ingrediente que iremos incluir na nossa receita é a regra parm, que já utilizamos pelo menos um milhão de vezes ao longo do tempo em que programamos o Genexus. Eu mesmo nunca parei para pensar como a coisa funcionava, a não ser que tínhamos que enviar o mesmo número e tipo de parâmetros entre os objetos para que ocorresse a correspondência entre eles.

Porém nesta altura do campeonato um novo cenário se consolidou, e com ele a necessidade de se pensar no parm de maneira totalmente diferente do que havíamos até então aprendido, e que havia sido deixado no canto, esquecido por aí, como aquela latinha de atum que está na despensa há mais ou menos três anos.

JSON

Já discutimos JSON aqui no Genexando em muitas ocasiões, não vamos tomar seu precioso tempo para aprofundar neste tema, pois nosso foco, aqui é apenas utilizá-lo para transferir algum tipo de informação estruturada entre sistemas.

{"nome":"Joao da Silva", "endereco":"Rua Um, 2", "cidade":"São Paulo"}

Para alcançar o JSON precisaremos em Genexus criar um SDT, e em seguida criar uma variável do mesmo tipo.


Uma vez carregado o conteúdo, poderemos formatar o conteúdo como JSON, mediante o ToJson().

&sdt.nome="Joao da Silva"
&sdt.endereco="Rua Um, 2"
&sdt.cidade="São Paulo"
&json = &sdt.tojson()

O resultado desta operação, como sabemos, é um texto formatado no padrão JSON, ou seja, saímos do conceito da variável SDT e passamos para texto simples.

A combinação que não funciona

Como na química e na culinária, temos certos elementos que não combinam, temos no Parm e no SDT uma difícil tarefa de encaixá-los.

Se tentarmos transmitir uma variável SDT como parâmetro em um objeto Web (WebPanel ou Transação) teremos um erro, informando que não é possível. E por outro lado, se fizermos a comunicação entre Procedures, este problema não aparecerá.

Esta restrição não é do Genexus, a própria Web na verdade não aceita que sejam passadas variáveis SDT, Vetores ou Coleções nas chamadas HTTP/S, quer seja por conta das dimensões limites do protocolo. Não é possível passagem por referência na Web, e a saída que todos propõem é a serialização da informação no formato JSON (ou XML), ou seja, transformar o conteúdo em texto.

Vamos ao Problema

Feitas as considerações iniciais, vamos à questão fundamental: Como transferir um JSON em chamadas REST, e recebendo-os como parâmetros, considerando, que teremos vários cenários distintos de linguagens e soluções que poderão ser utilizadas para consumir nosso serviço.

Primeiramente teremos que ter uma Procedure ou Data Provider configurado como REST, o que é muito simples fazer, pois teremos apenas que definir duas propriedades: Expose as Web Service = true, e REST Protocol = true.



Nosso objetivo é receber informações diretamente em uma variável SDT, o que vimos ser difícil de se implementar nas chamadas Web. Para isso será necessária uma regra Parm com nossa variável SDT. Neste caso &sdt é uma variável criada com um tipo SDT.


Vimos que esta operação é impossível de se realizar na Web, mas quando falamos REST estaremos submetendo informações para uma procedure, mas claro, de um sistema externo para nossa procedure.

Como fazer isso? Para se chamar uma procedure REST teremos que ter algum tipo de cliente, que pode ser, por exemplo, o Postman. Ao se programar uma chamada REST neste programa o que necessitamos é definir o Método (POST), a URL (http://localhost/teste/rest/wsrestexemplo), para a nossa procedure wsrestexemplo em construção (observe o /rest/ na URL). O Header indicando a passagem do tipo Content-Type: application/json e no Body a informação a ser postada no serviço.


No Body é que está o segredinho da nossa receita, pois deveremos combinar o nosso JSON original, com os dados que queremos transmitir:

{"nome":"douglas", "endereco":"Rua 1, 2", "cidade":"São Paulo"}

Com informações sobre a regra Parm do nosso serviço. Isso quer dizer que se programarmos uma regra Parm(in:&sdt) deveremos produzir outro JSON para representar a nossa variável de entrada &sdt. Estranho isso não?

{"sdt": ... }

A combinação das duas coisas nos leva a solução:

{"sdt": {"nome":"douglas", "endereco":"Rua 1, 2", "cidade":"São Paulo"} }

Ou seja, nosso cliente deverá reproduzir o nome da variável de entrada da Parm.

Recebendo parâmetros

E assim como enviamos, podemos receber também parâmetros, na própria regra Parm, como por exemplo uma variável texto &resposta.


O interessante é que o mesmo comportamento de Json de envio também aparece na mensagem de retorno, e se programarmos &resposta = 'retorno da mensagem '+&sdt.nome, teremos como retorno:

Ou seja, um JSON de retorno, que inicia com o nome da variável &retorno.


Em Genexus

Finalmente em Genexus, para se programar o cliente com o httpClient, deveremos adicionar no body o mesmo formato da chamada.

&service = 'wsrestexemplo'
&httpclient.Host = 'localhost/'
&httpclient.Port = 80
&httpclient.BaseUrl = '/teste/rest/'
&httpclient.AddHeader('content-type','application/json')
&body = '{"sdt":'+ &sdt.ToJson() +'}'
&httpclient.AddString(&body)
&httpclient.Execute('POST', &service)
if &httpclient.ErrCode<>200
   &retorno.FromJson(&httpclient.ToString())
endif


Voilá, nous avons notre chef-d'œuvre avec du parmesan

Ou em português claro, enfim, temos nossa obra prima com parmesão, ou melhor, com parm & json.

Bom, antes de fechar a conta, preciso confessar que essa receita, na verdade eu copiei de dois outros chefs famosos, o Bruno Camargo e o Gonzalo Gallotti, que já estavam curiosos com a culinária envolvendo parmesão. https://es.stackoverflow.com/questions/67187/webservice-rest-con-sdt-por-par%C3%A1metro


quinta-feira, 9 de março de 2017

Debug no Genexus

Debugar programas no Genexus é um desafio. A ferramenta sempre acaba enroscando em algum detalhe e quase sempre não executa a ação corretamente. É uma enorme frustração ao tentar debugar um programa, e no final das contas, muitos, inclusive eu, recorrem aos msg('passei aqui'), para tentar identificar o problema.

Depois de sofrer bastante para identificar o problema, a conclusão é que Genexus não tem muita culpa nessa história, pois a própria configuração do computador leva a esta chata situação.

A seguir os passos necessários para que o Debug execute em seu Genexus, sem maiores problemas. Ao que parece quem trava o Debug é o Firewall, e consegui resolver a questão com os seguintes passos.

  1. Desligar o Firewall, ou adicionar a exceção que bloqueia o programa Debug.
  2. Remover proteção UAC do Windows.
  3. Desligar o Genexus
  4. Abrir Genexus em modo administrador
  5. Definir modo DEBUG para a execução
  6. Abrir o programa desejado e incluir os breakpoints
  7. Executar o programa 
O passo (2) é meio incógnita, mas tive que fazer isso para que o Debug de fato operasse.

Fiz o teste no gerador .NET e Java e em ambas as situações o DEBUG abriu.

sábado, 11 de fevereiro de 2017

REST e Data Provider

Fechando o ciclo dos webservices do tipo REST falta ainda discutir um pouco a respeito da obtenção de uma lista de registros de um certo sistema, ou kb, para que possamos ter informação em nosso sistema cliente.

Em nossos artigos anteriores tratamos a respeito da metodologia para se desenvolver o CRUD utilizando uma transação: REST & SOAP, porém este modelo nos permite atuar sobre determinado registro apenas. Discutimos inclusive a importância de se estabelecer um mecanismo de segurança, que optamos pelo GAM pela praticidade e recursos. O último tratou de uma ferramenta de apoio para que possamos testar o serviço remotamente, REST client. O que falta agora é discutir como acessar informações no servidor, trazendo uma coleção de registros ao mesmo tempo.

Data Provider

Este recurso está passando por algumas transformações importantes, e desde o seu nascimento, a então Artech já dava as premissas de uma revolução no modelo de programação com seu descreva ao invés de programar.

Creio que a grande maioria dos genexandos já se depararam com este objeto, talvez apenas não avançaram para torná-lo um serviço REST, então vamos dedicar alguns parágrafos para explicá-lo. O Wiki Genexus traz muito boas informações sobre este tema.

1) Começamos montando um SDT 

Criar um SDT com a estrutura da informação desejada é o primeiro passo para exportarmos informação. E aqui vale inclusive arrastar a transação para o editor de SDT para que este inclua todos os campos desta no objeto que estamos construindo.

Seguindo a linha dos exemplos anteriores, vamos gerar uma lista de empresas a partir da transação Empresa. O SDT é o mesmo do exemplo em REST & SOAP


2) Criando o Data Provider

O próximo passo é arrastar o SDT para dentro do objeto Data Provider. Se encontrar alguma dificuldade para remover as atribuições padrões do Genexus temos aqui a solução Krueger.



A aplicação de filtros where é permitida, e neste caso teremos que enviar parâmetros para o serviço REST. Não esqueça de incluir uma regra Parm com os parâmetros

parm(&EmpresaId);

3) Definindo as propriedades do DP

Além de definir o Output como Collection=true, também será necessário expor o objeto como Serviço REST.



4) Montando o cliente

Uma segunda KB ou qualquer programa externo poderá consultar nosso DP REST, e claro, mantendo o mesmo esquema de proteção do GAM, o cliente deverá realizar primeiramente o login, para em seguida poder executar o DP. Uma chamada ao serviço /oauth, passando-se os parâmetros corretos, realiza a proposta. Como informamos no artigo anterior o client_id é obtido no GamHome.

sub 'login'
 &httpclient.Host = 'localhost/'
 &httpclient.Port = 80
 &httpclient.BaseUrl = '/Kb.NetEnvironment/oauth/'
 &addstring ='client_id=068868de058b4798bd3b90f696fa37f8&grant_type=password&scope=FullControl&username=admin&password=admin123'
 &httpclient.AddHeader("Content-Type", "application/x-www-form-urlencoded")
 &httpclient.AddString(&addstring)
 &httpclient.Execute('POST','access_token')
 &AccessTokenSDT.FromJson(&httpclient.ToString())
 &websession.set('TOKEN', &AccessTokenSDT.access_token)
EndSub

Para completar o processo de login e necessário obter o token, e para isto programamos o AccessTokenSDT que formata a respota do GAM. Se estivermos tratando de Genexus para Genexus, os objetos podem ser exportados e importados na Kb cliente, porém, para outros ambientes teremos que definir, na linguagem específica a respectiva estrutura.


Armazenamos o Token na sessão para poder acessar seu valor quando necessário.


&websession.set('TOKEN', &AccessTokenSDT.access_token)

A chamada ao Data Provider deve ser realizado passando-se o token.

&httpclient.AddHeader('Authorization','OAuth ' + &websession.get('TOKEN'))

Em seguida uma variável querystring é programada para informar o parâmetro a ser passado. Observe que o parametro deve ser declarado neste modelo, "?EmpresaId=" + trim(&EmpresaId.ToString()). O Data Provider é incluído no querystringempresadp.

&querystring='empresadp' + "?EmpresaId=" + trim(&EmpresaId.ToString())

O código completo da chamada é apresentado a seguir.

Event Enter
do 'login'

&httpclient.Host = 'localhost/'
&httpclient.Port = 80
&httpclient.BaseUrl = '/Kb.NetEnvironment/rest/'
&httpclient.AddHeader('content-type','application/json')
&httpclient.AddHeader('Authorization','OAuth ' + &websession.get('TOKEN'))
&querystring='empresadp' + "?EmpresaId=" + trim(&EmpresaId.ToString())
&httpclient.Execute('GET', &querystring)
&resposta = &httpclient.ToString()
Endevent

Para transformar a resposta em uma variável SDT é possível desde que definido. Fizemos isto no primeiro passo, ao criar o EmpresaSDT. Será necessário é transferir o objeto da Kb onde se encontra o Data Provider para a Kb cliente.


Uma variável do tipo EmpresaSDT deve ser criada como Collection=true. E, em seguida, um FromJson resolverá a parada.

&EmpresaSDT.FromJson(&httpclient.ToString())

O resultado disso? temos os registros recuperados da tabela Empresa, filtrados pela variável &EmpresaSDT. Desta forma podemos 'consultar' esta tabela em memória, na máquina cliente.

Fechando a conta

Temos muitos recursos interessantes que poderemos utilizar para solucionar nossos problemas de programação. O uso de webservices é importante porque toda lógica de proteção dos dados se encontra sob o domínio da Kb que fornece o serviço. Diferentemente de um acesso direto à tabela, por meio de um DataView, os webservices permitem um maior controle sobre o acesso e autorizações sobre as operações que cada sistema cliente terá.

Talvez não sirva para todas as situações, mas com certeza, para cenários de multiplos sistemas que necessitam trocar informações é uma excelente aposta.

Maiores informações:
Acesso aos exemplos, formato XPZ:
Gerador:
  • .Net a chamada ao serviço REST não precisa obedecer rigorosamente o nome do DP, podendo ser utilizado letras maiusculas ou minúsculas
  • Java exige que o nome do serviço obedeça o case das fontes.


REST Client

Quando trabalhamos com chamadas REST é importante utilizar uma ferramenta cliente para que possamos testar os parâmetros de conexão.  Se optarmos pelo Genexus para realizar estes testes teremos que passar por diversas compilações para termos certeza que o serviço responde adequadamente.

Recentemente fui apresentado pelo Mauricio, a uma extensão do Google muito interessante chamada postman, gratuita e muito interessante para realizarmos chamadas na Web, testar serviços, entender a comunicação entre o servidor e o cliente.


No chrome basta instalar a extensão, e em seguida a mesma poderá ser chamada pelo próprio Desktop do Windows.


Fica a dica ai!

sexta-feira, 10 de fevereiro de 2017

REST & SOAP

Temos ha algum tempo dois modelos de conexão a webservices implementados no Genexus a sua disposição para que inicie seus projetos: SOAP (Simple Object Access Protocol) e o REST (Representational State Transfer). O primeiro um protocolo de comunicação e o segundo um modelo de conexão entre sistemas. Esquisito? sim e muito, veja aqui as diferenças de implementação e uso.

Objetos Tipo Webservices

Primeiramente, Genexus permite que vários de seus objetos atuem como webservice, de forma que podemos implementar cenários bastante interessantes:
  • Transaction: permite fazer com que o CRUD (Create, Read, Update, and Delete) de uma Transação opere como webservice. Em outras palavras, podemos disponibilizar serviços que façam o Insert, Update e Delete de registros em tabelas a partir da porção Business Component da transação.
  • Procedures: poderíamos implementar regras de negócio mais especificas para realizar tanto a operação de manutenção das tabelas, como também para devolver informações que deveriam passar por algum tipo de processamento.
  • Data Provider: permite-nos criar um recurso que facilmente recupera informações das tabelas, podendo gerar 'views dinâmicas', que associados aos webservices podem alimentar sistemas externos famintos por informação.
Desta forma, a ferramenta suporta, digamos, com o padrão Genexus de fazer as coisas ficarem mais simples, uma ampla gama de possibilidades quando pensamos nos tais webservices.

REST & SOAP

Ja apresentamos alguns exemplos aqui no Genexando a respeito da aplicação e consumo de webservices SOAP, principalmente devido a sua simplicidade de construção e uso em Genexus. Pois para consumir basta utilizarmos uma ferramenta que aponta para o WSDL do serviço e que cria automaticamente um objeto externo que aponta para a execução da procedure remota, esta ferramenta é chamada WSDL IMPORT. Para construir, basta pegar uma procedure qualquer e marcá-la com as opções Main Program = true e Call Protocol = SOAP.

Os webservices do tipo SOAP são mais simples de utilizar porque, no Genexus, estão associados a um objeto externo. Podemos encarar a coisa mais ou menos assim, imagina que uma procedure está em uma Kb e foi pubicada como SOAP, em outra Kb, quando apontamos para este SOAP com o WSDL Import, o Genexus criará um objeto (External Object) com o mesmo nome desta procedure 'externa', permitindo que executemos esta procedure. Seria algo como, tornar uma procedure, que inicialmente encontra-se sob o escopo privado em uma Kb, como pública e acessível para outras Kbs ou mesmo sistemas externos, não Genexus.

Agora quando falamos de REST a coisa muda um pouquinho. Praticamente o que temos é estilo para chamar recursos na Web mediante a definição da operação na URI, mas deixemos as discussões quanto às diferenças para os filósofos da Internet, melhor focar na implementação do Genexus para ambos os recursos.

Genexus publica ambos os modelos para que possam ser acessados mediante chamadas HTTP ou HTTPS, SOAP vai se comunicar utlizando XML, e REST é mais flexível, permitindo que a transferência de informações ocorra tanto com XML, JSON, Texto, ou mesmo outro modelo qualquer.

Em ambos os cenários a questão da segurança é muito importante, porque se estamos transferindo algo valioso entre sistemas, pela Web, com certeza algum bisbilhoteiro vai tentar interceptar a comunicação. Desta forma se você não é adepto a certificados digitais SSL, esqueça este recurso, isto aqui não é para você. Bom, na verdade, as credenciais dos seus usuários neste momento também estão expostas, neste caso.

Programando REST

Para entender um pouco mais da programação webservice Genexus com SOAP, recomendo a leitura do artigo http://www.genexando.com/2009/07/web-services.html, em que já apresentamos um exemplo sobre este modelo.

Quanto ao REST necessitamos explorar um pouco deste tema aqui no Genexando, pois o modo de programação é bastante diferente.

1) Segurança é tudo!

Primeiramennte é necessário entender um pouco a respeito da segurança da chamada ao serviço, e no caso do Genexus o GAM sempre é uma boa opção para proteger tudo.

No GAM é necessário realizar um login na aplicação, controlado pelo protocolo OAuth implementado no recurso, e este cria um Token assim que se estabelece a conexão. Por meio deste Token será possível realizar as próximas conexões com o servidor sem que se tenha de reenviar o usuário e senha. Por isso a importância do protocolo SSL, pois a passagem dos parâmetros, principalmente neste momento deve estar completamente encriptada e protegida. GAM com http não vai adiantar nada!

Para procedermos é necessário fazer uso do tipo HttpClient do Genexus que permite configurar os parâmetros para a execução do processo, que deve acontecer pela chamada POST ao serviço /oauth/access_token. A seguir um exemplo de login no GAM.

Event 'login'
  &httpclient.Host = 'localhost/'
  &httpclient.Port = 80
  &httpclient.BaseUrl = '/Kb.NetEnvironment/oauth/'
  &addstring ='client_id=068868de058b4798bd3b90f696fa37f8&grant_type=password&scope=FullControl&username=admin&password=admin123'

  &httpclient.AddHeader("Content-Type", "application/x-www-form-urlencoded") 
  &httpclient.AddString(&addstring)
  &httpclient.Execute('POST','access_token')
 
  &json = &httpclient.ToString()
  &AccessTokenSDT.FromJson(&json)

  &websession.set('TOKEN', &AccessTokenSDT.access_token)
EndEvent

O valor de client_id informado deve ser obtido no GAM, na área Applications - Client Application Data.


O retorno da chamada é no formato JSON, desta forma é necessário converter o texto recebido em um SDT. No nosso exemplo, algo como:


Um exemplo de retorno da chamada deste serviço seria algo como:

{
 "access_token":"54f03097-e105-4bfd-9ff4-eba57KM6hSB39vFRlA7a4iAE",
 "scope":"FullControl",
 "refresh_token":"",
 "user_guid":"985ee5fe-8d5c-4008-8a0b-050beb6bddb0"
}

A estratégia do exemplo é armazenar o Token na websession, para em seguida obtê-lo quando necessário.


2) Inserindo registros

Considerando uma transação Empresa que foi definida como REST.


Poderemos realizar operação de inserção de registros por meio do serviço, que se encontrará disponibilizado na URL a seguir, observe que se deve incluir um /rest e o /Empresa é a transação, que não precisamos ascrescentar a extensão aspx (se for .Net)

localhost/Kb.NetEnvironment/rest/Empresa

A sequencia de ações para se chamar este serviço REST é a seguinte:
  1. Definir o caminho ao serviço
  2. Incluir no Header da mensagem o Token do GAM
  3. Incluir no corpo da mensagem o registro a  ser inserido
  4. Definir a chave primária do registro a ser inserido
  5. Executar o POST

A primeira ação é a operação de conexão ao servidor, que definimos em uma sub rotina chamada Host. Observe que é necessário passar o Token obtido no login anteriormente apresentado, incluíndo-o no cabeçalho da chamada.

Sub 'Host'
  &httpclient.Host = 'localhost/'
  &httpclient.Port = 80
  &httpclient.BaseUrl = '/ Kb.NetEnvironment/rest/'
  &httpclient.AddHeader('Authorization','OAuth ' + &websession.get('TOKEN'))
EndSub

Em seguida,  será necessário incluir o registro a ser inserido no corpo da mensagem, através do &httpclient.AddString(&EmpresaSDT.ToJson()). Desta forma, deveremos ter um objeto EmpresaSDT para definir a estrutura da informação.


Falaremos do gx_md5_hash logo abaixo.

Um detalhe importante na chamada é a passagem do Id do registro, no caso EmpresaId, que para o insert, deve ser zero. Ou seja, temos como enviar parâmetros para a regra Parm do objeto.

O Execute no método POST realiza a chamada ao serviço.

Event 'Insert'
  do 'Host'
  &httpclient.AddHeader('content-type','application/json')
  &httpclient.AddString(&EmpresaSDT.ToJson())
  &querystring='Empresa' + "/" + &EmpresaSDT.EmpresaId.ToString()
  &httpclient.Execute('POST', &querystring)
  &StatusCode=201
  do 'Message'
Endevent

Para tratar o resultado temos o código de retorno 201 sendo o StatusCode correto para a operaçao, ou seja, se acontecer o registro foi inserido corretamente.

Sub 'Message'
  if &httpclient.StatusCode=&StatusCode
    msg("Informação atualizada")
  else
    msg("Ocorreu um erro.." + &httpclient.ToString())
  endif
EndSub


2) Atualizando registros

A atualização é muito semelhante ao processo de inserção, com apenas um pequeno detalhe, será necessário obter primeiro o registro que se encontra na tabela. Isto é feito para que o Genexus realize o controle de concorrência no processo de atualização.

Sendo assim, a carga do registro deve ser feita por uma chamada GET ao serviço, a seguir um exemplo.

Event 'get'
  do 'Host'
  &httpclient.AddHeader('content-type','application/json')
  &querystring='Empresa' + "/" + &EmpresaSDT.EmpresaId.ToString()
  &httpclient.Execute('GET',&querystring)
  &EmpresaSDT.FromJson(&httpclient.ToString()) 
  &StatusCode=200
  do 'Message'
Endevent

O registro deve ser manipulado a partir de uma variável SDT, e em seguida sua atualização deve ser enviada ao servidor chamando-se um método PUT, conforme o modelo a seguir. Em todo processo será necessário informar a chave primária do registro.

Event 'update'
  if &EmpresaSDT.gx_md5_hash.IsEmpty()
    msg('Do a get first')
  else
    do 'Host'
    &json=&EmpresaSDT.ToJson()  
    &httpclient.AddHeader('content-type','application/json')
    &httpclient.AddString(&json) 
    &querystring='Empresa' + "/" + &EmpresaSDT.EmpresaId.ToString()
    &httpclient.Execute('PUT', &querystring)
    &StatusCode=200
    do 'Message'
 endif
EndEvent

O detalhe importante neste processo é a necessidade de se armazenar um código Hash gerado pelo Genexus para controlar a concorrência, obtido no GET e enviado para o servidor no PUT, em seguida. Este hash se altera a cada operação de consulta ao banco.


Este Hash é guardado no SDT na propriedade gx_md5_hash, toda comunicação que venha atualizar o mesmo deverá informar esse valor.

3) Apagando registros

Finalmente, para se apagar o registro precisaremos de uma simples chamada DELETE, conforme o exemplo apresentado a seguir.

Event 'delete'
 do 'Host'
 &querystring='Empresa' + "/" + &EmpresaSDT.EmpresaId.ToString()
 &httpclient.Execute('DELETE', &querystring)
 &StatusCode=200
 do 'Message'
Endevent

Resumindo

Podemos utilizar uma transação e implementar um mecanismo de operação remota dos registros publicando-a como REST.  O próprio Genexus utiliza esta estratégia para prover informações e funcionalidades para as aplicações SD.

O mecanismo torna o modelo de programação um pouco mais cheia de código, porém, oferece novos cenários de aplicação.

O que se deve levar em conta é a velha história do desempenho x controle, pois neste modelo teremos um forte controle sobre o compartilhamento da informação, por outro lado, teremos um novo cenário no qual não poderemos explorar muito o desempenho do acesso direto ao banco de dados. Para compensar, o modelo vai exigir um pouco mais de infra-estrutura para compensar a pequena ineficiência.

Particularmente, acho interessante.

Mais informação em:
Acesso aos exemplos, formato XPZ: