Web Services 🌐
Um Web Service é uma tecnologia que permite a comunicação entre diferentes sistemas de software pela internet. Ele disponibiliza uma interface acessível pela web, utilizando padrões abertos e protocolos como HTTP, XML e JSON, para que sistemas diferentes possam trocar dados e funcionalidades entre si, mesmo quando desenvolvidos em plataformas e linguagens distintas.
Na prática, isso significa que um sistema pode solicitar dados ou serviços a partir de outro sistema, enviar informações e até mesmo realizar operações remotas, tudo isso através de chamadas HTTP.
Existem dois tipos principais de Web Services:
-
XML Web Services: Utiliza dois padrões principais: SOAP e WSDL. O SOAP (Simple Object Access Protocol) é um protocolo baseado em XML para troca de mensagens entre sistemas, enquanto o WSDL (Web Services Description Language) é uma linguagem baseada em XML para descrever a interface de um Web Service. Os Web Services baseados em XML são mais complexos e pesados, mas oferecem suporte a funcionalidades avançadas, como segurança e transações.
-
REST (Representational State Transfer): O REST é um estilo arquitetural que utiliza os próprios métodos HTTP, como GET, POST, PUT e DELETE, para realizar operações sobre recursos. Os Web Services RESTful são geralmente mais simples de implementar e mais leves que os serviços SOAP. Devido à sua simplicidade, atualmente, existe uma grande adesão a este estilo de Web Service, por essa razão, o REST será o foco desta disciplina.
Os Web Services desempenham um papel fundamental na construção de arquiteturas de sistemas como a de micro serviços. Micro serviços são uma abordagem arquitetural na qual um aplicativo é construído como um conjunto de pequenos serviços independentes, cada um executando um processo específico e comunicando-se através de APIs leves, geralmente baseadas em serviços REST ou protocolos de mensagens assíncronas. Cada serviço é desenvolvido, implantado e dimensionado de forma independente, permitindo maior flexibilidade, escalabilidade e facilidade de manutenção em comparação com arquiteturas monolíticas.
Para saber mais sobre Web Services: consulte o o capítulo 7 do livro Desenvolvimento de software, v.3 programação de sistemas web orientada a objetos em Java para compreender detalhes sobre a implementação de Web Services em Java.
RESTful Web Services na prática com Quarkus 🛠️
No Jakarta EE, o JAX-RS (hoje chamado de Jakarta RESTful Web Services) provê a funcionalidade necessária para a construção de Web Services baseados em REST. O Quarkus implementa essa especificação por meio da extensão resteasy-reactive, que utiliza anotações Java para transformar uma classe comum em um recurso REST, sem a necessidade de arquivos de configuração XML.
Vamos construir, passo a passo, um pequeno catálogo de produtos para entender as principais anotações. Os exemplos abaixo utilizam o pacote jakarta.ws.rs, o mesmo utilizado pelo projeto PW2 ConversionService já mencionado nos exercícios desta página.
Passo 1: Criando o projeto
mvn io.quarkus.platform:quarkus-maven-plugin:3.8.2.Final:create \
-DprojectGroupId=dev.rpmhub \
-DprojectArtifactId=produtos \
-DclassName="dev.rpmhub.ProdutoResource" \
-Dpath="/produtos" \
-Dextensions="resteasy-reactive,resteasy-reactive-jackson"
cd produtos
Note que a extensão resteasy-reactive implementa o JAX-RS no Quarkus, enquanto resteasy-reactive-jackson adiciona suporte à conversão automática entre objetos Java e JSON.
Passo 2: Um recurso REST básico
Diferente do Jakarta EE tradicional, o Quarkus não exige uma classe que estenda Application com a anotação @ApplicationPath: basta anotar uma classe com @Path para transformá-la em um recurso REST:
package dev.rpmhub;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;
@Path("/produtos")
public class ProdutoResource {
@GET
@Produces(MediaType.TEXT_PLAIN)
public String ola() {
return "Bem-vindo ao catálogo de produtos!";
}
}
@Path: define a URI do recurso (o endpoint do serviço). Pode ser usada tanto na classe quanto em métodos individuais.@GET: indica que o método responde a requisições HTTP do tipo GET.@Produces: define o tipo MIME que o método retorna para o cliente (nesse caso, texto simples).
Ao rodar ./mvnw quarkus:dev e acessar http://localhost:8080/produtos, a mensagem “Bem-vindo ao catálogo de produtos!” é exibida no navegador.
Passo 3: Parâmetros na URI
Para buscar um produto específico, precisamos de um identificador na própria URI, por exemplo, /produtos/1. Isso é feito com @PathParam:
@GET
@Path("/{id}")
@Produces(MediaType.APPLICATION_JSON)
public Produto buscarPorId(@PathParam("id") Long id) {
return catalogo.get(id);
}
O trecho {id} no @Path funciona como uma variável de template: o valor informado na URI é injetado no parâmetro do método por meio de @PathParam("id"). Para representar o produto, podemos usar um record Java: ele já gera automaticamente construtor e métodos de acesso aos campos. A conversão desse objeto para JSON na resposta é feita pela extensão resteasy-reactive-jackson, adicionada no Passo 1:
public record Produto(Long id, String nome, String categoria, double preco) {}
Passo 4: Parâmetros de consulta (query string)
Quando o filtro é opcional, o mais comum é utilizar parâmetros de consulta (query params), por exemplo, /produtos?categoria=eletronicos. Nesse caso, utilizamos a anotação @QueryParam:
@GET
@Produces(MediaType.APPLICATION_JSON)
public List<Produto> listar(@QueryParam("categoria") String categoria) {
if (categoria == null) {
return catalogo.values().stream().toList();
}
return catalogo.values().stream()
.filter(p -> p.categoria().equalsIgnoreCase(categoria))
.toList();
}
Note que, diferente do @PathParam, o parâmetro de consulta é opcional: se o cliente não informar categoria na URL, o valor injetado será null.
Passo 5: Recebendo dados simples no corpo da requisição
Nem toda requisição POST envia um objeto estruturado. Quando o corpo contém apenas um valor simples (um número ou um texto, por exemplo), basta declarar o parâmetro do método com o tipo primitivo esperado, sem nenhuma classe auxiliar:
@POST
@Path("/desconto")
@Consumes(MediaType.TEXT_PLAIN)
@Produces(MediaType.TEXT_PLAIN)
public String aplicarDesconto(double preco) {
double comDesconto = preco * 0.9;
return Double.toString(comDesconto);
}
@POST: indica que o método responde a requisições HTTP do tipo POST.@Consumes: define o tipo MIME que o método espera receber do cliente. Nesse caso,TEXT_PLAINinforma que o corpo da requisição é apenas texto puro, e não um objeto JSON.
Esse é exatamente o padrão utilizado no exercício de conversão de quilômetros por hora para milhas por hora (veja os Exercícios - Parte 1 abaixo): o corpo da requisição contém somente o número a ser convertido, sem nenhuma estrutura JSON.
Passo 6: Recebendo dados estruturados em JSON
Quando o corpo da requisição representa um objeto com vários campos, o mais comum é enviá-lo em JSON. No JAX-RS, basta declarar um parâmetro com o tipo do objeto esperado: a conversão de JSON para objeto Java (e vice-versa) é feita automaticamente pela extensão resteasy-reactive-jackson, adicionada no Passo 1:
@POST
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
public Response criar(Produto produto) {
catalogo.put(produto.id(), produto);
return Response.status(Response.Status.CREATED).entity(produto).build();
}
Note que o parâmetro produto não possui nenhuma anotação: quando um método recebe um objeto sem @PathParam, @QueryParam ou similares, o JAX-RS entende que ele deve ser preenchido a partir do corpo (body) da requisição.
🚨 Uma dúvida comum é a diferença entre @Consumes e @Produces: @Consumes descreve o que o servidor aceita receber, enquanto @Produces descreve o que o servidor envia de volta.
Passo 7: Controlando a resposta HTTP
Até aqui, os métodos retornaram diretamente um objeto (Produto, List<Produto>). Isso funciona bem quando a resposta é sempre “200 OK”. Porém, muitas vezes precisamos informar códigos de status HTTP diferentes, por exemplo, 201 ao criar um recurso, 404 quando ele não existe, ou 204 quando a remoção é concluída sem conteúdo de retorno. Para esses casos, utilizamos a classe jakarta.ws.rs.core.Response:
@DELETE
@Path("/{id}")
public Response remover(@PathParam("id") Long id) {
if (catalogo.remove(id) == null) {
return Response.status(Response.Status.NOT_FOUND).build();
}
return Response.noContent().build(); // HTTP 204
}
| Método | Código HTTP | Situação |
|---|---|---|
Response.status(Response.Status.CREATED) | 201 | Recurso criado com sucesso |
Response.ok() | 200 | Requisição processada com sucesso |
Response.noContent() | 204 | Sucesso, mas sem conteúdo de retorno (ex.: remoção) |
Response.status(Response.Status.NOT_FOUND) | 404 | Recurso não encontrado |
Resumo das anotações
| Anotação | Onde é usada | Função |
|---|---|---|
@Path | Classe ou método | Define a URI do recurso ou endpoint. Aceita variáveis de template, por exemplo, /produtos/{id} |
@GET @POST @PUT @DELETE | Método | Associa o método a um verbo HTTP |
@PathParam | Parâmetro de método | Injeta um valor vindo de uma variável do @Path |
@QueryParam | Parâmetro de método | Injeta um valor vindo da query string (?nome=valor) |
@Consumes | Método | Define o tipo MIME que o método recebe do cliente |
@Produces | Método | Define o tipo MIME que o método envia ao cliente |
Exercícios - Parte 1 📝
Desenvolva um Web Service em Rest utilizando o framework Quarkus que permita realizar as seguintes conversões de unidades de medida:
- Conversão de Quilômetro por hora para Milhas por hora:
- Este método deve aceitar requisições do tipo POST e produzir os resultados em formato de texto.
- A fórmula de conversão a ser utilizada é: 1 quilômetro por hora equivale a 0.621371 milhas por hora.
- Conversão de Nós para Quilômetros por hora:
- Este método deve aceitar requisições do tipo GET e retornar os resultados em formato JSON.
- A fórmula de conversão a ser aplicada é: 1 nó equivale a 1.852 quilômetros por hora.
💡 Reveja os passos 2 (recurso básico), 3 (@PathParam), 5 (corpo em texto simples) e 7 (Response) da seção anterior para lembrar como declarar métodos GET/POST e retornar JSON.
Certifique-se de implementar corretamente os casos de teste do exercício.
/**
* Test case for converting kilometers to miles.
*
* This test sends a POST request to the "/Conversion/km-to-miles"
* endpoint with a body of "50" (representing 50 kilometers per hour).
* The expected result is a response with a status code of 200 and a
* body of "31.06855" (the equivalent value in miles per hour).
*/
@Test
void testConversionKmMiles() {
given()
.contentType(ContentType.TEXT)
// 50 quilômetros por hora
.body("50")
.when()
.post("/Conversion/km-to-miles")
.then()
.contentType(ContentType.TEXT)
.statusCode(200)
.body(is("31.06855"));
}
/**
* Test case to verify the conversion from knots to kilometers per hour.
* The expected value for 1 knot in km/h is 1.852.
*/
@Test
void testConversionKnotsKm() {
given()
.contentType(ContentType.TEXT)
.when()
.contentType(ContentType.JSON)
.get("/Conversion/knots-to-km/1")
.then()
.statusCode(200)
.body("value", is(1.852f));
}
⚠️ Caso você tenha dificuldades para implementar o exercício, consulte o
código fonte do projeto [PW2 ConversionService](https://github.com/rpmhubdev/pw2-conversion)
para obter um exemplo.
Exercícios - Parte 2 📝
Desenvolva um microserviço para gerenciar uma lista de tarefas. A API REST do serviço deve oferecer as seguintes funcionalidades:
1) Para adicionar uma nova tarefa, envie uma requisição POST para a rota /tarefas. A requisição deve conter um JSON com o título e a descrição da tarefa. Em resposta, o servidor retorna a tarefa criada com um ID gerado automaticamente no formato JSON.
2) Para listar as tarefas cadastradas, envie uma requisição GET para a rota /tarefas. A resposta será um JSON contendo todas as tarefas armazenadas.
3) Para excluir uma tarefa, use o método DELETE e inclua o ID da tarefa na rota /tarefas/{id}. A resposta será um JSON confirmando a exclusão da tarefa.
4) Desafio Opcional: adicione suporte a filtros, por exemplo, GET /tarefas?concluida=true.
💡 Este exercício é uma boa oportunidade para reaproveitar o exemplo do catálogo de produtos: a criação de tarefas (POST em JSON) segue o mesmo padrão do passo 6, a listagem/filtro (GET com @QueryParam) segue o passo 4, e a exclusão (DELETE) segue o passo 7.
Material complementar (legado) 📼
As gravações abaixo são materiais mais antigos da disciplina, anteriores à adoção do Quarkus como framework principal. Elas utilizam Jakarta EE tradicional (implantado em um servidor de aplicação), e não o Quarkus. Ficam disponíveis apenas como referência histórica.
RESTFul Web Services
Deprecated
XML Web Services
Deprecated
Dúvidas na configuração do XML Web Service? seguem os arquivos de configuração server.xml e pom.xml utilizados no vídeo.
Referências 📚
-
Alex Soto Bueno; Jason Porter; Quarkus Cookbook: Kubernetes-Optimized Java Solutions. Editora: O’Reilly Media, 2020.
-
Writing JSON REST services. Disponível em: https://quarkus.io/guides/rest-json
-
Jakarta RESTful Web Services. Disponível em: https://jakarta.ee/specifications/restful-ws/

CC BY 4.0 DEED