BT

Disseminando conhecimento e inovação em desenvolvimento de software corporativo.

Contribuir

Tópicos

Escolha a região

Início Artigos Trabalhando Dúvidas sobre REST

Trabalhando Dúvidas sobre REST

Invariavelmente aprender sobre REST significa que você vai acabar se questionando sobre o quão aplicável o conceito é de fato para seu cenário. E dado que você provavelmente está acostumado a abordagens arquiteturais completamente diferentes é natural que você comece a pensar se REST, ou melhor RESTful HTTP, realmente funciona na prática ou simplesmente quebra quando você passa dos itens introdutórios do tipo “Hello, World”. Nesse artigo eu vou tentar tirar 10 das dúvidas mais comuns que as pessoas têm sobre REST quando elas começam a explorá-lo, especialmente se elas possuem ampla experiência na abordagem arquitetural por trás de Web services SOAP/WSDL.

1. REST pode ser usado para CRUD, mas não para lógica de negócios “de verdade”

Essa é a reação mais comum que eu vejo nas pessoas que são céticas sobre os benefícios de REST. Afinal de contas, se tudo o que você tem é criar/ler/atualizar/apagar, como você poderia expressar semânticas de aplicação mais complicadas? Eu tentei abordar algumas dessas preocupações no artigo introdutório dessa série, mas esse ponto definitivamente merece uma discussão mais apurada.

Antes de mais nada, os verbos HTTP - GET, PUT, POST e DELETE - não são mapeados 1:1 para operações de banco de dados. Por exemplo, tanto POST quanto PUT podem ser utilizados para criar um novo recurso: eles são diferenes no sentido de que com PUT é o cliente que determina a URI do recurso (que é então atualizado ou criado), enquanto um POST é endereçado a uma “coleção” ou “fábrica” de recursos e é tarefa do servidor prover uma URI. Mas de qualquer forma, de volta a questão: como você lida com lógica de negócios mais complexa?

Qualquer computação calc(a, b) que retorna um resultado c pode ser transformada em uma URI que identifica seu resultado — por exemplo x = calc(2,3) pode se tornar http://exemplo.com/calculo?a=2&b=3. A princípio isso parece um erro grosseiro no uso de RESTful HTTP — não deveríamos usar URIs para identificar recursos ao invés de operações? Sim, mas de fato é isso que estamos fazendo: http://exemplo.com/soma?parcela1=2&parcela2=3 identifica um recurso, o resultado de se somar 2 com 3. E nesse exemplo particular (e obviamente restrito), usar GET para pegar o resultado pode ser uma boa idéia — afinal de contas, é possível colocá-lo no cache, ele pode ser referenciado, e computá-lo é provavelmente seguro e não muito caro.

Claro que em muitos, se não na maioria dos casos, usar um GET para computar algo pode ser a abordagem errada. Lembre-se que GET é para ser uma operação “segura”, isto é, o cliente não aceita nenhuma obrigação (como pagar pelo seus serviços) ou assume qualquer responsabilidade quando tudo o que ele faz é seguir um link emitindo um GET. Em vários outros casos é mais razoável prover a entrada de dados ao servidor de forma que ele possa criar um novo recurso via POST. Na sua resposta o servidor indica a URI do resultado (e possivelmente retorna um redirect para levá-lo até lá). O resultado é então reutilizável, pode ser salvo nos favoritos, pode ser guardado no cache quando é acessado … você pode basicamente estender esse modelo para qualquer operação que leve a um resultado — que pode ser qualquer uma que você imagine.

2. Não há contrato formal nem linguagem de descrição

De RPC a CORBA, de DCOM a Web Services, nós estamos acostumados a ter uma descrição de interfaces que listas as operações, seus nomes e os tipos de seus parâmetros de entrada e saída. Como REST pode ser utilizável sem uma linguagem de descrição de interfaces?

Há três respostas para essa pergunta feita frequentemente.

Antes de mais nada, se você decidir usar RESTful HTTP com XML — uma escolha bastante comum — todo o mundo de linguagens de schema XML, como os DTDs, XML Schema, RELAX NG ou Schematron ainda estão disponíveis para você. Discutivelmente, 95% do que você normalmente descreve usando WSDL não está de nenhuma forma amarrado ao WSDL, mas sim relacionado com os tipos complexos do schema XML que você define. O que WSDL adiciona além disso é basicamente relacionado a operações e seus nomes — e descrevê-las se torna bastante chato com a interface uniforme do REST: afinal de contas, GET, PUT, POST e DELETE são todas as operações que você tem. Com relação ao uso de Schema XML, isso significa que você pode usar sua ferramenta favorita de binding de dados (se você tiver uma) para gerar código de binding para a linguagem de programação escolhida, mesmo que você use uma interface RESTful. (Essa não é uma resposta completa, veja mais abaixo).

Em segundo lugar, pergunte-se por que você precisa de uma descrição. O caso mais comum — apesar de não único — para se ter alguma descrição é a geração de stubs e skeletons para a interface que você está descrevendo. Isso normalmente não vale como documentação, já que a descrição contida em algo como WSDL não diz nada sobre a semântica da operação — ela apenas lista um nome. De qualquer jeito você precisa de alguma documentação para as pessoas para que elas saibam como invocar a operação. Em uma abordagem tipicamente REST, o que você provê é documentação em formato HTML, possivelmente incluindo links diretos para os seus recursos. Usando a abordagem de se ter múltiplas representações, você pode ter recursos auto-documentados — apenas envie um HTTP GET para um recurso a partir de seu browser e receba uma documentação HTML contendo dados e uma lista das operações (verbos HTTP) que você pode realizar sobre o recurso e os tipos de conteúdo que ele aceita e retorna.

Por último, se você insiste em ter uma linguagem de descrição para seu serviço RESTful, você pode usar tanto a Web Application Description Language (WADL) ou — dentro de alguns limites — WSDL 2.0, que segundo seus autores também é capaz de descrever serviços RESTful. No entanto, nem WADL nem WSDL 2 são úteis para descrever hipermídia — e considerando que esse é um dos aspectos fundamentais de REST, eu não tenho tanta certeza de que elas são suficientemente úteis.

3. Quem iria querer expor tanto da implementação interna de sua aplicação?

Outra preocupação comum é que recursos são muito baixo nível, isto é, um detalhe de implementação que ninguém deveria expor. Afinal de contas, isso não coloca no cliente (o consumidor) a responsabilidade de usar os recursos para atingir algo significativo?

A resposta curta é: Não. A implementação de um GET, PUT ou qualquer outro método de um recurso pode ser tão simples ou complicada quanto a implementação de um “serviço” ou chamada RPC. Aplicar princípios de design REST não significa que você precisa expor itens individuais de seu modelo de dados — significa apenas que ao invés de expor sua lógica de negócios de uma forma baseada em operações, você o faz de uma forma baseada em dados.

Uma preocupação relacionada é a noção de que não permitir acesso direto aos recursos melhora a segurança. Isso é baseado em uma velha crença de “segurança por obscuridade”, e alguém pode argumentar que o efeito é o oposto: ao esconder quais recursos individuais você acessa a partir do protocolo específico da sua aplicação, você não pode mais usar facilmente a infra-estrutura para protegê-los. Ao dar URIs individuais para recursos significativos, você pode por exemplo usar as regras de segurança do Apache (assim como lógica de re-escrita, logging, estatística, etc.) para trabalhar diferentemente com recursos diferentes. Ao tornar isso explícito, você não diminui, mas sim aumenta, a segurança.

4. REST funciona somente com HTTP, não é independente de protocolo de transporte

Antes de mais nada, HTTP não é de forma alguma um protocolo de transporte, mas sim um protocolo de aplicação. Ele usa TCP como transporte, mas possui semântica que vai além dele (de outra forma não serviria para muita coisa). Usar HTTP meramente como transporte é abusar dele.

Em segundo lugar, abstração nem sempre é uma boa idéia. Web services usam a abordagem de tentar esconder muitas tecnologias diferentes sobre uma única camada de abstração — mas abstrações tendem a vazar. Por exemplo, há uma diferença gigantesca entre enviar uma mensagem via JMS ou via uma requisição HTTP. Tentar simplificar todas as diferentes opções a um mesmo denominador comum não ajuda ninguém. Uma analogia seria criar uma abstração que esconde um banco de dados relacional e um sistema de arquivos através de uma API comum. Claro que isso é factível, mas assim que você tratar aspectos como pesquisas através de queries, a abstração se torna um problema.

Por último, como Mark Baker disse certa vez: “Independência de protocolo é um bug, não uma funcionalidade”. Apesar de isso ser estranho a primeira vista, você tem que considerar que independência de protocolo de fato é impossível de se atingir — você pode no máximo escolher depender de um protocolo diferente que pode ou não estar em outro nível. Depender de um protocolo ofcial e amplamente aceito como HTTP não é um problema. Isso é especialmente verdade se ele é muito mais usado e suportado que a abstração tentando substituí-lo.

5. Não há nenhum guia prático, claro e consistente de como modelar aplicações RESTful

Há vários aspectos do design RESTful em que não há um conjunto “oficial” de boas práticas, não há nenhuma forma padrão de resolver um problema particular usando HTTP de acordo com princípios REST. Há poucas dúvidas de que isso podia ser melhor. Ainda assim, REST possui muito mais conceitos de aplicação que web services baseados em WSDL/SOAP. Em outras palavras: enquanto há muito valor nessa crítica, ela é muito mais relevante com relação às alternativas (que basicamente não oferecem nenhum guia).

Ocasionalmente essa dúvida aparece na forma de “mesmo os experts em REST não concordam sobre como usá-lo”. Em geral, isso não é verdade — por exemplo, eu tendo a acreditar que os principais conceitos que eu descrevi aqui algumas semanas atrás não são discutidos (nem serão) por nenhum membro da comunidade REST (se é que podemos assumir que existe uma), não exatamente porque esse é um grande artigo, mas simplesmente porque há bastante consenso depois que as pessoas aprendem um pouco além do básico. Se você tiver a chance de realizar um experimento, veja se é mais fácil achar cinco proponentes de SOA que concordam em algo ou cinco proponentes de REST. Baseado em experiências passadas e extensa participação em grupos de discussão de SOA e REST, eu tendo a apostar meu dinheiro no pessoal do REST.

6. REST não suporta transações

O termo “transação” é bastante sobrecarregado, mas em geral, quando as pessoas falam sobre transações, elas se referem ao ACID usado em bancos de dados. Em um ambiente SOA — independente de se baseado em web services ou apenas HTTP — é provável que cada implementação de serviço (ou sistema, ou aplicação web) interaja com um banco de dados que suporte transações: nada demais até aqui, fora o fato de que você mesmo provavelmente terá que criar a transação explicitamente (a menos que o seu serviço rode num container EJB ou outro ambiente que trate as transações por você). O mesmo vale caso você interaja com mais de um recurso.

As coisas começam a ficar diferentes quando você combina (ou compõe, se preferir) transações em uma unidade maior. Em um ambiente de Web services existe pelo menos uma opção para fazer as coisas se comportarem de forma similar ao que as pessoas estão acostumadas em cenários 2PC (2-phase commit) como suportados por ambientes Java EE, por exemplo: WS-Atomic Transaction (WS-AT), que é parte da família de padrões WS-Coordination. Em essência, WS-AT implementa algo bastante similar ou igual ao protocolo 2PC especificado pela XA. Isso significa que o seu contexto de transação será propagado usando cabeçalhos SOAP e sua implementação fará com que os gerenciadores de recursos a relacionem à transação existente. Em essência, o mesmo modelo em desenvolvimento de EJBs é usado — sua transação distribuída se comporta atomicamente como se fosse uma transação local.

Existem muitas coisas para se dizer sobre, ou melhor, contra, transações atômicas em um ambiente SOA:

  • Baixo acoplamento e transações, especialmente as do tipo ACID, simplesmente não funcionam bem juntos. O simples fato de que você está coordenando um commit através de múltiplos sistemas independentes cria um acoplamento bem alto entre eles.
  • A capacidade de executar essa coordenação precisa de controle centralizado sobre todos os serviços — é bastante improvável, provavelmente impossível, executar uma transação 2PC para além das fronteiras da empresa.
  • A infra-estrutura necessária para suportar isso normalmente é cara e complicada.

Na maioria das vezes, a necessidade de transações ACID em um ambiente SOA ou REST na verdade indica um problema de design — você provavelmente modelou seus serviços ou recursos do jeito errado. É óbvio que transações atômicas são só um tipo de transação — existem modelos estendidos de transações que podem ser uma opção melhor em sistemas com acoplamento baixo. No entanto, sua adoção ainda é pequena — mesmo no campo dos Web services.

7. REST não é confiável

É comum falarem que não há nenhum equivalente de WS-ReliableMessaging para RESTful HTTP, e muitos concluem que por causa disso REST não pode ser aplicado quando confiabilidade é importante (o que se aplica a praticamente todo sistema que tem alguma relevância em cenários de negócios). Mas muito frequentemente o que você quer não é um componente de infra-estrutura que lide com entrega de mensagens, mas sim o que você de fato precisa saber é se a mensagem foi entregue ou não.

Normalmente receber uma mensagem de resposta — como um simples 200 OK no caso do HTTP — significa que você sabe que o outro lado da comunicação recebeu a requisição. Problemas acontecem quando você não recebe a resposta: você não sabe se sua requisição não chegou ao outro lado ou se ela foi recebida (resultando em algum processamento) e a mensagem de resposta é que foi perdida.

A forma mais simples de garantir que uma mensagem de requisição chegue ao outro lado é reenviá-la, o que obviamente só é possível se o receptor é capaz de lidar com requisições duplicadas (por exemplo, ignorando-as). Essa capacidade é chamada idempotência. O HTTP garante que GET, PUT e DELETE são idempotentes — e se sua aplicação for implementada corretamente, um cliente pode simplesmente reenviar aquelas requisições se ele não tiver recebido a resposta. No entanto, uma mensagem de POST não é idempotente — ao menos não há nenhuma garantia na especificação HTTP que diga que sim. Você tem algumas opções: você pode começar a usar PUT (se sua semântica puder ser mapeada para ele), usar uma boa prática comum descrita por Joe Gregorio , ou adotar alguma das propostas existentes que visam padronizar isso (como o POE de Mark Nottingham, a Confiabilidade de SOA de Yaron Goland , ou o HTTPLR de Bill de hÓra).

Pessoalmente eu prefiro a abordagem de boa prática — isto é, transformar a questão da disponibilidade em um aspecto de design da aplicação, mas opiniões com relação a isso divergem.

Enquanto boa parte dessas soluções resolve boa parte do desafio da confiabilidade, não há nada — ou ao menos nada que eu conheça — que suporte garantia de entrega, como entrega ordenada de uma sequência de requisições e respostas HTTP. Vale a pena notar, no entanto, que muitos dos cenários existentes de SOAP/WSDL funcionam sem WS-Reliable Messaging ou qualquer um de seus predecessores.

8. Ausência de suporte a publicação/assinatura

REST é fundamentalmente baseado em um modelo cliente-servidor e HTTP sempre se refere a um cliente e um servidor como extremos de uma comunicação. Um cliente interage com um servidor enviando requisições e recebendo respostas. Em um modelo de publicação/assinatura a parte interessada assina uma categoria particular de informação e é notificada cada vez que algo novo aparece. Como o modelo de publicação/assinatura pode ser suportado em um ambiente RESTful HTTP?

Não precisamos olhar muito longe para ver um exemplo perfeito disso: isso é chamado sindicação, do que RSS e Atom são exemplos. Um cliente pede novas informações enviando uma requisição HTTP para um recurso que representa uma coleção de mudanças, por exemplo, uma categoria particular ou um determinado intervalo de tempo. Isso poderia ser extremamente ineficiente, mas não é, porque GET é a operação mais otimizada da web. De fato, você pode facilmente imaginar que um servidor de weblog popular precisaria escalar muito mais se ele tivesse que notificar ativamente cada cliente que assinou o serviço sobre cada mudança. Notificação por polling escala extremamente bem.

Você pode estender o modelo de sindicação para os recursos da sua aplicação — por exemplo, oferecer um feed Atom para mudanças em recursos de clientes ou para a auditoria de reservas. Além de ser capaz de satisfazer um número sem limite de aplicações assinando o serviço, você também pode ver esses feeds em um leitor de feeds, similar a ver a representação HTML de um recurso no seu browser.

Claro que isso não é uma resposta razoável para todos os cenários. Por exemplo, requisitos de soft realtime podem eliminar isso como opção e outra tecnologia pode ser mais apropriada. mas em muitos casos, a mistura de baixo acoplamento, escalabilidade e notificação pelo modelo de sindicação é uma excelente opção.

9. Ausência de interações assíncronas

Dado o modelo de requisição/resposta do HTTP, como é possível implementar comunicação assíncrona? Novamente, precisamos notar que existem várias coisas as quais as pessoas se referem quando se fala sobre assincronismo. Alguns se referem ao modelo de programação, que pode ser bloqueante ou não-bloqueante independente das interações na rede. Isso não é nossa preocupação aqui. Mas como você entrega uma requisição de um cliente (consumidor) para um servidor (provedor) quando o processamente pode levar algumas horas? Como o consumidor pode saber que o processamento foi concluído?

HTTP tem um código de resposta específico, 202 (Accepted), cujo significado é definido como “A requisição foi aceita para processamento, mas o processamento não está acabado.” Isso obviamente é o que estamos procurando. Dependendo do resultado, existem várias opções: o servidor pode retornar uma URI de um recurso que o cliente pode invocar por GET para acessar o resultado (embora se o recurso foi criado especificamente devido a essa requisição, um 201 Created seria provavelmente melhor). Ou o cliente pode incluir uma URI pela qual espera que o servidor informe o resultado com um POST quando tiver acabado.

10. Falta de ferramentas

Por último, as pessoas costumam reclamar da falta de ferramentas disponíveis que suportam o desenvolvimento de RESTful HTTP. Como indicado no item 2, isso não é verdade com relação aos dados — você pode usar o binding de dados e outras APIs de dados com as quais você está acostumado, já que essa é uma preocupação ortogonal ao número de métodos e as formas de invocá-los. No que diz respeito a HTTP básico e suporte a URIs, absolutamente todas as linguagens de programação, frameworks e toolkits do planeta possuem suporte pronto. Por último, fornecedores estão surgindo com suporte cada vez maior, (supostamente) mais fácil e melhor para desenvolvimento RESTful HTTP nos seus frameworks, como por exemplo o JAX-RS (JSR 311) da Sun ou o suporte a REST no .NET 3.5 ou o Framework de Serviços de Dados ADO.NET da Microsoft.

Conclusão

Então REST e HTTP, sua implementação mais comum, são perfeitos? Claro que não. Nada é perfeito, definitivamente não em todos os cenários, e muitas vezes nem mesmo um único. Eu ignorei completamente algumas áreas de problemas bastante razoáveis que requerem respostas mais complicadas, por exemplo, segurança baseada em mensagens, atualizações parciais e processamento em lote, e prometo tratá-los em um futuro artigo. Eu espero ter conseguido tirar algumas de suas dúvidas — e se eu ignorei as mais importantes, você sabe para que servem os comentários.

Stefan Tilkové o lead editor da comunidade SOA da InfoQ e co-fundador, principal consultor e principal RESTafarian da empresa alemã/suíça innoQ.

Avalie esse artigo

Relevância
Estilo/Redação

Conteúdo educacional

BT