When the solution matters*

4D Internacional


| Clientes | Histórias de êxito | Buscador de soluções | Perfis |

Companhia Geral de Essências

Serviços Web Avançados
Por Tito Ciuro

No meu artigo anterior explicava a nível básico como começar a
trabalhar
com os Serviços Web. Neste artigo nos adentraremos de cheio no terreno avançado. Veremos quatro exemplos que nos permitirão enviar e receber estruturas complexas:
  • Serviço Web que facilita uma lista de estados nos
    E.E.U.U. em que se encontram os doutores.
  • Serviço Web que facilita uma lista de doutores
    localizado em uma zona.
  • Serviço Web que facilita uma lista de doutores
    localizado em uma zona (versão otimizada)
  • Serviço Web que codifica informação relacionada
    com um doutor. Esta informação está repartida em
    várias tabelas e consolida-se em um “Bag”
    (variável de tipo BLOB).
Para terminar, a seção “Obtendo valores de um Bag” mostrará como concordar aos valores associados com uma etiqueta

 

Agradecimentos

Este artigo, igual aos programas que escrevi em 4D, se baseiam em uma tecnologia chamada GenRoTools.

Sem a ajuda de Bags, a implementação de soluções via Serviços Web podem ser realmente complicadas, quero agradecer a Jeff Edwards e a Giovanni Porcari por ter produzido esta ferramenta de grande qualidade. A licença é gratuita, tanto no nível de desenvolvimento como de instalação. A página de descarga será pública proximamente, mais, no entanto, Jeff e Giovanni me permitiram distribuir GenRoTools junto com os exemplos incluídos neste artigo.

 

Pré-requisitos

Para poder tirar partido ao artigo, o leitor deveria ler meu artigo anterior.

 

Primeiro exemplo: Serviço Web que facilita uma lista de estados nos E.E.U.U. em que se encontram os doutores

A intenção deste exemplo é a de criar um Serviço Web que retorne um Bag com os estados americanos codificados da seguinte maneira:

 

O método do servidor que gera esta lista é “WS_ListaDeEstados”:

 

 

 

  `Declaração do tipo de variável que retornaremos ao cliente

SOAP DECLARATION(SOAPBLOB;Is BLOB ;SOAP Output ;"SOAPBLOB")

 

  `Variável temporal utilizada para recolher as listas de estados

ARRAY STRING(2;EstadosDeEEUU;0)

 

DEFAULT TABLE([Doutores])

ALL RECORDS

DISTINCT VALUES([Doutores]Estado;EstadosDeEEUU)

 

C_LONGINT($i;$Records)

$Records:=Size of array(EstadosDeEEUU)

 

For ($i;1;$Records)

    GNT_Bags ("SetItem";->SOAPBLOB;"Estado."+EstadosDeEEUU{$i};->EstadosDeEEUU{$i})

End for

 

A linha de código mais importante é:

GNT_Bags ("SetItem";->SOAPBLOB;"Estado."+EstadosDeEEUU{$i};

->EstadosDeEEUU{$i})

 

Acrescentamos um elemento ao Bag mediante o primeiro parâmetro, “SetItem”.

Nota: GenRoTools substituirá a etiqueta e o seu valor correspondente pela especificada mediante “SetItem”. Dito de outra maneira se deseja acrescentar ao Bag todos os valores selecionados, terá que se assegurar de que a etiqueta é única.

O método do cliente que obtém o Bag com os Estados é “Provar_ListaDeEstados”:

C_BLOB(vDebugBag)

vDebugBag:=WS_ListaDeEstados

 

C_STRING(75;vEtiquetaDeLista)

vEtiquetaDeLista:="Lista de Estados em E.E.U.U. disponíveis:"

 

If (BLOB size(vDebugBag)#0)

    C_LONGINT($width;$height)

    GET FORM PROPERTIES([Table 1];"BagDebugList";$width;$height)

    

    OpenCenteredWindow ($width;$height;Plain window ;"Estados")

    DIALOG([Table 1];"BagDebugList")

    CLOSE WINDOW

Else

    ALERT("Não pôde obter-se os Estados do servidor.")

End if

 

- O método WS_ListaDeEstados criou-se automaticamente mediante o “Assistente Serviços Web” do 4D.

 

- O formulário “BagDebugList” contém os seguintes elementos:

No método de objeto “hBagDebugList” vamos pôr o seguinte método:

Case of

    : (Form event=On Load )

        C_LONGINT(hBagDebugList)

        GNT_Bags ("BuildList";->vDebugBag;"hBagDebugList")

        

        If (Is a list(hBagDebugList))

            REDRAW LIST(hBagDebugList)

        End if

        

    : (Form event=On Clicked )

        C_LONGINT($vlItemPos;$vlItemRef;$hSublist)

        C_STRING(255;$vsItemText)

        C_BOOLEAN($vbExpanded)

        

        $vlItemPos:=Selected list item(hBagDebugList)

        

        If ($vlItemPos>0)

            C_STRING(2;vEstado)

            

            GET LIST ITEM(hBagDebugList;$vlItemPos;$vlItemRef;$vsItemText;$hSublist;

$vbExpanded)

            If (Is a list($hSublist))

                DISABLE BUTTON(vOK)

            Else

                vEstado:=$vsItemText

                ENABLE BUTTON(vOK)

            End if

        End if

        

    : (Form event=On Unload )

        If (Is a list(hBagDebugList))

            CLEAR LIST(hBagDebugList;*)

        End if

End case

 

GenRoTools oferece-nos um método muito útil que converte um Bag em uma lista hierárquica:

GNT_Bags ("BuildList";->vDebugBag;"hBagDebugList")

 

Se tudo sai corretamente, veremos o seguinte resultado:

 

Segundo exemplo: Serviço Web que facilita uma lista de doutores localizado em uma zona

Baseando-nos no primeiro exemplo, solicitaremos aos doutores pertencentes a um Estado. A vantagem de utilizar a lista gerada pelo servidor no primeiro exemplo é que o usuário não há de adivinhar que Estados estão disponíveis. Isso facilita o trabalho ao usuário e melhora a sua forma de trabalhar. O método é o seguinte:

C_BLOB(vDebugBag)

vDebugBag:=WS_ListaDeEstados

 

C_STRING(75;vEtiquetaDeLista)

vEtiquetaDeLista:="Lista de Estados em E.E.U.U. disponíveis:"

 

If (BLOB size(vDebugBag)#0)

    C_LONGINT($width;$height)

    GET FORM PROPERTIES([Table 1];"BagDebugList";$width;$height)

 

    OpenCenteredWindow ($width;$height;Plain window ;"Estados")

    DIALOG([Table 1];"BagDebugList")

    CLOSE WINDOW

 

    If (OK=1)

        vDebugBag:=WS_ObterDoutores (vEstado)

        

        C_STRING(75;vEtiquetaDeLista)

        vEtiquetaDeLista:="Doutores no estado de: "+vEstado

 

        If (BLOB size(vDebugBag)#0)

            C_LONGINT($width;$height)

            GET FORM PROPERTIES([Table 1];"BagDebugList";$width;$height)

            

            OpenCenteredWindow ($width;$height;Plain window ;"Doutores")

            DIALOG([Table 1];"BagDebugList")

            CLOSE WINDOW

        Else

            ALERT("Não pôde obter-se os doutores do estado “"+vEstado+"”.")

        End if

    End if

Else

    ALERT("Não pôde obter-se os Estados do servidor.")

End if

 

- Na primeira instância, obtemos a lista de Estados disponíveis. O método do objeto BagDebugList detecta o clique no valor (não a etiqueta), no qual nos permite guardar o valor na variável vEstado.

- Se fechamos a janela mediante um clique em OK, procedemos a obter os doutores, passando o valor de vEstado como parâmetro. Igual que no exemplo 1, utilizamos o Assistente Serviços Web para gerar o método WS_ObterDoutores. Este chama ao método com o mesmo nome no servidor, que contêm o seguinte:

 

COMPILER_WEB

 

SOAP DECLARATION(SOAPString5;Is String Var ;SOAP Input ;"SOAPString5")

SOAP DECLARATION(SOAPBLOB;Is BLOB ;SOAP Output ;"SOAPBLOB")

 

DEFAULT TABLE([Doutores])

QUERY([Doutores]Estado=SOAPString5)

 

C_LONGINT($i;$Records)

$Records:=Records in selection

 

If ($Records>0)

    FIRST RECORD

    For ($i;1;$Records)

        GNT_Bags ("SetItem";->SOAPBLOB;"Doutores."+String([Doutores]ID)+".ID";

->[Doutores]ID)

        GNT_Bags ("SetItem";->SOAPBLOB;"Doutores."+String([Doutores]ID)+".Nome";

->[Doutores]Nome)

        GNT_Bags ("SetItem";->SOAPBLOB;"Doutores."+String([Doutores]ID)+".Sobrenome";

->[Doutores]Sobrenome)

        GNT_Bags "SetItem";->SOAPBLOB;"Doutores."+String([Doutores]ID)

+".Especialidade";->[Doutores]Especialidade)

        GNT_Bags ("SetItem";->SOAPBLOB;"Doutores."+String([Doutores]ID)+".Endereço";

->[Doutores]Endereço)

        GNT_Bags ("SetItem";->SOAPBLOB;"Doutores."+String([Doutores]ID)+".Cidade";

->[Doutores]Cidade)

        GNT_Bags ("SetItem";->SOAPBLOB;"Doutores."+String([Doutores]ID)+".Estado";

->[Doutores]Estado)

        NEXT RECORD

    End for

End if

- O método COMPILER_WEB contém as seguintes declarações:

 

ARRAY LONGINT(aSOAPLongint;0)

ARRAY TEXT(aSOAPText;0)

 

C_LONGINT(SOAPLongint)

C_STRING(5;SOAPString5)

C_BLOB(SOAPBLOB)

 

- Selecionamos os doutores pertencentes ao Estado passado na variável SOAPString5 (o valor da variável vEstado enviada pelo cliente) e acrescentamos os valores de cada cliente ao Bag.

Nota: é importante destacar que a etiqueta é comum por cada grupo de valores, enquanto isso a etiqueta de cada valor é única, o que permite agrupá-las em um mesmo modo. Por exemplo:

Doutores. 5000063.ID --> 5000063

Doutores. 5000063.Nome --> Kevin

Doutores. 5000063.Sobrenome --> Nguyen

Doutores. 5000063.Especialidade --> Podiatry

Doutores. 5000063.Endereço --> 28785 Via Passa Tempo

Doutores. 5000063.Cidade --> Laguna Niguel

Doutores. 5000063.Estado --> CA

Resulta na seguinte estrutura hierárquica:

- Em cada iteração, o código do doutor muda, no qual gera outro grupo de valores, e assim sucessivamente.

 

Terceiro exemplo: Serviço Web que facilita uma lista de doutores localizado em uma zona (versão otimizada)

O método “WS_ObterDoutores” do servidor realiza a sua função, mais gera muito tráfico já que utiliza NEXT RECORD por cada iteração. Se há poucos clientes não a um problema, porém, se a seleção é muito maior, a perdida de velocidade pode ser importante. A versão melhorada contém as seguintes mudanças:

COMPILER_WEB

 

SOAP DECLARATION(SOAPString5;Is String Var ;SOAP Input ;"SOAPString5")

SOAP DECLARATION(SOAPBLOB;Is BLOB ;SOAP Output ;"SOAPBLOB")

 

DEFAULT TABLE([Doutores])

QUERY([Doutores]Estado=SOAPString5)

 

ARRAY LONGINT($aDoutorID;0)

ARRAY STRING(75;$aDoutorNome;0)

ARRAY STRING(75;$aDoutorSobrenome;0)

ARRAY STRING(75;$aDoutorEspecialidade;0)

ARRAY STRING(75;$aDoutorEndereço;0)

ARRAY STRING(75;$aDoutorCidade;0)

ARRAY STRING(5;$aDoutorEstado;0)

 

SELECTION TO ARRAY([Doutores]ID;$aDoutorID;[Doutores]Nome;$aDoutorNome;

[Doutores]Sobrenome;$aDoutorSobrenome;[Doutores]Especialidade;$aDoutorEspecialidade;

[Doutores]EndereçoDireccion;$aDoutorEndereço;[Doutores]Cidade;$aDoutorCidade;

[Doutores]Estado;$aDoutorEstado)

C_LONGINT($i;$Records)

$Records:=Records in selection

 

For ($i;1;$Records)

    GNT_Bags ("SetItem";->SOAPBLOB;"Doutores."+String($aDoutorID{$i})+".ID";

->$aDoutorID{$i})

    GNT_Bags ("SetItem";->SOAPBLOB;"Doutores."+String($aDoutorID{$i})+".Nome";

->$aDoutorNome{$i})

    GNT_Bags ("SetItem";->SOAPBLOB;"Doutores."+String($aDoutorID{$i})+".Sobrenome";

->$aDoutorSobrenome{$i})

    GNT_Bags ("SetItem";->SOAPBLOB;"Doutores."+String($aDoutorID{$i})+".Especialidade;

->$aDoutorEspecialidade{$i})

    GNT_Bags ("SetItem";->SOAPBLOB;"Doutores."+String($aDoutorID{$i})+".Endereço";

->$aDoutorEndereço{$i})

    GNT_Bags ("SetItem";->SOAPBLOB;"Doutores."+String($aDoutorID{$i})+".Cidade";

->$aDoutorCidade{$i})

    GNT_Bags ("SetItem";->SOAPBLOB;"Doutores."+String($aDoutorID{$i})+".Estado";

->$aDoutorEstado{$i})

End for

 

- A cópia de valores a arrays está otimizado em 4D Server, o qual permite gerar um Bag muito mais rápido, que mediante iterações via NEXT RECORD.

 

Quarto exemplo: Serviço Web que codifica informação relacionada com um doutor

Neste exemplo recolhemos em um Bag toda a informação relacionada com um doutor: dados pessoais e visitas realizadas. O método “Provar_InfoDeUmDoutor” no cliente é o seguinte:

C_BLOB(vDebugBag)

vDebugBag:=WS_InfoDeUnDoctor (12226)

 

C_STRING(75;vEtiquetaDeLista)

vEtiquetaDeLista:="Informação do doutor 12226:"

 

If (BLOB size(vDebugBag)#0)

    C_LONGINT($width;$height)

    GET FORM PROPERTIES([Table 1];"BagDebugList";$width;$height)

    

    OpenCenteredWindow ($width;$height;Plain window ;"Doutor 12226")

    DIALOG([Table 1];"BagDebugList")

    CLOSE WINDOW

Else

    ALERT("Não pôde obter-se informação do doutor 12226.")

End if

 

- O código do doutor passa-se diretamente como parâmetro para simplificar o exemplo. Idealmente, o usuário deveria poder selecionar um doutor da lista e obter toda a informação. Deixo esse exercício para o leitor.

o método “WS_InfoDeUnDoctor” do servidor contém o seguinte:

COMPILER_WEB

 

SOAP DECLARATION(SOAPLongint;Is LongInt ;SOAP Input ;"aSOAPLongint")

SOAP DECLARATION(SOAPBLOB;Is BLOB ;SOAP Output ;"SOAPBLOB")

 

DEFAULT TABLE([Doutores])

QUERY([Doutores]ID=SOAPLongint)

 

ARRAY LONGINT(aDoutorID;0)

ARRAY STRING(75;aDoutorNome;0)

ARRAY STRING(75;aDoutorSobrenome;0)

ARRAY STRING(75;aDoutorEspecialidade;0)

ARRAY STRING(75;aDoutorEndereço;0)

ARRAY STRING(75;aDoutorCidade;0)

ARRAY STRING(5;aDoutorEstado;0)

 

SELECTION TO ARRAY([Doutores]ID;aDoutorID;[Doutores]Nome;aDoutorNome;

[Doutores]Sobrenome;aDoutorSobrenome;[Doutores]Especialidade;aDoutorEspecialidade;

[Doutores]Endereço;aDoutorEndereço;[Doutores]Cidade;aDoutorCidade;

[Doutores]Estado;aDoutorEstado)

 

C_LONGINT($i;$Records)

$Records:=Records in selection

 

For ($i;1;$Records)

    GNT_Bags ("SetItem";->SOAPBLOB;"Doutor."+ f+".ID";

->aDoutorID{$i})

    GNT_Bags ("SetItem";->SOAPBLOB;"Doutor."+String(aDoutorID{$i})+".Nome";

->aDoutorNome{$i})

    GNT_Bags ("SetItem";->SOAPBLOB;"Doutor."+String(aDoutorID{$i})+".Sobrenome";

->aDoutorSobrenome{$i})

    GNT_Bags ("SetItem";->SOAPBLOB;"Doutor."+String(aDoutorID{$i})+".Especialidade";

->aDoutorEspecialidade{$i})

    GNT_Bags ("SetItem";->SOAPBLOB;"Doutor."+String(aDoutorID{$i})+".Endereço";

->aDoutorEndereço{$i})

    GNT_Bags ("SetItem";->SOAPBLOB;"Doutor."+String(aDoutorID{$i})+".Cidade";

->aDoutorCidade{$i})

    GNT_Bags ("SetItem";->SOAPBLOB;"Doutor."+String(aDoutorID{$i})+".Estado";

->aDoutorEstado{$i})

 

    ARRAY DATE(aVisitaData;0)

    ARRAY LONGINT(aVisitaHora;0)

    ARRAY TEXT(aVisitaPropósito;0)

    ARRAY BOOLEAN(aVisitaPago;0)

 

    DEFAULT TABLE([Visitas])

    QUERY([Visitas]ID=aDoutorID{$i})

    SELECTION TO ARRAY([Visitas]Data;aVisitaData;[Visitas]Hora;aVisitaHora;

[Visitas]Propósito;aVisitaPropósito;[Visitas]Pago;aVisitaPago)

 

    C_LONGINT($j;$Visitas)

    $Visitas:=Records in selection

 

    C_BLOB(BagVisitas)

 

    For ($j;1;$Visitas)

        C_TIME(vTime)

        vTime:=aVisitaHora{$j}

        C_STRING(25;vTimeString)

        vTimeString:=String(vTime;HH MM SS )

 

        GNT_Bags ("SetItem";->BagVisitas;String($j)+".data";->aVisitaData{$j})

        GNT_Bags ("SetItem";->BagVisitas;String($j)+".Hora";->vTimeString)

        GNT_Bags ("SetItem";->BagVisitas;String($j)+".Propósito";->aVisitaPropósito{$j})

        GNT_Bags ("SetItem";->BagVisitas;String($j)+".Pago";->aVisitaPago{$j})

    End for

 

    GNT_Bags ("SetItem";->SOAPBLOB;"Doutor."+String(aDoutorID{$i})+".Visitas";

->BagVisitas)

End for

 

- Este método está baseado no terceiro exemplo.

- Cabe destacar a inclusão das visitas em um Bag secundário (BagVisitas) que logo procedemos a acrescentar no Bag que contém o resto da informação pertencente ao doutor.

Se tudo sai corretamente, veremos o resultado seguinte:

 

Obtendo valores de um Bag

A utilidade de GenRoTools que permite-nos converter um Bag em uma lista hierárquica é de ajuda fenomenal, já que nos permite estudar o Bag que recebemos do servidor. Na hora de depurar, esta opção pode-nos poupar muito tempo.

Agora bem, uma vez temos os dados que queremos codificados em um Bag, como podemos extraí-los? Mediante o seletor “GetItem”:

GNT_Bags ("GetItem";Bag;Etiqueta;VariávelDeDestino)

 

Bag -> Ponteiro ao Bag

Etiqueta -> Referência da que queremos obter o valor

VariávelDeDestino <- Valor associado a etiqueta

 

Centrem no Bag retornado pelo servidor no quarto exemplo.

Para extrair o valor de “Nome”:

C_TEXT(vNome)

GNT_Bags ("GetItem";->vDebugBag;”Doutor.12226.Nome”;->vNome)

 

Para extrair o Bag “Visitas” que contém as visitas:

C_BLOB(vVisitas)

GNT_Bags ("GetItem";->vDebugBag;”Doutor.12226.Visitas”;->vVisitas)

Para obter o número de visitas:

C_TEXT(vNumeroItems)

GNT_Bags ("CountItems";->vDebugBag;”Doutor.12226. Visitas”;->vNumeroItems)

 

Para obter o número de valores pertencentes ao doutor:

C_TEXT(vNumeroItems)

GNT_Bags ("CountItems";->vDebugBag;”Doutor.12226”;->vNúmeroItems)

 

GenRoTools automaticamente converte-se o valor guardado no Bag ao tipo da variável de destino. Por exemplo, o valor da etiqueta “ID” é Longint, mais podemos convertê-lo a String facilmente mediante:

C_TEXT(vStringID)

GNT_Bags ("GetItem";->vDebugBag;”Doutor.12226.ID”;->vStringID)

 

Conclusão

A utilização de Bags simplifica enormemente a codificação de dados para ser transpassados mediante Serviços Web. Além do mais, o código é de leitura fácil e a transmissão é ótima já que se enviam tudo de uma vez. A extração dos valores é simples e limpa, já que os dados estão organizados hierarquicamente. GenRoTools provém de uma ferramenta muito útil na hora de depurar: a conversão de um Bag em uma lista hierárquica permite analisar o conteúdo do Bag sem ter que escrever rotinas de extração.

Eu gostaria de comentar um pequeno detalhe: a conversão de um Bag com muitos registros a uma lista hierárquica é lenta. Durante o desenvolvimento isto não é talvez muito importante, já que no fim e a cabo trata-se de um elemento de depuração. No entanto, se deseja programar esta opção na versão final que o usuário manipulará, recomenda-se compilar o programa.

Um último comentário. A documentação distribuída atualmente não é muito boa, mais, com um pouco de paciência pode-se sair adiante. No meu caso, decidi escrever uns métodos próprios de apoio, para não ter que memorizar a ordem de parâmetros. Por exemplo, o método “GetBagWithLabelFromBag”:

C_POINTER($1;$DestinationBagPtr;$3;$SourceBagPtr)

C_TEXT($2;$LabelToExtract)

 

$DestinationBagPtr:=$1

$LabelToExtract:=$2

$SourceBagPtr:=$3

 

  `Obtain the desired nested bag to the destination BLOB

GNT_Bags ("GetItem";$SourceBagPtr;$LabelToExtract;$DestinationBagPtr)

 

Este método permite obter um Bag dentro de outro Bag. O utilizo da seguinte maneira:

C_BLOB(vVisitas)

GetBagWithLabelFromBag(->vVisitas;”Doutor.12226.Visitas”;->vDebugBag)

 

Uma última nota sobre a instalação: GenRoTools há de instalar-se mediante 4D Insider, então, trata-se de um componente. Logo, na estrutura onde deseja utilizar há de se colocar a seguinte linha em método do banco de dados On Startup:

GNT_GenRoTools ("OnDatabaseStart")

Mesmo que tenha tentado cobrir às partes mais significativas, é impossível cobrir tudo. Espero que este artigo tenha servido como exemplo e permita ao leitor adentrar-se no mundo genial dos Bags. Vá em frente e ânimo!

 

                                                                           Tito Ciuro
 
                                                                         Miami – Junho de 2005

 

 



Internacional | Empresa | Contatar 4D | Mapa do Site | © 4D, S.A. 2008 | Tamanho da fonte: [A] [A] [A] *Quando a solução é o que importa