BT
x A sua opinião é importante! Por favor preencha a pesquisa do InfoQ sobre os seus hábitos de leitura!

Esquemas para Web Services – Parte 1: Tipos de dados básicos

Postado por Dennis Sosnoski , traduzido por Acyr Tedeschi em 08 Jun 2009 |

A troca de mensagens XML é a base para a grande parte dos web services, incluindo as abordagens SOAP e REST. O uso do XML traz consigo algumas desvantagens, como problemas em potencial com desempenho, mas também oferecem um nível de abstração que permite diminuir o acoplamento entre as partes envolvidas na troca. Para que este "desacoplamento" realmente funcione, é preciso ser capaz de definir a estrutura dos documentos XML que estão sendo cambiados de uma maneira que sua estrutura possa ser verificada. A linguagem de definição de esquemas XML da W3C (o qual será referenciado apenas como “esquema” pelo resto deste artigo) é a abordagem mais utilizada por estas definições de estrutura de mensagens.

Grande parte das aplicações web service não trabalham com documentos XML diretamente, utilizam uma camada de conversão de um toolkit web service. Isto é conveniente para desenvolvedores, já que significa que podem trabalhar diretamente com suas estruturas de dados nas linguagens de programação escolhidas. Mas esta conversão deve saber lidar com os desencontros entre os esquemas dos tipos de dados e os tipos de dados e estruturas das linguagens de programação, já que podem causar problemas nas aplicações. Se precisa que a aplicação web service seja consistente, compatível em diferentes plataformas (o que é, geralmente, o maior motivo para se utilizar web services), é preciso planejar suas definições de esquema para evitar áreas de problemas em potencial – ou, no mínimo, estar ciente dos riscos envolvidos ao se utilizar esquemas com características problemáticas.

Nesta série de arquivos iremos estudar os diversos tipos de problemas que aparecem dos desencontros entre o esquema e conversões de dados das aplicações web service. Neste primeiro artigo começaremos com o nível mais básico, olhando tipos de dados simples e os problemas que geram.

Representando números

Valores numéricos são os mais fundamentais com que trabalhar á quando falamos em dados de negócio. Dada a importância dos números, você deve pensar que esta seria uma área onde o esquema trabalhasse de forma harmoniosa e consistente. E em um sentido abstrato, realmente trabalha – mas quando o esquema é aplicado por toolkits de web services você ainda pode enfrentar uma porção de problemas.

Parte do problema é o grande número de ramificações internas ao tipos numéricos do esquema. A Figura 1 mostra as partes da árvore de tipos de dados do esquema envolvidas nesta área. Para entendê-la, pense em termos de especialização – quanto mais você desce em uma das ramificações da árvore, mais especializado é o dado representado pelo tipo. No nível mais alto, diretamente abaixo do tipo genérico anySimpleType, estão os três tipos numéricos básicos float, decimal e double. Float e double são tipos finais, em acordo com o padrão IEEE para números de ponto flutuante, o quê oferece excelente interoperabilidade através as plataformas de web services: Todas as grandes linguagens de programação suportam números de ponto flutuante 32 bits em acordo com a especificação de números float e suportam números de ponto flutuante 64 bits em acordo com a especificação do esquema double, assim toolkits de web services apenas os mapeiam diretamente para os tipos nativos da linguagem. Podem existir pequenas diferenças entre representações de texto para valores especiais (não é número, infinito positivo e negativo, zero positivo e negativo) entre a linguagem de programação e os utilizados pelo esquema, mas os toolkits podem facilmente traduzí-los.

Figura 1. Schema numeric types

É quando você percorre a linha decimal na árvore que começam os problemas. decimal por si é definido com uma string de qualquer número de dígitos decimais, com um sinal opcional no começo da sentença e com um, também opcional, ponto decimal. integer, um descendente direto de decimal, aceita um subconjunto dos valores correspondentes ao decimal que permitem qualquer número de dígitos decimais, com um sinal opcional, mas que não permite o ponto decimal. Os descendentes de integer restringem ainda mais os valores permitidos, no caso dos nonPositiveInteger e nonNegativeInteger ao proibir valores maiores e menores que zero, respectivamente e no caso do tipo long ao limitar a faixa de valores para equivalentes 2s-complementares de 64-bits. int, short e byte são ainda mais restritivos, sendo 2s-complementares para 32-bits, 16-bits e 8-bits, respectivamente, enquanto as variações unsigned aceitam valores sem sinal dos mesmos números de bits.

Todas as grandes linguagens de programação suportam valores que combinam com os tipos de esquema: long, int e short através da ramificação principal da árvore, mas as outras variações criam problemas em potencial. Java, por exemplo, não inclui tipos primitivos que correspondam à unsignedLong ou unsignedInt. Frameworks para web services Java geralmente resolvem esta falha no suporte da linguagem usando classes especial no lugar de primitivos para estes tipos, mas isto torna a interface web service nada elegante e pode criar problemas de desempenho (já que primitivos são, geralmente, mais rápidos do que objetos quando utilizados em cálculos).

 

Nem os tipos decimal e integer demonstram problemas. A maioria dos toolkits Java lidam com eles usando as classes padrão java.lang.BigDecimal e java.lang.BigInteger, que sofrem com o baixo desempenho mas suportam valores de tamanho ilimitado. Já o .Net usa um valor de tamanho fixo de128-bits para a representação, o que limita a faixa de valores (como permitido pela especificação do esquema) mas oferece um relativo bom desempenho.

O esquema de tipos numéricos é confuso e inconsistente (porque um tipo nonPositiveInteger, mas não um tipo nonPositiveDecimal? Por exemplo.) e geralmente representam apenas detalhes sintáticos de qualquer maneira (já que as faixas podem ser implementadas usando a restrição simpleType). Por estas razões é melhor evitar a utilização da maioria destes tipos nas suas definições de esquema, especialmente aqueles que irão ser usados com web services. Use tipos de tamanho específico (double e float para números reais, long e int para inteiros) quando possível, já que estes são traduzidos de maneira consistentes para tipos primitivos de linguagem. Se precisa trabalhar com valores além da faixa ou precisão oferecida por eles, entenda que decimal e integer não irão, necessariamente, oferecer o que quer devido à diferenças de implementação, ao invés disso considere utilizar uma string e manipular a conversão de valor no código da aplicação.

Problemas com o Time

Valores relacionados com tempo são outra fonte comum de problemas ao se trabalhar com esquemas. Nove tipos de dados distintos são definidos, todos baseados em uma versão particular do calendário Western Gregorian. Diferente dos tipos numéricos, valores relacionados com o tempo não têm nenhuma forma direta de especialização - todos são considerados descendentes diretos do tipo genéricoanySimpleType.

Os tipos mais amplamente utilizados são dateTime, date e time. Estes três tipos de dados compartilham uma representação comum de formato, sendo o dateTime o caso geral. Aqui temos um exemplo de dateTime, para o momento em que estou escrevendo este artigo: "2008-09-08T15:38:53". Um valor date usa a mesma representação que dateTime, sem a letra 'T' e os valores de hora, minuto e segundo que a seguem (restando, neste caso, o "2008-09-08"); um valor time do contrário, retira tudo do começo até a letra 'T', inclusive, mantendo somente os valores de hora, minuto e segundo ("15:38:53").

Parece simples até aqui, certo? O quê torna as coisas confusas é atual interpretação de um destes valores. Datas e horários variam de acordo com o local onde você está, e esta variação é expressa, normalmente, em termos de time zones. Por exemplo, enquanto escrevo este artigo na Nova Zelândia, estou 12 horas à frente do horário Universal e 19 horas à frente do Pacific Daylight Time que afeta a costa oeste dos Estados Unidos. No instante que escrevia meu exemplo de dateTime como "2008-09-08T15:38:53", as horas em Seatle eram"2008-09-08T20:38:53".

Para muitas aplicações você precisar especificar datas/horas de uma maneira que permita o relacionamento entre um valor e outro. Esquemas suportam esta necessidade permitindo a indicação de time zone nos valores de data e hora. Esta indicação pode ter a forma da letra 'Z', indicando um valor UTC, ou uma diferença a partir do Universal time em horas e minutos. Assim, quaisquer destes valores dateTime (bem como muitas outras variações) podem representar o mesmo momento: "2008-09-08T15:38:53+12:00", "2008-09-07T20:38:53-08:00" ou "2008-09-08T03:38:53Z".

Mas o esquema não exige exige que você especifique uma time zone e sem tal informação um valor data/hora pode ser interpretado como pertencendo à qualquer lugar. Para algumas aplicações este comportamento é o que você quer – uma data de aniversário, por exemplo, normalmente é tratada como uma data particular sem referencia de uma localidade, e as pessoas preferem celebrar o Ano Novo Gregoriano de maneira local ao redor do mundo – mas para outros casos ele cria problemas. Considere o caso de uma chamada em conferência, por exemplo, onde as partes envolvidas precisam sincronizar o instante do evento com seus relógios locais.

Infelizmente, o esquema não permite que você faça distinção entre os casos onde uma data/hora totalmente especificada é esperada e aos que permitem valores sem time zone ou sequer esperadas (pelo menos não de uma maneira onde os web services possam interpretar – é possível fazê-lo usando padrões de restrição do simpleType mas padrões geralmente são ignorados pelos toolkits). Assim a ambiguidade do esquema neste ponto significa que os toolkits precisam lidar com ambos os casos, com e sem indicação de time zone.

Esta necessidade cria algumas dores de cabeça em termos de interpretação, especialmente considerando que linguagens de programação implementam data/hora baseadas em valores absolutos de tempo. Simplesmente não existe como converter um valor de esquema, sem o time zone, para um valor absoluto de tempo. Obviamente, isto não impossibilita que os toolkits façam alguma com estes valores. Na maioria dos casos eles convertem o valor supondo um time zone local, e geralmente é isso que você quer – mas quando não, os problemas resultantes podem ser muito difíceis de isolar.

Problemas de time zones são especialmente confusos para os tipos date. É comum que pessoas tratem datas como um lugar fixo no calendário. Quando você assina um documento, por exemplo, geralmente preenche a data em que está assinando. Se concorda com um novo projeto, será agendado uma data para seu término (por mais fantasiosa que esta data possa ser). E se lhe pedem sua licença de motorista para comprovar sua idade em uma compra, o vendedor olhará sua data de nascimento e a comparará com uma idade de corte. Em todos estes casos dias estão sendo utilizados para a resolução de datas e diferenças entre time zones são comumente ignorados. Mas o tipo de dado date do esquema utiliza uma time zone associada ao valor, como o fazem os tipos dateTime e time. Esta utilização de time zone separa o tipo date do esquema da forma comum de uma data. Isto é resolvido convertendo as datas para a hora 00:00 (meia noite, como o início do dia) representando o início do dia em qualquer time zone Mas se você verificar o valor daquela data usando um time zone local, poderá encontrar diferenças entre o que estava especificado originalmente no documento.

Se o esquema separasse os tipos de dados para valores de data/hora com time zone e os que não o tivessem, seria fácil para que as aplicações escolhessem que tipo iriam utilizar. Sem esta habilidade, é difícil para os toolkits trabalharem com representações incorretas dos valores de data/hora. A API Java JAXB 2.0 utiliza o que pode ser a melhor abordagem para o problema, tratando todos os tipos de dado data/hora como uma classe especial (javax.xml.datatype.XmlGregorianCalendar) que corresponde diretamente às representações de esquema. Esta abordagem preserva todos os detalhes dos valores representados pelo esquema, mas passam os problemas de interpretação para os desenvolvedores. Outros toolkits utilizam defaults, como assumir time zone local.

Dados os desagradáveis problemas que se esgueiram nesta área, a melhor abordagem é provavelmente utilizar apenas tipos data/hora para valores que possam ser totalmente especificados com indicações de time zone, e ter certeza que qualquer documento que você crie tenha esta indicação. Grande parte de toolkits para web services gerarão indicações de time zone para você automaticamente, sendo assim, a última parte é fácil. Exigir que seus documentos de entrada também usem indicações de time zone pode ser mais difícil., ainda mais que estes podem vir de diversos estágios de processamento. Se você quer ter certeza que não terá problemas causados por conversões equivocadas sua melhor solução é utilizar um tipo string, assim seu toolkit web service passará o valor para o código de seu aplicação sem tentar interpretar seu valor.

Se você precisa de valores de data/hora sem time zone (como no exemplo do aniversário), a melhor abordagem pode vir a ser, mais uma vez, utilizar o tipo string. Não muito satisfatório do ponto de vista de precisão de dados no esquema, mas impede que os toolkits web services interpretem valores sem time zone como sendo locais.

Referências

Estruturas de dados utilizadas internamente por aplicações contém múltiplas ligações entre componentes, incluindo referencias cruzadas e associações indiretas. XML, do contrário, é inerentemente estruturada em árvore. É muito fácil representar relacionamentos um para muitos em XML através de contenção, mas qualquer outro tipo de relacionamento é problemático. Até relacionamentos um para muitos podem ser ineficientes. Considere o caso de um documento que liste o histórico de pedidos de um cliente, por exemplo. Cada pedido estará associado à endereços de cobrança e entrega, mas estes endereços provavelmente serão repetidos entre os pedidos. Se simplesmente incluir o endereço dentro de cada pedido, acabará com uma quantidade muito grande de informação redundante em seus documentos.

Referências podem ser utilizadas para resolver as limitações da estrutura em árvore do XML. A ideia é que se define alguma coisa em um documento XML, incluindo um identificador único. Há qualquer momento que um dado precise utilizar aquela definição, você cria uma referência utilizando aquele identificador único.

O esquema suporta duas formas de referências de forma direta. A primeira, usando o tipo ID, define identificadores de elemento que podem ser ligados de qualquer lugar do documento utilizando os tipos IDREF ou IDREFS. A parte boa das ligações IDREF e IDREFS é que são simples – identificadores são nomes, e qualquer tipo de elemento pode definir um valor ID no esquema. A parte ruim destas ligações é que são utilizadas em um contexto global, assim não existe maneira de dizer que um valor utilizado para um IDREF particular deve ser definido em tipo de elemento particular, e os nomes utilizados como valores ID devem ser únicos no documento (inclusive entre tipos de elementos). Alguns toolkits web service suportam as ligações ID/IDREF para representar referências dentro de estruturas de dados (incluindo JAX-WS/JAXB 2.0 e o Apache Axis2 quando utilizado com a ligação de dados JiBX); outros toolkits (como o .Net e o Axis2 utilizado com ADB) não suportam tais ligações, tratando valores IDREF como simples valores string.

O segundo tipo de referências suportados pelo esquema são as ligações key/keyref. Enquanto ligações ID/IDREF são definidas utilizando tipos de dados, ligações key/keyref são parte da estrutura de definição do esquema. Esta diferença permite que estas ligações sejam muito mais expressivas que as ID/IDREF, incluindo definições contextuais onde valores chave são únicos. Porém, dado que as ligações deste segundo tipo são projetadas mais para propósitos de validação de documentos do que para estruturação, eles são complexos e geralmente não utilizados por ligações de dados em frameworks que convertem dados XML de e para estrutura de dados.

Assim, se você deseja incluir referências em seus documentos XML e utiliza toolkits web services para manipulá-los, sua única esperança é a abordagem ID/IDREF. Alguns toolkits suportarão estas ligações diretamente, outros tratarão os valores identificadores como strings, mas você pode escrever a aplicação para criar a referência ao identificador e valores, construindo suas próprias ligações.

Conclusão

Neste artigo vimos alguns problemas que aparecem quando utilizamos os tipos de dados mais comuns em esquemas de web services. Existem muitos outros tipos de dados especializados além dos aqui mencionados (num total de 42!), e alguns deles apresentam outros problemas. Como um princípio geral, a melhor abordagem nas definições de esquemas web service é evitar o uso de tipos excessivamente especializados (exceto os tipos numéricos que combinam com tipos de linguagens de programação) e utilize o tipo string quando desejar controle total sobre a interpretação dos valores.

É importante salientar que embora alguns dos problemas discutidos aqui possam ser melhor trabalhados por conversões de dados em frameworks, muitos dos problemas são do esquema em si. Em particular, a família do tipo de dados data/hora são os mais incômodos para trabalhar e os que mais inferem erros pela falta de distinção entre valores com e sem time zone É possível passar a confusão para o usuário, como o JAXB faz com o tipo XmlGregorianCalendar, mas isto não é uma solução de fato.

Sobre o autor

Dennis Sosnoski é consultor e instrutor especializado em web services e SOA baseado em Java. Sua experiência profissional em desenvolvimento de software ultrapassa 30, sendo que os últimos 10 foram focados em server-side XML e tecnologia Java. É desenvolvedor líder da ferramenta, com código livre, JiBX de conversão XML e associado ao framework de web services JiBX/WS, assim como integrante do framework Apache Axis2. Também é membro do grupo de especialistas das especificações JAX-WS 2.0 e JAXB 2.0. Para informações sobre seus serviços de treinamento e consultoria verifique seu portal web http://www.sosnoski.co.nz.

Olá visitante

Você precisa cadastrar-se no InfoQ Brasil ou para enviar comentários. Há muitas vantagens em se cadastrar.

Obtenha o máximo da experiência do InfoQ Brasil.

Dê sua opinião

HTML é permitido: a,b,br,blockquote,i,li,pre,u,ul,p

Receber mensagens dessa discussão
Comentários da comunidade

HTML é permitido: a,b,br,blockquote,i,li,pre,u,ul,p

Receber mensagens dessa discussão

HTML é permitido: a,b,br,blockquote,i,li,pre,u,ul,p

Receber mensagens dessa discussão

Dê sua opinião

Conteúdo educacional

Feedback geral
Bugs
Publicidade
Editorial
InfoQ Brasil e todo o seu conteúdo: todos os direitos reservados. © 2006-2014 C4Media Inc.
Política de privacidade
BT