Construa um Serviço da Web RESTful

Uma introdução à estrutura do REST e Restlet

Representational state transfer (REST) é um estilo de se projetar aplicativos fracamente acoplados que contam com recursos nomeados, e não com mensagens. A parte mais difícil da construção de um aplicativo RESTful é decidir sobre os recursos que você quer expor. Depois disso, o uso da estrutura do Restlet de software livre transforma a construção de serviços da Web RESTful em uma tarefa muito fácil. Este tutorial guia você passo a passo pelos conceitos fundamentais de REST e da construção de aplicativos com Restlets.

Andrew Glover , Co-Founder, ThirstyHead.com

Andrew GloverAndrew Glover é presidente da Stelligent Incorporated, que ajuda as empresas a adotar estratégias de teste de desenvolvedor e técnicas de integração contínua que permitem que equipes entreguem o software com mais rapidez. Consulte o blog do Andy para obter uma lista de suas publicações.



22/Jul/2008

Antes de Iniciar

Sobre este Tutorial

REST é uma forma de pensar, e não um protocolo ou um padrão. É um estilo de se projetar aplicativos fracamente acoplados — frequentemente, aplicativos orientados para a Web — que contam com recursos nomeados e não com mensagens. Neste tutorial, você saberá o que é o REST e como construir aplicativos RESTful com Restlets, um framework REST leve para aplicativos Java™ .

Objetivos

Este tutorial guia você passo a passo pelos conceitos fundamentais de REST e da construção de aplicativos com Restlets. Você aprenderá a:

  • Definir serviços da Web RESTful
  • Implementá-los com a estrutura do Restlet
  • Verificá-los com a estrutura de teste JUnit

Quando tiver concluído este tutorial, você entenderá os benefícios de se projetar com base nos princípios do RESTful e verá como a estrutura do Restlet facilita isso.

Pré-requisitos

Para se beneficiar deste tutorial, você deve estar familiarizado com a sintaxe Java e com os conceitos básicos do desenvolvimento orientado a objetos na plataforma Java. Você também deve estar familiarizado com aplicativos da Web. A familiaridade com Groovy, JUnit, DbUnit e XMLUnit também é útil.

Requisitos do Sistema

Para prosseguir e testar o código para este tutorial, você precisa de uma instalação funcional de:

Existem duas versões de código de origem para este tutorial (consulte Download). Uma versão inclui todo o código e as dependências necessárias (a estrutura do Restlet, JUnit, XMLUnit e DbUnit). Os leitores com uma conexão de baixa largura de banda podem preferir fazer o download do framework do Restlet, JUnit, XMLUnit e DbUnit através de seus respectivos sites (consulte Recursos) e utilize a versão do pacote Download que não inclui dependências.

A configuração do sistema recomendada para este tutorial é:

  • Um sistema que suporte o Sun JDK 1.5.0_09 (ou posterior) ou o IBM JDK 1.5.0 SR3 com pelo menos 500 MB de memória principal
  • Pelo menos 20 MB de espaço em disco para instalar os componentes de software e exemplos abordados

As instruções e os exemplos no tutorial são baseados em um sistema operacional Microsoft® Windows®. Todas as ferramentas cobertas no tutorial também operam em sistemas Linux® e UNIX®.


O que é REST?

REST é um estilo de se projetar aplicativos da Web fracamente acoplados que contam com recursos nomeados — em forma de Localizador Uniforme de Recursos (URL), Identificador Uniforme de Recursos (URI) e Nome de Recurso Uniforme (URN), por exemplo — e não com mensagens. Engenhosamente, o REST transporta a infraestrutura já validada e bem-sucedida da Web — HTTP. Ou seja, o REST alavanca aspectos do protocolo HTTP como pedidos GET e POST. Esses pedidos são perfeitamente mapeados para necessidades de aplicativo de negócios padrão, como create read, update, and delete (CRUD), conforme mostrado na Tabela 1:

Tabela 1. Mapeamento CRUD/HTTP
Tarefa do aplicativoComando HTTP
CreatePOST
ReadGET
UpdatePUT
DeleteDELETE

Ao associar pedidos, que agem como verbos, com recursos, que agem como nomes, você termina com uma expressão lógica de comportamento — GET este documento eDELETE aquele registro, por exemplo.

Roy Fielding, o verdadeiro pai do REST, afirma em sua dissertação de PhD que o REST "enfatiza a escalabilidade das interações do componente, a generalidade das interfaces, a implementação independente de componentes e componentes intermediários para reduzir a latência da interação, forçar a segurança e encapsular sistemas legados" (consulte Recursos). A construção de sistemas RESTful não é difícil, e os sistemas são altamente escaláveis, enquanto são fracamente acoplados aos dados subjacentes; eles também alavancam perfeitamente o armazenamento em cache.

Tudo na Web (páginas, imagens, entre outras coisas) são a essência de um recurso. O fato de o REST contar com recursos nomeados em vez de mensagens facilita o fraco acoplamento no design do aplicativo, pois isso limita a exposição da tecnologia subjacente. Por exemplo, a URL a seguir expõe um recurso sem envolver absolutamente nada da tecnologia subjacente http://thediscoblog.com/2008/03/20/unambiguously-analyzing-metrics/

Esta URL representa um recurso — um artigo chamado "Analisando Métricas sem Ambiguidade." Um pedido deste recurso alavanca o comando HTTP GET. Observe que a URL é baseada em substantivo. Uma versão baseada em verbo (que pode parecer com http://thediscoblog.com/2008/03/20/getArticle?name=unambiguously-analyzing-metrics) violaria os princípios do REST, pois ele contém uma mensagem embutida em forma de getArticle. Você poderia imaginar a postagem de um novo recurso (quer dizer, um recurso de artigo como http://thediscoblog.com/2008/03/22/rest-is-good-for-you/) via o comando HTTP POST. Embora você também possa imaginar APIs baseadas em verbo associadas — como createArticle?name=rest-is-good-for-you e deleteArticle?name=rest-is-good-for-you — essas chamadas interceptam o comando HTTP GET e, em sua maior parte, ignoram a infraestrutura HTTP disponível (e bem-sucedida). Em outras palavras, elas não são RESTful.

O melhor do REST é que os recursos podem ser tudo, e a forma como eles são representados também pode variar. No exemplo anterior, o recurso é um arquivo HTML; portanto, o formato da resposta seria HTML. Mas o recurso poderia ser facilmente um documento XML, um objeto serializado ou uma representação JSON. Isso não importa. O que importa é que um recurso é nomeado e que a comunicação com ele não afeta seu estado. Não afetar o estado é algo importante, pois interações sem preservação de estado facilitam a escalabilidade.

Por que se Preocupar?

Para citar Leonardo da Vinci, "simplicidade é o máximo da sofisticação." A implementação da World Wide Web é tão simples quanto parece e seu sucesso é incontestável. O REST alavanca a simplicidade da Web e gera sistemas fracamente acoplados altamente escaláveis que são simples de construir.

Como você verá, a parte mais difícil da construção de um aplicativo RESTful é decidir sobre os recursos que você quer expor. Depois disso, o uso da estrutura do Restlet transforma a construção de serviços da Web RESTful em uma tarefa muito fácil.


Mãos à obra: Construindo uma API do RESTful

Nesta seção, você construirá uma API do RESTful para um serviço da Web que alavanca a funcionalidade de um aplicativo suportado por banco de dados existente.

A Corrida do RESTful

Imagine um aplicativo on-line que gerencia corridas em que os competidores correm várias distâncias (como a Maratona de Chicago). O aplicativo gerencia corridas (ou eventos) e os corredores associados a elas. Ele relata o tempo de um determinado corredor (quanto tempo ele levou para fazer a corrida) e sua classificação (em que lugar o corredor terminou). A empresa de gerenciamento de corridas, a Acme Racing, quer que você construa um serviço da Web RESTful que permita que patrocinadores criem novas corridas e corredores para uma determinada corrida e que possa fornecer resultados oficiais para uma corrida específica.

A Acme Racing já possui um aplicativo cliente fat legado que suporta requisitos semelhantes e que alavanca um banco de dados simples junto com um modelo de domínio. Além disso, a tarefa de exposição dessa funcionalidade é tudo que resta a fazer. Lembre-se de que o melhor do REST é seu fraco acoplamento implícito com um aplicativo subjacente. Portanto, sua tarefa, no momento, não é se preocupar com o modelo de dados ou com a tecnologia associada a ele — é construir uma API do RESTful que suporte os requisitos da empresa.

URIs da Corrida

A Acme Races gostaria que os patrocinadores estivessem preparados para:

  • Visualizar os detalhes de corridas existentes
  • Criar novas corridas
  • Atualizar corridas existentes
  • Excluir corridas

Como o REST se resume a recursos nomeados, a API se torna uma série de padrões de URI, e o comportamento associado a um recurso é chamado via comandos HTTP padrão.

Como você pode ver, os requisitos do cliente são perfeitamente mapeados para CRUD. E como você viu na Tabela 1, o REST suporta CRUD via os pedidos de HTTP POST, GET, PUT e DELETE, respectivamente. Portanto, um URI RESTful de base que suporta esses requisitos poderia ser http://racing.acme.com/race. Observe que nesse caso, a corrida é o recurso com o qual os clientes iriam trabalhar.

A chamada deste URI com um HTTP GET retornaria uma lista de corridas. (Não se preocupe ainda com o formato da resposta.) Para incluir uma nova corrida, você chamaria o mesmo URI com um HTTP POST contendo as informações apropriadas (por exemplo, um documento XML contendo as informações necessárias sobre a corrida, como nome, data e distância).

Para atualizar e excluir corridas existentes, você precisaria agir sobre uma determinada instância de uma corrida. Portanto, corridas individuais podem ser endereçadas com um URI de http://racing.acme.com/race/race_id. Nesse caso, race_id representa um sinalizador de substituição para qualquer identificador de corrida (como 1 ou 600 metros). Consequentemente, a visualização de uma instância de corrida existente seria um HTTP GET para esse URI; a atualização ou a exclusão de uma corrida seria um pedido PUT ou DELETE, respectivamente.

A Acme Racing também quer expor dados referentes aos corredores associados a uma corrida. Ela gostaria que seu serviço suportasse:

  • A obtenção de todos os corredores para uma determinada corrida. Esses dados também incluem tempo de corrida e classificação para uma corrida já concluída.
  • A criação de um ou mais corredores para uma determinada corrida.
  • A atualização das informações de um corredor (como idade) para uma determinada corrida.
  • A exclusão de um corredor para uma determinada corrida.

A Acme também gostaria que o serviço permitisse que os usuários visualizassem dados para um determinado corredor em uma determinada corrida.

Assim como com as corridas, a aplicação de URIs do RESTful para corredores associados a uma corrida é um exercício lógico. A visualização de todos os corredores para uma determinada corrida, por exemplo, seria implementada via um pedido GET para http://racing.acme.com/race/race_id/runner.

A obtenção de dados individuais para um corredor em uma corrida seria endereçada como http://racing.acme.com/race/race_id/runner/runner_id.

Assim como race_id, runner_id é um sinalizador de substituição para a implementação lógica de IDs, que poderiam ser números, nomes, combinações alfanuméricas e assim por diante.

A inclusão de corredores em uma corrida seria um pedido POST para http://racing.acme.com/race/race_id /runner. A atualização ou a exclusão de determinados corredores seria, respectivamente, os pedidos PUT e DELETE para http://racing.acme.com/race/race_id/runner/runner_id.

Assim, esses URIs (cada um suportando algum ou todos os pedidos de HTTP padrão) capturam os requisitos da Acme Racing:

  • /race
  • /race/race_id
  • /race/race_id/runner
  • /race/race_id/runner/runner_id

Lembre-se, um determinado URI pode ser mapeado para mais de um verbo HTTP (por exemplo, a aplicação de um HTTP GET em /race retorna dados; a aplicação de um POST com os dados apropriados cria dados no servidor). Portanto, alguns comandos HTTP não seriam implementados. Por exemplo, /race não suportaria o comando DELETE (a Acme Racing não iria querer excluir todas as corridas); /race/race_id poderia suportar o comando DELETE porque a remoção de uma determinada instância de uma corrida é um requisito de negócios.


Formatando o Recurso

Nesta seção, você vai construir uma série de documentos XML para representar os recursos que o serviço da Web de corridas do RESTful vai suportar.

URIs da Corrida

A API do RESTful que você construiu na seção anterior para a Acme Racing cobre os terminais de rede ou os URIs, mas não os recursos. No que diz respeito ao REST, o formato dos recursos não importa, como mencionei antes. Você poderia passar fluxos XML ou binários lado a lado, por exemplo.

XML é discutivelmente a lingua franca da comunicação de máquina com máquina no contexto de transações de negócios, portanto, faz sentido construir uma série de documentos XML que o serviço RESTful suportará. O domínio para a corrida é absolutamente simples, e você pode utilizar um modelo de dados existente para que a tarefa de definição de alguns documentos XML que representam as corridas e os corredores seja direta.

Por exemplo, uma corrida pode ser definida em XML como na Listagem 1:

Listagem 1. Documento XML para uma Corrida
<race name="Mclean 1/2 Marathon" date="2008-05-12" distance="13.1" id="1">
 <uri>/races/1</uri>
  <description/>
</race>

Observe que uma <corrida> possui um id e que a Listagem 1 inclui um URI como parte da definição da corrida. Esse é um aspecto fundamental do REST e, de fato, os recursos da Web — estão relacionados e devem ser vinculados. Portanto, uma <corrida> sempre contém um elemento <uri> descrevendo sua representação do RESTful. O XML na Listagem 1 é discutivelmente a resposta de um pedido GET para /races/1.

Para criar uma nova corrida, você poderia omitir o aspecto id (pois o gerenciamento de IDs exclusivos é algo que o aplicativo que você está construindo aqui controla). Isso indica que você poderia excluir o elemento <uri> também. Consequentemente, um pedido POST seria semelhante à Listagem 2:

Listagem 2. XML de Criação de Corrida
<race name="Limerick 2008 Half" date="2008-05-12" distance="13.4">
 <description>erin go braugh and have a good time!</description>
</race>

E quanto aos corredores? Um corredor está conectado à corrida, certo? Portanto, o elemento <race> pode conter um ou mais elementos <runner>, conforme mostrado na Listagem 3:

Listagem 3. Corredores Associados a uma Corrida
<race name="Limerick 200 Half" date="2008-05-12" distance="13.4" id="9">
 <uri>races/9</uri>
 <description>erin go braugh and have a good time!</description>
 <runners>
  <runner first_name="Linda" last_name="Smith" age="25" id="21">
   <uri>/races/9/runner/21</uri>
  </runner>
  <runner first_name="Andrew" last_name="Glover" age="22" id="20">
   <uri>/races/9/runner/20</uri>
  </runner>
 </runners>
</race>

O documento XML na Listagem 3, por exemplo, é o que seria retornado via o URI /race/race_id/runner. A API também suporta operações CRUD executadas em um único corredor via o URI /race/race_id/runner/runner_id.

Portanto, o XML para essas ações CRUD é semelhante à Listagem 4:

Listagem 4. XML CRUD
<race name="Mclean 1/2 Marathon" date="2008-05-12" distance="13.1" id="1">
 <uri>/races1</uri>
 <description />
 <runner first_name="Andrew" last_name="Glover" age="32" id="1">
  <uri>/races/1/runner/1</uri>
  <result time="100.04" place="45" />
 </runner>
</race>

Observe que se a corrida já estiver concluída, os resultados de um corredor poderão ser incluídos no documento XML. Lembre-se, o uso de um pedido POST significa a criação de um corredor; consequentemente, o atributo id do elemento <runner> não estaria presente.


Restlets

Você definiu uma API do RESTful que é perfeitamente mapeada para corridas e corredores de CRUD. E definiu o formato da comunicação: documentos XML. Nesta seção, você começará a reunir tudo isso utilizando uma estrutura inovadora que é modelada depois dos servlets.

A Estrutura do Restlet

Os aplicativos Restlet são semelhantes aos aplicativos servlet porque residem em um contêiner, mas, na prática, eles se diferem totalmente de duas maneiras. Primeiro, os Restlets utilizam noção não direta de HTTP ou suas manifestações com preservação de estado, como cookies ou sessões, per se. Segundo, a estrutura do Restlet é extremamente leve. Como você verá, um aplicativo RESTful totalmente funcional pode ser construído com várias classes que se estendem a partir de algumas classes de base Restlet principais. A configuração e a implementação alavancam modelos de contêiner existentes, portanto, basta você atualizar o arquivo web.xml usual e implementar um arquivo Web archive (WAR) padrão.

Na maioria das vezes, uma grande quantidade de aplicativos RESTful construída com a estrutura do Restlet requer o uso de duas classes de base: Aplicativo e Recurso. Logicamente falando, uma instância de Application mapeia URIs para instâncias de Recurso. Instâncias de Recurso fazem o trabalho de manipulação de comandos CRUD básicos, que são, é claro, mapeados para GET, POST, PUT e DELETE.

O Aplicativo de Corrida

Você cria um ponto de partida com o framework Restlet estendendo-se a partir da classe Aplicativo do framework. Nessa classe, você define Recursos que respondem a URIs. Este processo de definição é feito com a classe Roteador do framework. Por exemplo, se você tiver um URI como order/order_id, será necessário especificar qual objeto pode tratar desses pedidos. Esse objeto é uma instância do tipo Recurso do framework. Você vincula objetos com URIs anexado-os a uma instância de Roteador, como na Listagem 5:

Listagem 5. Criando URIs de Mapeamento e Instâncias do Roteador
Router router = new Router(this.getContext());
router.attach("order/{order_id}", Order.class);

Portanto, nesse exemplo, o URI order/order_id é mapeado logicamente para uma classe Ordem (que, por sua vez, estende Recurso).

A Acme Racing possui quatro URIs lógicos do RESTful que você já definiu — quatro padrões que trabalham com vários aspectos de corridas e corredores:

  • /race
  • /race/race_id
  • /race/race_id/runner
  • /race/race_id/runner/runner_id

O comportamento de cada URI (como se ele trabalhasse com POST, DELETE, GET, entre outros) não é importante neste ponto. O comportamento de cada Recurso é a tarefa de uma instância Recurso; entretanto, a instância Aplicativo é utilizada para mapear esses URIs para Recursos (ainda não definidos) via uma instância Roteador, conforme mostrado na Listagem 6:

Listagem 6. Mapeando URIs da Acme Racing para Recursos
public class RaceApplication extends Application{
 public RaceApplication(Context context) {
  super(context);
 }

 public Restlet createRoot() {
  Router router = new Router(this.getContext());
  router.attach("/race", RacesResource.class);
  router.attach("/race/{race_id}", RaceResource.class);
  router.attach("/race/{race_id}/runner", RaceRunnersResource.class);
  router.attach("/race/{race_id}/runner/{runner_id}", RaceRunnerResource.class);
  return router;
 }
}

A classe base, Aplicativo, é uma classe abstrata. Classes de extensão devem implementar o método createRoot(). Neste método, você pode criar uma instância do Roteador e conectar Recursos a URIs, como fiz na Listagem 6.

Como você pode ver, existem quatro diferentes classes de Recurso. Eu as nomeei para que elas correspondam ao alto nível de comportamento desejado do URI. Por exemplo, o URI /race deve trabalhar com várias instâncias de corrida; consequentemente, tipo de corrida Recurso é nomeado RacesResource. Após um id ser incluído no URI (/race/race_id), a conclusão é de que uma única corrida está sendo manipulada; portanto, o tipo Recurso é nomeado RaceResource.

Recursos da Corrida

Agora que você definiu a instância Aplicativo para tratar de quatro diferentes padrões de URI, você deve implementar os quatro Recursos.

Os tipos de Recurso na estrutura do Restlet que são conhecidos como Restlets. Eles são o coração de qualquer aplicativo RESTful desenvolvido com a estrutura do Restlet. Ao contrário do tipo Aplicativo, a classe base Recurso não é abstrata. Ela se parece mais com o comportamento padrão que você pode substituir conforme for necessário.

Em nível superior, Recurso possui quatro métodos que requerem substituição. Não simultaneamente, eles são mapeados para os comandos HTTP básicos que são um critério do REST — GET, POST, PUT e DELETE. Como a classe Recurso não é abstrata, a estrutura requer um que par de métodos seja implementado para que o comportamento desejado seja chamado. Por exemplo, se quiser que um determinado recurso responda a pedidos DELETE, primeiro você teria que implementar o método delete(). Segundo, você também deveria implementar o método allowDelete() e fazê-lo retornar true (ele é padronizado como false). Por padrão, PUT, POST e DELETE correspondentes permitem que os métodos retornem falsee o método allowGet() retorna true. Isso significa que para Recursos somente de leitura, você precisa substituir apenas um método (em vez de dois nos outros três casos). Como alternativa, você pode chamar setModifcation(true) em uma classe Recurso e não precisa substituir métodos allow do verbo HTTP individual.

Por exemplo, RacesResource destina-se a responder a pedidos GET com um documento XML que descreve as corridas no sistema. Os usuários também podem criar novas corridas via este tipo de Recurso. Portanto, a classe RacesResource substitui pelo menos três métodos da classe base Recurso:

  • getRepresentation()
  • allowPost()
  • post()

Lembre-se, instâncias de Recursos, por padrão, são somente leitura. Consequentemente, o método allowGet() não precisa ser substituído.


Gerando Documentos XML

Em Formatando o Recurso, decidimos alavancar o XML como o mecanismo de dados para o compartilhamento de informações entre clientes e o serviço. Seus Restlets, portanto, devem manipular o XML: construa-o no caso de um GET e consuma-o no caso de um POST, PUT ou DELETE. Nesta seção, você conhecerá os esforços para se gerar e manipular documentos XML, alavancando a linguagem de script Groovy (consulte Recursos).

Alavancando Groovy

O trabalho com XML não é uma tarefa fácil. Ele pode ser, no mínimo, entediante e passível de erro. Felizmente, o Groovy torna o trabalho com XML muito mais fácil.

Você vai alavancar a força do Groovy para gerar XML e realizar a tarefa entediante de manipular documentos XML. O trabalho com XML no Groovy não poderia ser mais fácil. Por exemplo, a análise de um documento XML é muito fácil. Pegue o documento XML na Listagem 7:

Listagem 7. Um Documento XML Simples para Análise
<acme-races>
  <race name="Alaska 200 below" date="Thu Jan 01" distance="3.2" id="20">
    <uri>/races/20</uri>
    <description>Enjoy the cold!</description>
  </race>
</acme-races>

Suponha que você queira capturar o valor do atributo name elemento <race>. Tudo que você precisa é passar uma instância do documento XML para a classe XMLSlurper do Groovy, chamar o método parse() e navegar para o elemento ou atributo desejados, conforme mostrado na Listagem 8:

Listagem 8. Analisando XML no Groovy
def root = new XmlSlurper().parseText(raceXML)
def name = root.race.@name.text()

Se quiser a descrição, é tão fácil quanto chamar root.race.description.text().

Criar um XML também é muito fácil. Se você queria criar o snippet XML na Listagem 7, tudo que você tem que fazer é criar uma instância da classe MarkupBuilder do Groovy e incluir nós nela, como na Listagem 9:

Listagem 9. Criar um XML Não Poderia Ser Mais Fácil
def writer = new StringWriter()
def builder = new MarkupBuilder(writer)
builder."acme-races"() {
    race(name: "Alaska 200 below",  date: "Thu Jan 01", distance: "3.2", id: "20") {
        uri("/races/20")
        description("Enjoy the cold!")
    }
}
println writer.toString()

Observe como os elementos são incluídos no documento XML anexando-se nomes à instância construtor. Tive que colocar aspas ao redor de acme-races porque hifens não são permitidos em cadeias literais no Groovy; consequentenente, transformar acme-races em uma String resolve perfeitamente esse problema.

Elementos podem ter atributos. Nomes de atributos e valores são criados através da construção de um mapa Groovy, que vincula os dois (por exemplo, name:"Alaska 200 below").


A Camada de Dados

Esta seção descreve os objetos de domínio existentes que compõem a camada de dados que o serviço RESTful vai reutilizar.

Objetos de Domínio

Como você viu em Mãos à obra: Construindo uma API do RESTful, a Acme Racing investiu em uma camada de dados para um projeto anterior e quer reutilizá-la para o novo serviço da Web. Isso, é claro, facilita ainda mais seu trabalho. Em poucas palavras, a camada de dados consiste em três objetos de negócios: Corrida, Corredor e Resultado. Eles são efetivamente gerenciados por Spring e Hibernate; entretanto, esses frameworks ficam escondidos de você; você só tem um arquivo JAR que funciona perfeitamente (ou seja, permite que você crie novas corridas, localize corredores existentes, entre outras coisas).

Os objetos de negócios suportam uma série de métodos localizadores que tornam a obtenção de instâncias de corrida e de corredor totalmente fácil. Objetos podem ser persistidos, atualizados e removidos do banco de dados subjacente via os métodos save(), update() e remove(), respectivamente.

Por exemplo, um objeto Corrida suporta uma série de métodos localizadores e facilita a manipulação de dados que persistiram para que ela seja realizada perfeitamente. A API do objeto Corrida é direta, conforme mostrado na Listagem 10:

Listagem 10. API de Corrida
Collection<Race> findAll();
Race findById(long id);
Race findByName(String name);
void create(Race race);
void update(Race race);
void remove(Race race);

Uma instância Corrida possui inúmeras propriedades, conforme mostrado na Listagem 11:

Listagem 11. Propriedades de Corrida
private long id;
private String name;
private Date date;
private double distance;
private Set<Runner> participants;
private Set<Result> results;
private String description;

Todas as propriedades de Corrida estão disponíveis via getters e setters. Além disso, as coletas de itens (como participantes e resultados) suportam a inclusão de itens individuais. Portanto, o objeto Corrida possui um método addParticipant(), mostrado na Listagem 12:

Listagem 12. Método addParticipant() de Corrida
public void addParticipant(final Runner participant) ;

Como você viu, o trabalho com este modelo de domínio é muito fácil.


Construindo e Testando o Serviço

Agora que você já sabe como vai trabalhar com XML e já possui uma camada de dados para utilizar, é hora de continuar construindo seu aplicativo RESTful com Restlets e fazer a preparação de alguns testes.

O Serviço Corridas

Lembre-se de que a Acme Racing gostaria que seus serviços permitissem que os clientes visualizassem corridas existentes, bem como criassem algumas novas. Você já esboçou o URI do RESTful que vai suportar este comportamento: /race.

Via a classe Roteador na classe RaceApplication, você vinculou este URI à classe RacesResource. Você já sabe que deve implementar três métodos:

  • getRepresentation()
  • allowPost()
  • post()

Portanto, crie uma classe chamada RacesResource e certifique-se de que ela estenda org.restlet.resource.Resource. Além disso, implemente um construtor de três parâmetros, conforme mostrado na Listagem 13:

Listagem 13. Construtor de Três Parâmetros em RacesResource
public class RacesResource extends Resource {
 public RacesResource(Context context, Request request, Response response) {
  super(context, request, response);
 }
}

Restlets devem ser instruídos sobre como comunicar representações de recursos corretamente. Como o XML servirá de formato de recursos, você deve direcionar seu Restlet incluindo um tipo variante XML. Variantes, no Restlets, representam um formato para Recursos. A classe base, Recurso, contém um método getVariants() que facilita a inclusão de vários tipos Variantes. Portanto, inclua a linha na Listagem 14 em seu construtor:

Listagem 14. Representando XML como uma Variante
this.getVariants().add(new Variant(MediaType.TEXT_XML));

A estrutura do Restlet suporta uma grande variedade de tipos de mídia, incluindo imagens e vídeos.

Manipulando Pedidos GET

Agora é hora de implementar o comportamento mais fácil da classe: a manipulação de um pedido GET. Substitua o método getRepresentation() conforme mostrado na Listagem 15:

Listagem 15. Substituindo getRepresentation()
public Representation getRepresentation(Variant variant) {
 return null;
}

Como você pode ver, este método retorna um tipo de Representação, do qual existem várias implementações. Uma implementação — adequadamente dublada StringRepresentation — representa cadeias e atende às suas necessidades.

Como sabe, você já possui um modelo de domínio legado que suporta o trabalho com o banco de dados. Isso também significa que alguém já gravou uma classe do utilitário, chamada RaceReporter, que transforma objetos de domínio em documentos XML. O método racesToXml() desta classe faz uma coleta de instâncias Corrida e retorna uma String representando um documento XML semelhante à Listagem 16:

Listagem 16. Uma Resposta XML
<acme-races>
 <races>
  <race name="Leesburg 5K" date="2008-05-12" distance="3.1" id="5">
  <uri>/races/5</uri>
  <description/>
 </race>
 <race name="Leesburg 10K" date="2008-07-30" distance="6.2" id="6">
  <uri>/races/6</uri>
  <description/>
 </race>
 </races>
</acme-races>

De fato, este documento XML é um exemplo do que o serviço da Web RESTful retornará quando o URI /race for chamado com um pedido GET.

Portanto, seu trabalho é vincular a recuperação de todas as instâncias de corrida no armazém de dados subjacente; de fato, neste ponto, você já pode gravar um teste.

Testando o Serviço

Utilizando um framework Restlet, você pode construir uma instância do cliente e fazê-la chamar seu serviço da Web RESTful. Além disso, você pode alavancar XMLUnit (consulte Recursos) para verificar se a saída do serviço é algum documento XML conhecido. Por fim, mas não menos importante, você também pode utilizar DbUnit (consulte Recursos) para colocar o banco de dados subjacente em um estado conhecido (para que você possa voltar sempre para o mesmo documento XML).

Utilizando JUnit 4, você pode criar dois pertences que inicializem corretamente XMLUnit e DbUnit, conforme mostrado na Listagem 17:

Listagem 17. Configurando XMLUnit e DbUnit
@Before
public void setUpXMLUnit() {
 XMLUnit.setControlParser(
  "org.apache.xerces.jaxp.DocumentBuilderFactoryImpl");
 XMLUnit.setTestParser(
  "org.apache.xerces.jaxp.DocumentBuilderFactoryImpl");
 XMLUnit.setSAXParserFactory(
  "org.apache.xerces.jaxp.SAXParserFactoryImpl");
 XMLUnit.setIgnoreWhitespace(true);
}

@Before
public void setUpDbUnit() throws Exception {
 Class.forName("org.hsqldb.jdbcDriver");
 IDatabaseConnection conn =
  new DatabaseConnection(
   getConnection("jdbc:hsqldb:hsql://127.0.0.1", "sa", ""));
 IDataSet data = new FlatXmlDataSet(new File("etc/database/race-db.xml"));
 try {
  DatabaseOperation.CLEAN_INSERT.execute(conn, data);
 } finally {
  conn.close();
 }
}

No método setUpDbUnit, uma representação XML do banco de dados é inserida no banco de dados via o comando CLEAN_INSERT. Este arquivo XML insere efetivamente seis corridas diferentes. Portanto, a resposta para um GET será um documento XML com seis corridas.

Em seguida, você pode criar uma etapa de teste que chame um HTTP GET no URI /race, obtenha o XML da resposta e a compare com um arquivo XML de controle utilizando a classe Diff do XMLUnit, conforme demonstrado na Listagem 18:

Listagem 18. Verificando uma Resposta GET com XMLUnit
@Test
public void getRaces() throws Exception {
 Client client = new Client(Protocol.HTTP);
 Response response =
  client.get("http://localhost:8080/racerrest/race/");

 Diff diff = new Diff(new FileReader(
  new File("./etc/control-xml/control-web-races.xml")),
   new StringReader(response.getEntity().getText()));
 assertTrue(diff.toString(), diff.identical());
}

O arquivo control-web-races.xml é a resposta XML esperada do serviço da Web. Ele contém os dados mostrados na Listagem 19:

Listagem 19. Um Arquivo XML de Controle
<acme-races>
 <races>
  <race name="Mclean 1/2 Marathon" date="2008-05-12" distance="13.1" id="1">
   <uri>http://localhost:8080/races/1</uri>
   <description/>
  </race>
  <race name="Reston 5K" date="2008-09-13" distance="3.1" id="2">
   <uri>http://localhost:8080/races/2</uri>
   <description/>
  </race>
  <race name="Herndon 10K" date="2008-10-22" distance="6.2" id="3">
   <uri>http://localhost:8080/races/3</uri>
   <description/>
  </race>
  <race name="Leesburg 1/2 Marathon" date="2008-01-02" distance="13.1" id="4">
   <uri>http://localhost:8080/races/4</uri>
   <description/>
  </race>
  <race name="Leesburg 5K" date="2008-05-12" distance="3.1" id="5">
   <uri>http://localhost:8080/races/5</uri>
   <description/>
  </race>
  <race name="Leesburg 10K" date="2008-07-30" distance="6.2" id="6">
   <uri>http://localhost:8080/races/6</uri>
   <description/>
  </race>
 </races>
</acme-races>

É claro que a execução deste teste agora causa uma série de falhas, pois você ainda não implementou o serviço RESTful. Observe também que o arquivo de compilação Ant incluído no download de origem contém tarefas para a implementação de um arquivo WAR e para iniciar e parar um Tomcat (consulte Download). Existem pré-requisitos para a execução de um teste bem-sucedido.

Isso significa que atender a um pedido GET é muito fácil. Tudo que você precisa fazer é chamar o método findAll no objeto de domínio Corrida e, em seguida, passar o resultado da chamada para o método racesToXml() de RaceReporter. Portanto, você precisa atualizar a instância RacesResource com uma nova variável de membro junto com uma nova inicialização no construtor, conforme mostrado na Listagem 20:

Listagem 20. Não se Esqueça de Incluir RaceReporter
public class RacesResource extends Resource {
 private RaceReporter reporter;

 public RacesResource(Context context, Request request, Response response) {
  super(context, request, response);
  this.getVariants().add(new Variant(MediaType.TEXT_XML));
  this.reporter = new RaceReporter();
 }
}

Agora ficou muito fácil concluir a implementação do pedido GET. Basta incluir três linhas no método getRepresentation, conforme mostrado na Listagem 21:

Listagem 21. Concluindo o Pedido GET
public Representation getRepresentation(Variant variant) {
 Collection<Race> races = Race.findAll();
 String xml = this.reporter.racesToXml(races);
 return new StringRepresentation(xml);
}

Acredite ou não, é só isso!

Mas espere: você não tem que implementar este aplicativo para testá-lo?


Implementação e Verificação

Antes de poder testar de fato seu serviço RESTful que retorna uma lista de corridas, você precisa implementar o aplicativo. Esta seção mostra como fazer isso.

Configurando web.xml

Felizmente, a implementação de um aplicativo Restlet não poderia ser mais fácil. Basta você criar um arquivo WAR normal e se certificar de que o arquivo web.xml seja configurado corretamente.

Para que um aplicativo Restlet funcione de forma adequada em um contêiner do servlet, você deve atualizar o arquivo web.xml para:

  • Carregar corretamente seu aplicativo
  • Rotear todos os requisitos através do servlet customizado da estrutura

Consequentemente, seu arquivo web.xml deve ser semelhante à Listagem 22:

Listagem 22. Arquivo web.xml de Amostra
<?xml version="1.0" encoding="UTF-8"?>
<web-app id="WebApp_ID" version="2.4"
 xmlns="http://java.sun.com/xml/ns/j2ee"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
   http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
 <display-name>RESTful racing</display-name>
 <context-param>
  <param-name>org.restlet.application</param-name>
  <param-value>RaceApplication</param-value>
 </context-param>
 <servlet>
  <servlet-name>RestletServlet</servlet-name>
  <servlet-class>com.noelios.restlet.ext.servlet.ServerServlet</servlet-class>
 </servlet>
 <servlet-mapping>
  <servlet-name>RestletServlet</servlet-name>
  <url-pattern>/*</url-pattern>
 </servlet-mapping>
</web-app>

O código fonte para este tutorial (consulte Download) inclui uma tarefa war que constrói automaticamente um arquivo WAR, e o arquivo de compilação suporta a implementação do arquivo WAR em uma instância local do Tomcat.

Como você pode ver, na primeira parte da Listagem 22, org.restlet.application faz par com o nome de classe do aplicativo Restlet, que é RaceApplication. (Talvez você precise qualificar esse nome se você deu a ele um nome de pacote.) Observe também que a última seção do documento mapeia todos os pedidos para o tipo RestletServlet, que foi mapeado anteriormente para a classe com.noelios.restlet.ext.servlet.ServerServlet.

Testando "RESTfully"

O teste do serviço da Web RESTful agora significa reexecutar as etapas de teste na Listagem 18.

Dê uma outra olhada no teste para obter algumas explicações. O objeto Cliente do Restlet suporta os comandos HTTP básicos de GET, PUT, POSTe DELETE. E o objeto Cliente pode ter a forma de protocolos diferentes — você pode estar contando com um HTTP nesse caso.

Seu pedido GET já está funcionando (consulte a Figura 1), portanto você pode gravar outro teste. Desta vez, dê mais detalhes sobre o comportamento desejado de um POST; ou seja, teste a criação de uma nova corrida via a classe RacesResource.

Figura 1. Visualizando um Pedido GET do RESTful em um Navegador
Visualizando um Pedido GET do RESTful em um Navegador

Para testar um POST, você precisa formar um documento de pedido XML com as informações relevantes e garantir que o serviço envie de volta uma resposta bem-sucedida. É claro que isso significa que gravar este teste é absolutamente simples. Tudo que você precisa fazer é incluir certo código adicional na classe JUnit existente, conforme mostrado na Listagem 23:

Listagem 23. A Etapa de Teste createRace
private static String raceXML = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" +
 "<acme-races>\n" +
 " <race name='Limerick 2008 Half' date='2008-05-12' distance='13.4'>\n" +
 " <description>erin go brach</description>\n" +
 " </race>\n" +
 "</acme-races>";

@Test
public void createRace() {
 Form form = new Form();
 form.add("data", this.raceXML);
 Representation rep = form.getWebRepresentation();
 Client client = new Client(Protocol.HTTP);

 Response response =
  client.post("http://localhost:8080/racerrest/race/", rep);
 assertTrue(response.getStatus().isSuccess());

Como você pode ver, a Listagem 23 cria rapidamente uma String que representa um documento XML. Nesse caso, estou criando uma nova corrida chamada Limerick 2008 Half. Depois, ela utiliza o objeto Cliente da estrutura do Restlet para postar este documento para o servidor. Por fim, ela garante que uma indicação de sucesso seja retornada.

Agora execute o teste. Ele falhou, não é? Isso aconteceu porque você não implementou o código do pedido POST, o que será feito na próxima seção.


Criação de Corrida "RESTfully"

A criação de corridas via o serviço da Web RESTful é uma questão que envolve algumas etapas: receber um documento XML, analisá-lo, criar uma nova instância Corrida no banco de dados subjacente e, finalmente, retornar uma resposta indicando o resultado da transação. Esta seção cobre estas etapas.

Manipulando Pedidos POST

Para implementar um comportamento de estilo de criação via REST, você precisa manipular pedidos POST logicamente. Portanto, na classe RacesResource, você deve substituir dois métodos: allowPost() e post().

O método post() faz todo o trabalho aqui. Ele utiliza uma instância Representação, da qual é possível obter dados postados. Lembre-se de que etapas de teste createRace na Listagem 23 associaram o documento XML a um nome: data. Consequentemente, através do Formulário do framework Restlet, você pode obter uma String representando o XML recebido, que pode então ser passado para o objeto RaceConsumer fornecido. Este objeto é conveniente o bastante para aceitar documentos XML e, de maneira correspondente, manipular o banco de dados subjacente.

Se a transação funcionar, você responderá de acordo com a resposta bem-sucedida; caso contrário, você precisará responder com uma mensagem de erro.

Prossiga e substitua allowPost() e post(), conforme mostrado na Listagem 24:

Listagem 24. Substituindo Métodos POST
public boolean allowPost() {
 return true;
}

public void post(Representation representation) {}

Como você vai utilizar o objeto RaceConsumer, faz sentido incluí-lo como uma variável de membro da classe RacesResource e inicializá-lo no construtor. Atualize o objeto conformemente, como mostra a Listagem 25:

Listagem 25. Incluindo a Classe RaceConsumer
public class RacesResource extends Resource {
 private RaceReporter reporter;
 private RaceConsumer consumer;

 public RacesResource(Context context, Request request, Response response) {
  super(context, request, response);
  this.getVariants().add(new Variant(MediaType.TEXT_XML));
  this.reporter = new RaceReporter();
  this.consumer = new RaceConsumer();
 }
}

Em seguida, certifique-se de que o método post() seja semelhante à Listagem 26:

Listagem 26. Implementando post()
public void post(Representation representation) {
 Form form = new Form(representation);
 String raceXML = form.getFirstValue("data");
 Representation rep = null;
 try {
  long id = this.consumer.createRace(raceXML);
  getResponse().setStatus(Status.SUCCESS_CREATED);
  rep = new StringRepresentation(raceXML, MediaType.TEXT_XML);
  rep.setIdentifier(getRequest().getResourceRef().getIdentifier() + id);
 } catch (Throwable thr) {
  getResponse().setStatus(Status.SERVER_ERROR_INTERNAL);
  rep = new StringRepresentation("there was an error creating the race",
    MediaType.TEXT_PLAIN);
 }
 getResponse().setEntity(rep);
}

Como você pode ver, acontece muita coisa no método post; entretanto, mediante uma inspeção de fechamento, as coisas não são tão racionais quanto parecem. Primeiro, o XML recebido é obtido via o objeto Formulário. O XML (em forma de uma String) é passado para o método createRace() da instância consumidor. Se as coisas tiverem funcionado (ou seja, se a corrida persistiu), será gerada uma resposta contendo um status bem-sucedido e, depois, um reformulação do XML recebido, mais o URI resultante (ou seja, race/43, em que 43 é o id da corrida recém-criada).

Se as coisas não foram bem, o mesmo processo será seguido, exceto que um status de falha será retornado com uma mensagem de erro: nenhum URI foi retornado porque nada foi criado.

Prossiga e reexecute o teste createRace. Supondo que você reimplementou o aplicativo da Web RESTful, as coisas deverão funcionar perfeitamente!


Um ponto "RESTed"

Este tutorial gerenciou a implementação de apenas um pequeno número de requisitos da Acme Racing. Mas nesse processo, você viu que o trabalho com o Restlets é totalmente simples. A parte mais difícil do exercício inteiro foi descobrir a API lógica do RESTful. O código fonte para este tutorial possui todos os recursos implementados para sua satisfação com o aprendizado (consulte Download).

O poeta Alexander Pope disse "Há uma certa grandiosidade na simplicidade que está muito acima da singularidade da razão." Isso não poderia ser mais verdadeiro quando se trata do REST. Lembre-se, o REST é uma forma de se pensar — um estilo de se projetar aplicativos fracamente acoplados que contam com recursos nomeados, e não com mensagens. E ao transportar a infraestrutura já validada e bem-sucedida da Web, o REST torna esses aplicativos simples de se projetar e implementar. E os aplicativos REST são perfeitamente escalados.

Este tutorial cobriu um punhado de recursos do framework Restlet, mas não se engane com isso. O framework oferece muita coisa, incluindo a adição de segurança quando necessário. É um prazer codificar Restlets, e o código base é fácil de se entender após alguns Restlets serem codificados.

Albert Einstein disse "Tudo deveria ser tornado tão simples quanto possível. Mas não mais simples do que isso." Espero que você concorde comigo que o framework Restlet e o REST em si exemplificam a sabedoria deste mantra.


Downloads

DescriçãoNomeTamanho
Sample code with dependent librariesj-rest.zip19.5MB
Sample code without dependent librariesj-rest2.zip19KB

Recursos

Aprender

  • Estilos de Arquitetura e o Design de Arquiteturas de Software Baseadas em Rede (Roy Thomas Fielding, Universidade da Califórnia em Irvine, 2000): Dissertação de doutorado de Fielding descrevendo o REST.
  • Restlet: Visite o Web site da estrutura do Restlet.
  • Groovy: Visite o Web site do Groovy.
  • "Serviços da Web orientados a recursos versus orientados a atividades" (James Snell, developerWorks, outubro de 2004): Dê uma rápida olhada no relacionamento entre os serviços da Web estilo REST e estilo SOAP.
  • "Grave serviços REST" (J. Jeffrey Hanson, developerWorks, outubro de 2007): Trabalhe neste tutorial para criar serviços REST com tecnologia Java e com o Atom Publishing Protocol.
  • "Cruzando fronteiras: REST nos Trilhos" (Bruce Tate, developerWorks, agosto de 2006): Leia sobre a construção de aplicativos RESTful com uma estrutura de desenvolvimento de aplicativo da Web popular não-Java.
  • "Groovy com facilidade" (Andrew Glover, developerWorks, março de 2008): Introdução ao Groovy. Aprenda sobre os recursos de sintaxe e produtividade do Groovy, como coletas nativas, expressões regulares integradas e encerramentos. Grave sua primeira classe Groovy e teste-a utilizando código Java puro.
  • "Mergulhe no JUnit 4" (Andrew Glover, developerWorks, fevereiro de 2007): Este tutorial mostra como alavancar os novos recursos no JUnit 4 ativado por anotações, incluindo testes de parâmetros, testes de exceção e testes de tempo.
  • "Descubra XMLUnit" (Andrew Glover, developerWorks, dezembro de 2006): Desenvolvedores são solucionadores naturais de problemas, portanto, faz sentido alguém ter trazido à tona uma maneira mais fácil de se validar documentos XML. Este artigo introduz XMLUnit, uma estrutura de extensão JUnit que atende a todas as suas necessidades de validação de XML.
  • "Faça marcações com o Groovy Builders" (Andrew Glover, developerWorks, abril de 2005): O Groovy Builders permite imitar linguagens de marcação como XML, HTML, tarefas Ant e até GUIs com frameworks como Swing. Elas são úteis principalmente para a rápida criação de protótipos e, como mostra este artigo, são uma alternativa acessível para frameworks de ligação de dados quando você precisa de marcações consumíveis com muita facilidade!
  • "Teste de Unidade Efetivo com DbUnit" (Andrew Glover, OnJava, janeiro de 2004): A gravação de testes de unidade pode ser pouco prática quando seu código depende de acesso ao banco de dados. Conheça o DbUnit, que permite gravar arquivos XML simples para preencher um banco de dados que ainda não foi preenchido para fins de teste.
  • Navegue pela livraria tecnológica para localizar livros sobre esses e outros tópicos técnicos.
  • Zona de tecnologia Java do developerWorks: Localize centenas de artigos sobre cada aspecto da programação Java.

Obter produtos e tecnologias

Discutir

Comentários

developerWorks: Conecte-se

Los campos obligatorios están marcados con un asterisco (*).


Precisa de um ID IBM?
Esqueceu seu ID IBM?


Esqueceu sua senha?
Alterar sua senha

Ao clicar em Enviar, você concorda com os termos e condições do developerWorks.

 


A primeira vez que você entrar no developerWorks, um perfil é criado para você. Informações no seu perfil (seu nome, país / região, e nome da empresa) é apresentado ao público e vai acompanhar qualquer conteúdo que você postar, a menos que você opte por esconder o nome da empresa. Você pode atualizar sua conta IBM a qualquer momento.

Todas as informações enviadas são seguras.

Elija su nombre para mostrar



Ao se conectar ao developerWorks pela primeira vez, é criado um perfil para você e é necessário selecionar um nome de exibição. O nome de exibição acompanhará o conteúdo que você postar no developerWorks.

Escolha um nome de exibição de 3 - 31 caracteres. Seu nome de exibição deve ser exclusivo na comunidade do developerWorks e não deve ser o seu endereço de email por motivo de privacidade.

Los campos obligatorios están marcados con un asterisco (*).

(Escolha um nome de exibição de 3 - 31 caracteres.)

Ao clicar em Enviar, você concorda com os termos e condições do developerWorks.

 


Todas as informações enviadas são seguras.


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=80
Zone=Linux, Software livre
ArticleID=386693
ArticleTitle=Construa um Serviço da Web RESTful
publish-date=07222008