Desenvolvimento - Java

Web Services REST

O objetivo deste artigo é partir de um exemplo rico o suficiente de arquitetura orientada a serviços e então fazer a modelagem e desenvolvimento dos mesmos com uso de uma abordagem RESTful.

por Bruno Luiz Pereira da Silva





Introdução

Este artigo fala sobre web services REST. Esta linha de web services vem se tornando cada vez mais popular e é uma excelente opção para integração este sistemas de diferentes plataformas.

Neste artigo abordarei os principais conceitos deste tipo de serviços, utilizarei um exemplo de leilão do Mercado Livre para ilustrar o problema, e mostrarei detalhes da arquitetura e implementação.

Definição

Surgimento

Roy Fielding é um dos principais autores do protocolo HTTP, e ele propôs em sua tese de doutorado um estilo de arquitetura que faz extenso uso dos recursos oferecidos por este protocolo.

Enquanto nos serviços WS-I os recursos do HTTP são muito pouco explorados (inclusive porque o SOAP é independente de transporte), nos serviços REST umas das principais características é a utilização de muitos recursos do HTTP para elaborar um protocolo de comunicação conciso e claro.

REST ? TCP/IP - WS-I ? OSI

WS-I:

  • Muitas especificações antes das implementações
  • Muitos documentos e complexidade para definir as implementações
  • Modelo semelhante a waterfall/cascata

REST:

  • Conjunto de regras simples
  • Especificações criadas após uso maduro
  • Especificações por grupos de estudo do IETF
  • Modelo incremental de desenvolvimento dos padrões/boas práticas

Motivação

Por que implementar serviços REST?

  • Protocolos menos complexos
  • Mais poder e flexibilidade nas comunicações
  • Arquitetura amplamente disponível nas empresas
  • Menos overhead de protocolo

Quando NÃO implementar serviços REST?

  • Integrações com produtos fechados WS-*
  • Quando WS-Transaction fizer sentido
  • Quando WS-Security fizer sentido
  • Quando não houver API HTTP razoável no servidor e/ou clientes-alvo


Arquitetura

A arquitetura dos web services WS-* se baseia em um protocolo bem definido, com regras precisas quanto ao formato dos dados trafegados e seguindo padrões acordados em consórcios de grandes corporações. Contrastando com isso, arquitetura dos web services REST é radicalmente diferente.

Poderíamos ilustrar as filosofias de projeto de serviços WS-* e REST com as sentenças a seguir.

WS-*: “Já temos o protocolo e os padrões, devemos definir os serviços que vamos oferecer e os documentos que desejamos trocar entre as partes.”

REST: “Vamos identificar os recursos envolvidos e utilizar extensamente os recursos do HTTP para definir um bom protocolo de interação com estes recursos.”

Estilos de acesso aos serviços

REST: Clientes interagem com os Recursos através de requisições HTTP GET, PUT, POST e DELETE

WS-*: Clientes invocam diferentes operações, com conjuntos variados de parâmetros de entrada e saída

Estilo Declarativo x Imperativo

A URI deve indicar o que você está manipulando e o método (ou verbo) HTTP indicará como você está manipulando. Neste exemplo, a URI /usuario/123456 nos indica que estamos manipulando um usuário específico. Sabendo que estamos usando o método HTTP GET, temos a clara indicação de que estamos buscando os dados deste usuário. Este estilo de invocação de serviços pode ser considerado Declarativo.

Em bons protocolos REST, a junção do método HTTP com a URI do recurso já nos indica na maioria dos casos qual é a operação sendo realizada. Assim, com um pequeno trecho do cabeçalho HTTP já somos capazes de compreender a comunicação.

Nos web services WS-*, a informação da operação que está sendo realizada fica encapsulada no corpo da requisição. Mesmo quando a camada de transporte das mensagens SOAP é HTTP, a URI não esclarece de forma alguma a operação envolvida. A informação dos serviços disponíveis fica descrita por elementos operation de um documento WSDL, geralmente em um formato fazerEssaOperacao. Esta maneira de desenvolver web services é classificada como Imperativa.



Modelagem dos serviços

Nesta seção falaremos da modelagem e desenvolvimento dos serviços utilizando REST. Para ilustrar bem os cenários abordados, trabalharemos em cima de um problema proposto: um processo de leilão do Mercado Livre.

Apresentação do problema

O problema que buscaremos resolver envolve serviços referentes a um processo de leilão do Mercado Livre. Num leilão típico, um usuário cadastrado no site coloca para venda um produto (novo ou usado), definindo um valor para o lance inicial e então aguarda pelas ofertas de compra por parte de outros usuários interessados no produto.

Após receber algumas ofertas pelo produto, em um determinado momento o vendedor decide aceitar a melhor oferta recebida, e então vende o produto para o comprador que fez esta oferta, encerrando neste momento o leilão. Em seguida à venda, ocorrem os trâmites de pagamento e entrega do produto (que não trataremos aqui) e no final, vendedor e comprador avaliam um ao outro, o que é essencial para os usuários sentirem maior segurança ao realizar negociações futuras deste gênero.

Na modelagem desta aplicação, quatro entidades serão utilizadas: Usuário, Item, Oferta e Avaliação.

Modelagem com Recursos

O ponto de partida do desenvolvimento com REST é definir quais são os recursos envolvidos, com base nos requisitos do sistema e nos serviços que se deseja oferecer. No nosso exemplo, esta etapa não é complexa. Os recursos que manipularemos são: Usuario, Item, Oferta e Avaliacao.

Embora esta identificação tenha sido trivial no domínio que definimos, em alguns casos este processo pode ser um dos mais complexos na modelagem da aplicação. De uma maneira geral, quanto mais a aplicação se aproxima de um CRUD, mais fácil é a identificação dos recursos.

Para este processo de leilão, os serviços que disponibilizaremos serão os da Tabela 1:

Serviço

Descrição

Anunciar item

Permite que um usuário coloque um produto à venda.

Buscar itens do vendedor

Pesquisa os itens à venda de um vendedor.

Cadastrar usuário

Realiza o cadastro de um novo usuário no site.

Realizar oferta

Permite que um comprador faça uma oferta por um produto.

Retirar oferta

Permite a remoção de uma oferta por parte do comprador.

Buscar ofertas do item

Pesquisa por todas as ofertas feitas sobre um produto.

Buscar melhor oferta

Busca a melhor oferta feita até o momento sobre um produto.

Aceitar melhor oferta

Permite que um vendedor aceite a melhor oferta feita sobre o seu produto, e com isso encerre o leilão do mesmo.

Avaliar usuário

Realiza a avaliação de um usuário por parte de outro usuário, após o término do processo de compra.

Buscar avaliações do usuário

Pesquisa por todas as avaliações recebidas por um usuário.
Tabela 1. Serviços oferecidos para interação com o processo de leilão.

Protocolo de comunicação REST

Tendo definido os recursos e os serviços que precisamos oferecer, é necessário definir as manipulações possíveis sobre os recursos existentes. Esta etapa é a tradução de operações de negócio em interações diretas sobre usuários, ítens, ofertas e avaliações. Esta tradução feita explorando os recursos do HTTP nos levará a um conjunto de URIs que a aplicação oferece para os clientes.

Para elaborar um bom protocolo de comunicação REST, devemos pensar em algumas questões importantes, como estas:

  • Quais são os recursos?
  • Quais são as URIs?
  • Quais são os formatos manipulados?
  • Que métodos HTTP são aceitos em cada URI?
  • Que status HTTP deve ser retornado em cada situação?
  • Que cabeçalhos HTTP são relevantes em cada situação?

A definição das URIs e dos métodos HTTP aceitos é primordial para um protocolo REST com clareza e de fácil utilização e extensão. Ao ler as URIs já devemos ser capazes de entender quais são os recursos presentes nas mesmas, e o casamento delas com os métodos HTTP deve ser intuitivo.

A Tabela 2 descreve todas as URIs disponíveis na aplicação e quais métodos HTTP podem ser invocados em cada uma delas. Além disso, são especificados os recursos manipulados por cada requisição e o efeito que uma determinada chamada exerce sobre os recursos existentes.

URI

Método

Formato

Efeito

/item/{id}

GET

Item

Busca um item.

PUT

Item

Atualiza um item.

/item/{id}/ofertas

GET

Coleção de ofertas

Busca ofertas feitas sobre um item.

POST

Oferta

Adiciona oferta a um item.

/oferta/{id}

GET

Oferta

Busca uma oferta.

PUT

Oferta

Atualiza uma oferta.

DELETE

-

Remove uma oferta.

/usuario

POST

Usuario

Cadastra um usuário.

/usuario/{id}

GET

Usuario

Busca um usuário.

PUT

Usuario

Atualiza um usuário.

/usuario/{id}/avaliacoes

GET

Coleção de avaliações

Busca as avaliações recebidas por um usuário.

/usuario/{id}/itens

GET

Coleção de ítens

Busca os ítens anunciados por um determinado usuário.

POST

Item

Usuário coloca novo item à venda.

/avaliacao/{id}

GET

Avaliação

Busca uma determinada avaliação.

/avaliacao/de/{id}/para/{id}

POST

Avaliação

Realização da avaliação de um usuário sobre outro.

/services

GET

Coleção de URIs

Consulta URIs e métodos HTTP disponíveis para acesso.
Tabela 2. URIs de acesso a recursos e métodos HTTP aceitos por cada uma delas

Implementação com a JAX-RS (Java API for RESTFul Web Services)

Para oferecer melhor suporte a serviços REST em Java, foi criada a JSR-311. Veja os objetivos desta JSR.

Com a JAX-RS, um recurso web é implementado como uma classe Recurso e as requisições são tratadas por métodos da mesma. Uma classe Recurso é simplesmente um POJO contendo anotações da JAX-RS para indicar os mapeamentos e operações existentes.

Ciclo de vida e ambiente

Por padrão, uma nova instância da classe Recurso é criada para cada requisição àquele Recurso. Inicialmente o construtor é invocado, dependências necessárias são injetadas, e então o método adequado é executado. Após estas etapas, o objeto fica disponível para o coletor de lixo.

As classes Recurso são instanciadas pelo runtime JAX-RS e devem possuir pelo menos um construtor público. Um construtor público pode incluir parâmetros com uma das seguintes anotações: @Context, @HeaderParam, @CookieParam, @MatrixParam, @QueryParam e @PathParam. Estas anotações realizam injeção de dependências relativas a serviços REST, e são apresentadas na Tabela 3.

Anotação

Descrição

@Context

Injeta uma instância de recursos como UriInfo, HttpHeaders, ServletConfig, ServletContext, HttpServletRequest e HttpServletResponse. Outros recursos de Java EE podem ser opcionalmente oferecidos por uma implementação desta JSR.

@HeaderParam

Extrai o valor de um cabeçalho da requisição HTTP.

@CookieParam

Extrai o valor de um cookie presente na requisição.

@MatrixParam

Extrai o valor de parâmetros enviados no formato chave=valor dentro de um segmento da URI. Exemplo: /usuário/123/itens;categoria=eletronicos;limitePreco=1000

@QueryParam

Extrai o valor de um parâmetro fornecido na query string da requisição.

@PathParam

Extrai o valor de um parâmetro enviado dentro da URI.

Tabela 3. Anotações da JAX-RS para injeção de dependências

Com exceção do Context, estes parâmetros são enviados dentro de URIs, query strings, cabeçalhos HTTP e cookies. Sendo assim, sua representação na camada de transporte é como String. Entretanto, podemos colocar estas anotações sobre parâmetros que não são String, para já recebermos os dados convertidos em um formato mais adequado para nossa manipulação. Tipos de parâmetros que podem ser marcados com estas anotações são:

  • Tipos primitivos
  • Classes que possuam um construtor tendo uma única String como parâmetro
  • Classes que possuam um método estático valueOf() recebendo uma String como parâmetro;
  • List<T>, Set<T> ou SortedSet<T>, onde T satisfaz a condição 2 ou a 3.

A seguir um exemplo de uso destas anotações:

@GET
@Path("{usuarioId}")
public Response buscarUsuario(@PathParam("usuarioId") String usuarioId) {

Usuario usuario = usuarioService.buscar(usuarioId);
Response resposta = Response.ok(usuario).build();

return resposta;
}

Este exemplo mostra como poderia ser um método de busca de usuário recebendo uma URI /usuario/{usuarioId}, como /usuario/123. O parâmetro usuarioId do método poderia ser int ou Integer, caso o ID do usuário fosse um número inteiro. A implementação da API faria a conversão do parâmetro enviado na URI para o tipo especificado no método.

Requisições aos métodos de Recursos

Um método de Recurso é uma operação exposta como um serviço REST. Estes métodos ficam em uma classe Recurso e são anotados com o método HTTP associado à operação em questão. O conjunto de anotações que define os métodos HTTP que podem ser utilizados nas operações é: @GET, @POST, @PUT, @DELETE, @HEAD e @OPTIONS.

Métodos de Recursos que serão expostos para os clientes devem ser públicos. Implementações da JSR devem alertar os desenvolvedores caso encontrem métodos não-públicos que sejam marcados com alguma destas anotações de método HTTP.

Os parâmetros dos métodos são convertidos da requisição de acordo com as anotações apresentadas na Tabela 3. Um método de Recurso pode ter no máximo um parâmetro não-anotado. Este parâmetro não-anotado será obtido do corpo da requisição.

A obtenção de parâmetros do corpo da requisição só faz sentido quando estamos falando de requisições POST e PUT. Estes são os únicos métodos HTTP que possuem um corpo, e quase sempre são utilizados em operações de criação e atualização de Recursos, respectivamente. O trecho a seguir apresenta um exemplo de método que recebe requisições POST para cadastro de usuários.

@POST
public Response cadastrarUsuario(Usuario usuario) {

usuario = usuarioService.cadastrar(usuario);
try {
return Response.created(new URI(usuario.getCodUsuario())).build();

} catch (URISyntaxException e) { throw new RuntimeException(e); }

}

Respostas dos métodos de Recursos

As respostas aos métodos de Recursos podem ser declaradas como void, Response ou qualquer outra classe Java. Retornar void implica em enviar uma resposta com corpo vazio, o que é mapeado em um status HTTP 204 (No Content). Este status é utilizado pela JSR para indicar que a requisição teve sucesso, e a resposta não possui corpo.

Colocar uma classe Java como tipo de resposta fará com que o objeto retornado seja colocado no corpo da resposta. Um objeto nulo enviado na resposta implicará em status HTTP 204 e um objeto não-nulo implicará no status HTTP 200.

A classe Response pode ser colocada como tipo de retorno dos métodos caso desejemos ter mais controle sobre a resposta. Com esta forma de retorno, conseguimos especificar cabeçalhos, corpo, status, cookies e mais algumas informações da resposta enviada.

Até este momento não mencionamos nada a respeito do formato dos Recursos manipulados. Mostramos exemplos de busca e cadastro de usuários, mas ficou explícita nos exemplos apenas a manipulação de objetos Java. A questão dos formatos é muito importante e é coberta na seção “Manipulação de diferentes formatos”.

Tratamento de erros e exceções

Como padrão, quando ocorre uma exceção durante uma chamada REST, o cliente recebe um status HTTP 500 (Internal Server Error). Em muitas situações este status pode não ser satisfatório, pois não fornece muita informação sobre o erro que ocorreu no servidor. Para resolver este problema a JSR-311 permite que a resposta seja personalizada em caso de exceções. Podemos fazer este controle de duas formas.

A primeira forma é com uma exceção especial. Basta disparar uma unchecked exception javax.ws.rs.WebApplicationException. Criando esta exceção podemos passar o status HTTP e um objeto Response, permitindo total controle da resposta que será enviada para o usuário.

Porém, em uma aplicação grande é muito comum termos o lançamento de outras exceções em camadas inferiores. Para esta situação a JSR-311 permite que seja criada uma classe para mapear a resposta correspondente a cada exceção. Este mapeamento seria conhecido apenas pela camada de serviços REST.

A classe de mapeamento deve implementar a interface javax.ws.rs.ext.ExceptionMapper e ser anotada com @Provider. A partir daí, quando a exceção especificada for disparada o controle vai passar para o método toResponse() desta classe. Este método poderá construir a resposta de acordo com a exceção, que é passada como parâmetro.

Sobre a segunda forma de tratar exceções vale citar que ela ainda está sendo implementada na versão 0.8 da especificação, e está sujeita a modificações até a finalização da JSR.

O trecho a seguir apresenta um exemplo de mapeamento de exceção para uma resposta com status HTTP customizado.

@Provider
public class ItemJaVendidoExceptionMapper implements ExceptionMapper {
public Response toResponse(ItemJaVendidoException e) {

return Response.status(Response.Status.GONE).build();
}

}


Manipulação de diferentes formatos

Uma das capacidades mais interessantes que temos na JSR-311 é a de tratar facilmente diferentes formatos nos nossos serviços. Somos capazes de receber e gerar dados em diferentes tipos de conteúdo, sem ônus para os desenvolvedores.

Os formatos XML e JSON já estão disponíveis no Jersey (implementação de referência da JSR). Além disso, a JSR permite que seja oferecido o suporte a qualquer formato, através de classes Provider.

Classes Provider são desenvolvidas para permitir a leitura e/ou escrita de determinados tipos de conteúdo (Content-Types). Uma classe Provider implementa as interfaces MessageBodyReader e MessageBodyWriter para oferecer suporte ao tipo de conteúdo que se propõe. Poderíamos ter, por exemplo, um Provider que soubesse manipular recursos com formato mp3 em uma aplicação multimídia.

Classes Recurso podem usar as anotações @ConsumeMime e @ProduceMime para declarar quais são os tipos de conteúdo que elas geram e os tipos de conteúdo que elas aceitam receber, respectivamente. Caso nenhum tipo de conteúdo seja declarado, assume-se que qualquer tipo de conteúdo (”*/*”) é aceito.

O trecho a seguir apresenta um exemplo de uso destas anotações na classe UsuarioResource. Neste exemplo, declaramos que a classe aceita e gera conteúdo nos formatos text/xml e application/json.

@ConsumeMime( { "text/xml", "application/json" })

@ProduceMime( { "text/xml", "application/json" })
public class UsuarioResource {}

Neste exemplo, colocamos as anotações sobre a classe. Estas anotações também podem ser colocadas sobre métodos. Caso as anotações sejam colocadas sobre a classe e também sobre um método, a anotação sobre o método é a que vale.

O trecho abaixo mostra um exemplo de anotações sobre a classe e sobre um método. Neste exemplo, declaramos que a classe aceita os tipos de conteúdo text/xml e application/json em todos os seus métodos, com exceção do método buscarUsuario(). Neste método declaramos que produzimos apenas conteúdo em formato text/xml. O método atualizarUsuario() herda as declarações feitas na classe, portanto aceita conteúdo em text/xml e application/json.

@Path("usuario")
@ConsumeMime( { "text/xml", "application/json" })

@ProduceMime( { "text/xml", "application/json" })
public class UsuarioResource {

@GET
@Path("{usuarioId}")
@ProduceMime("text/xml")
public Response buscarUsuario(@PathParam("usuarioId") String usuarioId) {

Usuario usuario = usuarioService.buscar(usuarioId);
Response resposta = Response.ok(usuario).build();

return resposta;
}

@PUT
@Path("{usuarioId}")
public Response atualizarUsuario(Usuario usuario) {

usuarioService.atualizar(usuario);
return Response.ok().build();

}
}

É importante mencionar como funciona este tratamento dos tipos de conteúdo. Quando um cliente faz uma requisição HTTP, ele pode especificar o cabeçalho Accept. Este cabeçalho informa ao servidor quais são os tipos de conteúdo que o cliente aceita receber. Se o cliente não especificar este cabeçalho, o valor assumido do mesmo é “*/*”, o que indica que o cliente aceita qualquer tipo de conteúdo.

Ao receber uma requisição HTTP, o runtime JAX-RS irá comparar a lista de tipos enviados no cabeçalho Accept com a lista de tipos de conteúdo registrados para o método invocado. Nesta comparação, as seguintes regras serão aplicadas:

  • Caso exista apenas um tipo de conteúdo em comum entre as duas listas, este será o tipo de conteúdo enviado;
  • Caso exista mais de um tipo de conteúdo em comum entre as listas, o conteúdo será enviado no formato que aparecer primeiro na lista registrada no servidor. Se o método declarou geração de text/xml e application/json (nesta ordem) e o cliente aceita ambos os tipos, o cliente receberá text/xml;
  • Se a lista de tipos de conteúdo oferecida pelo servidor não contiver nenhum dos tipos que o cliente afirmou aceitar, o runtime JAX-RS envia uma resposta de falha com status HTTP 415 (Unsupported Media Type).

Com estes exemplos, mostramos a capacidade de gerar diferentes formatos em nossos serviços, sem que seja necessário tratar isso explicitamente pela nossa aplicação. A declaração dos tipos de conteúdo através das anotações @ConsumeMime e @ProduceMime é suficiente para que o runtime JAX-RS faça o tratamento correto.

Uma última questão que precisamos abordar neste contexto é como indicar para a JAX-RS como deve ser feito o mapeamento de nossas classes em XMLs. As implementações da JSR são obrigadas a suportar o uso de JAXB (Java Architecture for XML Binding) na conversão de Java para XML e de XML para Java. O JAXB é um dos componentes do Java EE 5 e foi incluído também na versão 6 do Java SE. Ele é a forma padrão de mapeamento entre classes Java e documentos XML na JSR-311.

Para utilizar o JAXB no mapeamento de nossas classes, a opção mais simples é utilizar a anotação @XmlRootElement sobre as mesmas. Ao fazer isso, o JAXB fará a conversão da classe e dos seus atributos em um documento XML cujo elemento root será o nome da classe (começando por minúscula). Os elementos filhos serão os atributos da classe, seguindo a nomenclatura da mesma.

Caso seja desejado, é possível especificar na anotação @XmlRootElement um nome de elemento XML diferente do nome da classe. Podemos também modificar os elementos XML dos atributos da classe usando a anotação @XmlElement. O JAXB nos permite customizar bastante os mapeamentos realizados, se assim quisermos. O trecho abaixo apresenta um exemplo no qual mapeamos a classe Avaliacao para um XML com elementos em inglês. Não entraremos em mais detalhes sobre o JAXB neste momento, pois isto fugiria do foco do artigo.

@XmlRootElement(name = "feedback")

public class Avaliacao {XmlElement(name = "feedbackCode")

private String codAvaliacao;
@XmlElement(name = "rater")


private Usuario avaliador;
@XmlElement(name = "positive")

private boolean positiva;
@XmlElement(name = "comment")


private String comentario;
}


Mapeamento de URIs e métodos HTTP em classes Recurso e seus métodos

Uma das principais vantagens no uso da JSR-311/Jersey é eliminar do nosso desenvolvimento a validação de requisições e o mapeamento das mesmas em classes e métodos que devem processá-las. Isto implica em obter a URI e o método HTTP das solicitações e conferir se existe algum dos nossos serviços que saiba tratá-la. Além disso, antes de invocar os serviços em questão, precisamos extrair das URIs os parâmetros que tenham sido enviados nas mesmas.

A anotação @Path pode ser colocada em classes e métodos. Quando a colocamos sobre uma classe, estamos associando a classe a um prefixo de URI. Esta anotação foi utilizada anteriormente no artigo para associar o prefixo /usuario da nossa aplicação à classe UsuarioResource.

Colocamos a anotação @Path sobre métodos para formar o caminho completo dos nossos serviços. Juntando os valores da anotação @Path sobre os métodos com o valor da anotação sobre a classe, temos a lista de URIs disponíveis na classe Recurso em questão.

A colocação das anotações @GET, @POST, @PUT, @DELETE, @HEAD e @OPTIONS complementa o mapeamento de URIs. Com estas duas informações sobre cada método, conseguimos mapear precisamente cada solicitação na classe e no método que devem tratá-la.

Mostraremos na próxima seção como fica o uso da JSR-311 no desenvolvimento dos serviços de leilão.

Aplicando a JSR-311 e o Jersey nos serviços de leilão

Nesta seção mostraremos como usar o Jersey para implementar os serviços do processo de leilão. Devido às limitações de espaço, escolhemos apenas uma parte dos serviços, mas de forma que seja possível ilustrar com clareza as diferenças.

A primeira etapa necessária é a configuração do Jersey no projeto. A distribuição binária estável mais recente no momento da escrita deste artigo é a 0.7. Esta distribuição pode ser obtida no site do projeto.

Devemos mapear todos os prefixos de URIs dos nossos serviços para um Servlet do Jersey. O trecho abaixo mostra um web.xml configurado com este mapeamento. Na nossa aplicação, como todas as URIs são de serviços REST, mapeamos toda a aplicação (/*) para o Servlet do Jersey.

<?xml version="1.0" encoding="UTF-8"?>

<web-app>
<servlet>

<servlet-name>Jersey Web Application</servlet-name>
<servlet-class>com.sun.ws.rest.spi.container.servlet.ServletContainer</servlet-class>
<init-param>
<param-name>com.sun.ws.rest.config.feature.Redirect</param-name>
<param-value>true</param-value>

</init-param>

<init-param>
<param-name>com.sun.ws.rest.config.feature.ImplicitViewables</param-name>
<param-value>true</param-value>
</init-param>

<load-on-startup>1</load-on-startup>


</servlet>

<servlet-mapping>
<servlet-name>Jersey Web Application</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>


</web-app>

Além da configuração do Servlet, precisamos adicionar algumas bibliotecas para utilizar o Jersey. O conjunto mínimo de bibliotecas que devem ser colocadas na aplicação inclui o jersey.jar, jsr311-api.jar e asm.jar. Estas bibliotecas e mais algumas dependências estão presentes no diretório /lib da distribuição binária do Jersey.

Se não estiver usando Java SE 6 ou Java EE 5, você também precisará adicionar o JAXB ao projeto. Utilizando um servidor de aplicações Java EE 5 e as bibliotecas presentes na distribuição binária do projeto, você tem a garantia de ter todas as dependências necessárias.

Desenvolvimento dos serviços

Mostraremos agora como pode ser feita a implementação de alguns serviços do processo de leilão com o uso do Jersey. Somente um subconjunto dos serviços será apresentado neste artigo, mas a implementação completa pode ser vista no código fonte.

Começaremos pelos serviços correspondentes ao prefixo /usuario e depois falaremos também sobre os serviços do prefixo /avaliacao. A Tabela 4 lista os serviços que apresentaremos neste artigo.

URI

Método

Formato

Efeito

/usuario

POST

Usuario

Cadastra um usuário.

/usuario/{id}

GET

Usuario

Busca um usuário.

PUT

Usuario

Atualiza um usuário.

/usuario/{id}/avaliacoes

GET

Coleção de avaliações

Busca as avaliações recebidas por um usuário.

/usuario/{id}/itens

GET

Coleção de itens

Busca os itens anunciados por um determinado usuário.

POST

Item

Usuário coloca novo item à venda.

/avaliacao/{id}

GET

Avaliação

Busca uma determinada avaliação.

/avaliacao/de/{id}/para/{id}

POST

Avaliação

Realização da avaliação de um usuário sobre outro.

A listagem abaixo apresenta a classe UsuarioResource. Esta é uma das classes Recurso da nossa aplicação e nela estão todos os serviços do prefixo /usuario. A classe foi anotada com @Path(”usuario”), o que faz a associação da mesma com o prefixo citado. Além disso, a classe possui as anotações @ConsumeMime e @ProduceMime, que neste caso declaram que os serviços da mesma são capazes de consumir e gerar conteúdo nos formatos text/xml e application/json.

@Path("usuario")
@ConsumeMime( { "text/xml", "application/json" })

@ProduceMime( { "text/xml", "application/json" })
public class UsuarioResource {


private ItemService itemService;
private UsuarioService usuarioService;
private AvaliacaoService avaliacaoService;

public UsuarioResource() {

this.itemService = ServiceFactory.getItemService();
this.usuarioService = ServiceFactory.getUsuarioService();

this.avaliacaoService = ServiceFactory.getAvaliacaoService();
}

@GET
@Path("{usuarioId}")

public Response buscarUsuario(@PathParam("usuarioId") String usuarioId) {
Usuario usuario = usuarioService.buscar(usuarioId);

if(usuario == null){
return Response.status(HttpServletResponse.SC_NOT_FOUND).build();

}
Response resposta = Response.ok(usuario).build();
return resposta;

}

@POST
public Response cadastrarUsuario(Usuario usuario) {
usuario = usuarioService.cadastrar(usuario);

try {
return Response.created(new URI(usuario.getCodUsuario())).build();

} catch (URISyntaxException e) { throw new RuntimeException(e); }

}

@PUT
@Path("{usuarioId}")
public Response atualizarUsuario(Usuario usuario) {

usuarioService.atualizar(usuario);
return Response.ok().build();

}

@POST
@Path("{usuarioId}/itens")
public Response cadastrarItem(@Context UriInfo uriInfo, 
@PathParam("usuarioId") String usuarioId, Item item)
throws URISyntaxException { Usuario usuario = new Usuario(usuarioId); item = itemService.cadastrar(item, usuario); URI uriItem = new URI(uriInfo.getBaseUri() + "item/" + item.getCodItem()); return Response.created(uriItem).build(); } @GET @Path("{usuarioId}/itens") public Response buscarItensDoUsuario(@PathParam("usuarioId") String usuarioId) { // Verifica se o usuário existe if (this.usuarioService.buscar(usuarioId) == null) { return Response.status(Status.NOT_FOUND).build(); } List itens = itemService.buscarPorVendedor(new Usuario(usuarioId)); return Response.ok(new ItensUsuario(itens)).build(); } @GET @Path("{usuarioId}/avaliacoes") public AvaliacoesUsuario
buscarAvaliacoesDoUsuario(@PathParam("usuarioId")String usuarioId){ Usuario usuario = new Usuario(usuarioId); List avaliacoes = avaliacaoService.buscarPorUsuario(usuario); return new AvaliacoesUsuario(avaliacoes); } }

O primeiro serviço é o de busca de usuário. Este método foi anotado com @Path(”{usuarioId}”). O casamento da anotação sobre o método com a anotação sobre a classe especifica que este método responde a requisições para a URI /usuario/{usuarioId}. Como o método também foi anotado com @GET, sabemos que as solicitações HTTP GET para /usuario/{usuarioId} serão tratadas por este método. Importante reparar no uso da anotação @PathParam para injetar no parâmetro usuarioId o valor que veio na URI.

Na resposta a esta solicitação, retornamos o status HTTP 200 (OK) e os dados do usuário no corpo da resposta. Caso o usuário não tenha sido encontrado, retornamos status 404 (Not Found). A listagem abaixo mostra a classe Usuario, que é manipulada por alguns serviços na classe UsuarioResource. Por simplicidade mostramos apenas a declaração da classe com os atributos.

@XmlRootElement
public class Usuario {

private String codUsuario;
private String nome;
private String login;
private String email;

private Item[] items;
private Oferta[] ofertas;
private Avaliacao[] avaliacoes;

}

O segundo serviço presente em UsuarioResource é o de cadastro de usuário. Como não colocamos nenhuma anotação @Path sobre este método, ele está associado à URI da classe (/usuario). O método foi anotado com @POST, então ele responde às solicitações POST na URI citada. O parâmetro contendo os dados do usuário não recebeu nenhuma anotação, o que significa que ele é obtido do corpo da solicitação. Como o método de cadastro de usuário não tem as anotações @ConsumeMime e @ProduceMime, ele herda as declarações feitas sobre a classe. Sendo assim, podemos cadastrar usuários usando text/xml ou application/json. Na resposta à criação do usuário nós enviamos o status HTTP 201 (Created), colocando no header Location a URI do novo usuário.

O método de atualização de usuário recebe solicitações PUT em /usuario/{usuarioId}. Os dados do usuário também são consumidos do corpo da solicitação, e as operações com sucesso resultam no envio do status HTTP 200.

No método de cadastrar itens, usamos a anotação @Path para associar o serviço à URI /usuario/{usuarioId}/itens. Usamos a anotação @PathParam para extrair da URI o ID do usuário envolvido. Usamos também a anotação @Context para injetar a classe UriInfo, que nos fornece informações sobre a URI de acesso aos serviços. No final, usamos a UriInfo para colocar no header Location o caminho absoluto de acesso ao item recém-criado. A listagem abaixo mostra a classe Item, manipulada neste serviço.

@XmlRootElement
public class Item {
private String codItem;
private String nome;

private String descricao;
private BigDecimal valorInicial;
private boolean novo;
private boolean vendido;

}

No método de buscar itens do usuário temos o primeiro serviço que manipula coleções. Este método trata de solicitações GET à URI /usuario/{usuarioId}/itens. Para retornar a lista de itens do usuário foi criada a classe ItensUsuario, que simplesmente contém a lista. A listagem a seguir apresenta a declaração desta classe.

@XmlRootElement
public class ItensUsuario {


private List item;
public ItensUsuario() {}
public ItensUsuario(List itens) { this.item = itens; }

}

O método de buscar avaliações do usuário é estruturalmente semelhante ao de buscar itens. Foi criada a classe AvaliacoesUsuario para retornar a lista de avaliações. Como esta é muito semelhante à ItensUsuario, ela será omitida. Para implementar os serviços do prefixo /avaliacao foi criada a classe AvaliacaoResource. Esta classe pode ser vista na listagem a seguir. Temos a anotação @Path registrando a URI desejada e também as anotações @ConsumeMime e @ProduceMime declarando que manipulamos text/xml e application/json.

@Path("avaliacao")
@ConsumeMime( { "text/xml", "application/json" })

@ProduceMime( { "text/xml", "application/json" })
public class AvaliacaoResource {


private AvaliacaoService avaliacaoService;
public AvaliacaoResource() {
this.avaliacaoService = ServiceFactory.getAvaliacaoService();

}

@GET
@Path("{avaliacaoId}")
public Response buscarAvaliacao(@PathParam("avaliacaoId") String avaliacaoId) {

Avaliacao avaliacao = avaliacaoService.buscar(avaliacaoId);
if(avaliacao == null){

return Response.status(HttpServletResponse.SC_NOT_FOUND).build();
}
return Response.ok(avaliacao).build();

}

@POST
@Path("de/{avaliador}/para/{avaliado}")
public Response avaliarUsuario(@Context UriInfo uriInfo, 
@PathParam("avaliador") String avaliador, @PathParam("avaliado") String avaliado, Avaliacao avaliacao)
throws URISyntaxException { Usuario usuarioAvaliado = new Usuario(avaliado); avaliacao = avaliacaoService.cadastrar(avaliacao, usuarioAvaliado); URI uri = new URI(uriInfo.getBaseUri() + "avaliacao/" + avaliacao.getCodAvaliacao()); return Response.created(uri).build(); } }

O primeiro serviço desta classe é o de busca de avaliação, que é muito semelhante ao serviço de busca de usuário que vimos anteriormente. Este serviço ficou mapeado em /avaliacao/{avaliacaoId}, recebendo solicitações GET.

O serviço de avaliar usuário é mais interessante. Extraímos dois parâmetros da URI e consumimos um recurso do corpo da solicitação. A URI /avaliacao/de/{avaliador}/para/{avaliado} é um bom exemplo da liberdade que temos na definição das URIs. Podemos moldá-las para aumentar a clareza das operações. Isto facilita a aproximação dos serviços com o nosso domínio da aplicação.

Criação de clientes Java para os serviços REST

Expusemos com bom nível de detalhe a implementação de serviços REST do lado do servidor. Para permitir uma visão completa da comunicação, é muito importante falar também de clientes RESTful.

Uma vez que já temos o protocolo estabelecido, o papel do cliente é manipular as solicitações e respostas no formato acordado com o servidor. Para exemplificar como pode ser feito isso do lado do cliente, apresentamos na listagem abaixo a classe OfertaTestREST, que realiza uma solicitação para cadastro de uma oferta sobre um item. O código deste teste foi feito para facilitar a ilustração do que está sendo feito. Este é o motivo de imprimir os cabeçalhos e corpo tanto da requisição como da resposta HTTP.

public class OfertaTestREST extends TestCase {


public void testCadastro() throws HttpException, IOException{
// Criamos o usuário

Usuario usuario = new Usuario();
usuario.setNome("Usuario Artigo");
usuario.setEmail("usuario@test.com");
usuario.setLogin("artigo" + System.currentTimeMillis());
UsuarioService usuarioService = ServiceFactory.getUsuarioService();
usuario = usuarioService.cadastrar(usuario);


// Criamos o item
Item item = new Item();
item.setDescricao("Item teste Artigo " + System.currentTimeMillis());
item.setNome("Iphone");
item.setNovo(true);
item.setValorInicial(new BigDecimal(0));
item.setVendido(false);
ItemService itemService = ServiceFactory.getItemService();
item = itemService.cadastrar(item, usuario);


// E agora a criação da Oferta de maneira RESTFul
Oferta oferta = new Oferta();
oferta.setDataModificacao(new Date());
oferta.setItem(item);
oferta.setOfertante(usuario);
oferta.setValor(new BigDecimal(111.22).setScale(2, RoundingMode.HALF_UP));


// Montando requisição com o commons-http-client
HttpClient client = new HttpClient();
PostMethod method =
new PostMethod("http://localhost:8080/item/" + item.getCodItem() + "/ofertas"); // Geração de XMLs com o XStream XStream xstream = new XStream(); xstream.alias("oferta", Oferta.class); xstream.alias("ofertante", Usuario.class); xstream.alias("item", Item.class); String ofertaXml = xstream.toXML(oferta); System.out.println(ofertaXml); // Definindo corpo da requisição StringRequestEntity requestEntity =
new StringRequestEntity(ofertaXml, "text/xml", "UTF-8"); method.setRequestEntity(requestEntity); // Here it goes... int statusCode = client.executeMethod(method); // Status HTTP deve ser 201 - Created assertEquals(HttpServletResponse.SC_CREATED, statusCode); System.out.println("\n#### REQUISIÇÃO ####\n"); System.out.println(method.getName() + " " + method.getPath()); Header[] headersRequest = method.getRequestHeaders(); for(Header header : headersRequest){ System.out.println(header.getName() + " : " + header.getValue()); } method.getRequestEntity().writeRequest(System.out); System.out.println("\n\n#### RESPOSTA ####\n"); System.out.println(method.getStatusLine().getHttpVersion() +
" " + method.getStatusLine().getStatusCode()); Header[] headersResponse = method.getResponseHeaders(); for(Header header : headersResponse){ System.out.println(header.getName() + " : " + header.getValue()); } System.out.println(method.getResponseBodyAsString()); } }

O formato da requisição HTTP gerada por esta classe pode ser visto na primeira listagem abaixo. Na listagem seguinte pode ser conferido o formato da resposta HTTP a esta solicitação. A terceira listagem desta sequência mostra o formato da resposta à busca de ofertas de um determinado item.

#### REQUISIÇÃO ####
POST /item/13c017ba-7c01-44aa-9a0b-b815a9ea298f/ofertas
User-Agent : Jakarta Commons-HttpClient/3.0.1
Host : localhost:8080
Content-Length : 596
Content-Type : text/xml; charset=UTF-8
<oferta>
  <valor>111.22</valor>
  <dataModificacao>2008-09-28 10:36:54.642 BRT</dataModificacao>

  <item>
    <codItem>13c017ba-7c01-44aa-9a0b-b815a9ea298f</codItem>
    <nome>Iphone</nome>
    <descricao>Item teste Tech Talk 1222609014628</descricao>

    <valorInicial>0</valorInicial>

    <novo>true</novo>
    <vendido>false</vendido>
  </item>

  <ofertante>
    <codUsuario>fc6104ad-b9a5-4b2d-9085-a186083b9c2d</codUsuario>
    <nome>Usuario Tech Talk</nome>
    <login>techtalk1222609014475</login>

    <email>usuario@test.com</email>
  </ofertante>
  <vencedora>false</vencedora>
</oferta>
#### RESPOSTA ####

HTTP/1.1 201
Server : Apache-Coyote/1.1
Location : http://localhost:8080/item/13c017ba-7c01-44aa-9a0b-b815a9ea298f/ofertas
Content-Length : 0
Date : Sun, 28 Sep 2008 13:36:55 GMT

A implementação das solicitações HTTP em Java pode ser feita com uso da biblioteca commons-http-client. Esta API permite que montemos requisições HTTP e recebamos suas respostas correspondentes, da mesma forma que ocorreria com um browser simples. Não existe a capacidade de executar código javascript e também não existem equivalentes para os plugins dos browsers completos. Mesmo sem estes recursos, o commons-http-client nos dá o poder de fazer praticamente qualquer operação padrão HTTP do lado cliente. Isto torna esta biblioteca um componente de fundamental importância para implementações RESTful feitas em Java.



Clientes Ajax/JSON

Quando falamos em web services a primeira idéia sobre o formato para troca das informações é o XML. Porém, outros formatos para troca de dados existem e podem atender melhor a alguns casos particulares.

Um destes formatos é o JSON. Ele tem se difundido bastante, especialmente como uma alternativa ao XML em AJAX, pois é um formato nativamente suportado por qualquer interpretador javascript. Outro motivo é que é um formato mais “enxuto”, gerando documentos menores e mais fáceis consumir. Por estas vantagens, tem sido comum o uso de JSON para criar clientes AJAX de serviços REST.

Na listagem abaixo apresentamos um exemplo de função javascript que busca itens de um usuário num serviço REST e apresenta os mesmos em uma tabela. Mostramos apenas os trechos mais relevantes. O leitor pode obter o código completo dos exemplos de clientes ajax no site da revista. A listagem seguinte apresenta a definição de uma função que faz uma requisição HTTP GET assíncrona a um serviço REST.

<script type="text/javascript">

function mostrarItens() {
// Faz a chamada rest
var url = "/usuario/" + document.getElementById("cod_usuario").value + "/itens";

var tabela = document.getElementById("itens");
var response = RESTFul.get(url);

if (response.status != 200) {
tabela.innerHTML = "<b>Usuário não encontrado!</b>";

return;
}

var itensUsuario = response.getObject();
var stringitens = "<table border="1">";
stringitens += "<thead><td>codigo</td><td>descrição</td><td>nome</td><td>novo</td>"

+"<td>valor inicial</td><td>vendido</td></thead>";
for (i in itensUsuario.itensUsuario.item) {

var item = itensUsuario.itensUsuario.item[i];
stringitens += "<tr><td>
// requisição Ajax para HTTP GET
get: function (url, request) {

// cria um XmlHttpRequest
xmlhttp = getXTR();
// constrói um request default
if (!request) {request = new RESTFul.Request();}

xmlhttp.open("GET",url,false); // requisição GET assíncrona
xmlhttp.setRequestHeader("Accept","application/json");
xmlhttp.send(null); // requisição sem corpo

while (xmlhttp.readyState != 4);
var response =
new RESTFul.Response(xmlhttp.status, xmlhttp.statusText,
xmlhttp.getAllResponseHeaders(), xmlhttp.responseText); return response; }


Web Application Description Language (WADL)

Há muita discussão a respeito de interfaces de descrição de serviços REST. Há quem julgue que elas não são necessárias. Outros acham interessante ter um “equivalente” do WSDL para REST.

Consideramos que é conveniente oferecer uma interface simples de consulta dos serviços disponíveis, mas sem tantos detalhes como o WSDL. Uma das opções disponíveis para isso é o WADL. O WADL informa quais são as URIs disponíveis, os métodos permitidos em cada uma delas, e os parâmetros de entrada e saída dos serviços.

O Jersey gera automaticamente um WADL dos nossos serviços a partir de nossas classes Recurso. Consideramos este documento bom o suficiente para não valer a pena gerar algo semelhante de forma customizada. Mais detalhes sobre os serviços podem ser colocados numa Wiki ou página semelhante. A geração do WADL é uma funcionalidade específica do Jersey, e não está presente na JSR-311.

Para acessar o WADL correspondente aos nossos recursos, devemos fazer uma solicitação HTTP GET à URI /application.wadl, na raiz da nossa aplicação. Isto é conveniente por permitir a fácil visualização com um browser. Veja o documento WADL dos serviços do processo de leilão.



Suporte de ferramentas

Um dos pontos positivos dos web services WS-* é que já existe um amplo conjunto de ferramentas para facilitar o trabalho com esta linha de serviços. Geradores de clientes e esqueletos de serviço estão disponíveis para várias plataformas e linguagens.

Os web services REST tiveram que esperar muito mais tempo para terem as primeiras ferramentas de desenvolvimento. Não devemos ver isso como um ponto negativo. Se existem muitos produtos para melhorar o trabalho com WS-* é porque as tecnologias envolvidas exigem isso.

Os serviços REST são essencialmente mais simples e concentram seus detalhes principais em torno de HTTP. Como as tecnologias utilizadas com REST já são muito maduras e conhecidas, indiretamente já havia um ótimo suporte a este desenvolvimento.

Recentemente o NetBeans introduziu plugins focados em serviços REST, e estes contribuem com um ganho de produtividade no começo do desenvolvimento. Somos capazes de gerar classes Recurso a partir de entidades JPA e também gerar clientes Java e Javascript para nossos serviços. Estas funcionalidades ajudam na rápida criação de protótipos, e oferecem um bom ponto de partida na implementação. Detalhes sobre o suporte do NetBeans ficam fora do escopo deste artigo, mas recomendamos que os leitores avaliem os benefícios que este IDE traz.



Conclusão

O objetivo deste artigo foi partir de um exemplo rico o suficiente de arquitetura orientada a serviços e então fazer a modelagem e desenvolvimento dos mesmos com uso de uma abordagem RESTful. Através do exemplo do leilão do Mercado Livre, fomos capazes de percorrer todas as etapas envolvidas na implementação de web services REST. Entre estas etapas podemos destacar a identificação dos recursos, mapeamento de URIs, definição do protocolo de comunicação e formas de mapeamento de Java para XML e XML para Java.

Foi possível ilustrar bem como o bom uso dos recursos do HTTP podem ajudar na definição de um protocolo de comunicação conciso e claro. Elementos já conhecidos como os status e métodos HTTP, URIs e Content-Types passam a ser utilizados para comunicações bem mais diversas do que a transferência simples de HTML.

Com o amadurecimento das implementações REST, estamos caminhando na direção de soluções poderosas e interoperáveis. Até alguns anos atrás, a adoção de web services representava uma troca de performance por interoperabilidade. Piorar a performance na comunicação com a mesma plataforma e linguagem para ser capaz de falar com qualquer outro serviço.

Isto felizmente está deixando de ser verdade. Estamos conquistando poder suficiente para ter ao mesmo tempo alta performance e interoperabilidade.

A JSR-311 e o Jersey trazem benefícios interessantes, e não nos tiram o poder do REST. A introdução destes componentes simplifica o desenvolvimento. Além disso, ganhamos funcionalidades que seriam muito trabalhosas de implementar de forma customizada. A capacidade de manipular múltiplos formatos e a geração do WADL são bons exemplos disso.

Esperamos que este artigo tenha contribuído com novas idéias para os leitores no desenvolvimento de serviços REST. Esta linha de serviços vem amadurecendo progressivamente e já se apresenta como uma opção poderosa para integração entre aplicações.

Pretendo publicar vários outros artigos na área de web services, integração e tecnologias relacionadas, então se você gostou deste artigo, acompanhe sempre as novidades por aqui ;)

Bruno Luiz Pereira da Silva

Bruno Luiz Pereira da Silva - » Visite o site do autor: http://brunopereira.org/.

Engenheiro eletrônico e de computação de 26 anos que se frustrou com a eletrônica e se apaixonou por software. Trabalha com software desde 2003 (sem contar o período acadêmico) e com Java desde o começo de 2004. Se interessa bastante por diversas áreas distintas, como linguagens de programação, open source, sistemas operacionais, processo de desenvolvimento, computação distribuída, SOA e Java em geral.

Trabalha há 2 anos e meio na Concrete Solutions, atuando na Globo.com (mais precisamente na área do ISP) desde minha chegada à empresa. Atualmente é também colunista da Java Magazine, com foco principal em Java EE e Web Services em geral.