← Spring Boot®: aplicações, contratos e operação
03 / 10 · 60 MIN

Rotas HTTP e contratos de pedidos

Lê o contrato HTTP desde o mapping até aos argumentos e à resposta; distingue erros de método, representação e conversão.

O contrato começa no mapping

Uma rota é mais do que um caminho. A declaração da classe pode definir /shipments, enquanto o método acrescenta /{id} e restringe a operação a GET. O pedido GET /shipments/42 pode corresponder; POST para o mesmo caminho não adquire esse contrato por partilhar a URL.

Antes de rever a lógica de negócio, escreve o método, o caminho efetivo e as condições relevantes. Inclui prefixos de contexto ou proxy quando existirem. Os exemplos desta aula assumem que o pedido já chegou ao MVC e que nenhuma camada de segurança ou customização altera a resolução.

Cada argumento tem uma origem

PathVariable obtém um valor capturado no caminho. RequestParam resolve parâmetros Servlet, incluindo query e formulário. RequestBody pede a leitura da mensagem por um conversor compatível. Trocar estas anotações muda o contrato de entrada; não é uma forma equivalente de escrever o mesmo endpoint.

Para /quotes/BOX-A?units=3, sku vem do caminho e units da query. O int exige conversão numérica, mas não significa que o valor seja positivo. A próxima aula acrescenta restrições explícitas. Ler corretamente um valor ainda não valida a regra que o negócio quer impor.

Ausência, repetição e formulários

Um RequestParam sem opção de ausência nem valor predefinido é obrigatório. Decide se a ausência é erro, se deve produzir um valor predefinido ou se tem significado próprio representado por Optional. Não deixes essa decisão dependente de uma suposição sobre o valor inicial de um tipo primitivo.

Se o cliente envia tag=urgent&tag=fragile, usa uma coleção para conservar a multiplicidade. Um mapa de valores escalares não exprime a mesma necessidade. Em formulários Servlet, o acesso aos parâmetros pode consumir o corpo durante o parsing; não planeies lê-lo depois livremente como se fosse um novo corpo JSON.

Entrada e saída negociadas

Content-Type identifica a representação enviada. Accept exprime as representações que o cliente aceita receber. Um POST pode enviar JSON e aceitar JSON, mas os dois cabeçalhos continuam a responder a perguntas diferentes. consumes restringe a entrada e produces a saída. Alterar Accept não corrige um corpo identificado incorretamente como text/plain.

Há uma regra importante na composição: consumes e produces definidos no método substituem os valores da classe para esse método. Uma classe que consome JSON pode ter um método que consome texto; não se exige um corpo que seja simultaneamente os dois formatos.

Estado, cabeçalhos e representação

ResponseEntity permite declarar o estado, os cabeçalhos e o corpo. Se a criação já terminou, created(uri).body(dto) exprime 201 com Location e um DTO. É ainda necessário um conversor capaz de escrever esse DTO no formato negociado. Um literal "201" devolvido no corpo não tem o mesmo significado.

O builder não executa a criação. Se a operação apenas foi aceite para processamento, o contrato deve descrever esse estado real. Também é possível falhar ao escrever a resposta depois de trabalho já realizado; a confirmação de efeitos e a política de repetição precisam de desenho próprio.

Ler a fase de rejeição

Num registo ilustrativo, PUT /shipments recebe 405 e o método POST de criação não é chamado. A primeira diferença a corrigir é o método HTTP. Noutro pedido, o caminho e método correspondem, mas text/plain encontra um contrato que só aceita JSON: a rejeição normal é 415. Quando o conflito está no Accept e na saída disponível, é 406.

Estes estados ajudam a formular uma hipótese; não substituem o tipo de exceção e a configuração real. Não atribuas uma espera por SQL a um pedido rejeitado antes do handler. Confirma também que o estado observado não foi produzido por um gateway ou handler personalizado.

Leitura guiada do controlador

No exemplo, considera três pedidos: units=3, units ausente e units=three. O primeiro permite construir QuoteInput; o segundo falha a obrigatoriedade; o terceiro falha a conversão numérica. A assinatura não contém uma restrição que impeça units=-2. Esse pedido pode ser legível e continuar a ser inadequado ao negócio.

Compara também Accept: application/xml com a declaração JSON. Se não existe outro handler aplicável, a preferência de saída não pode ser satisfeita. O exercício consiste em escolher e justificar estas interpretações; não exige executar o código nem demonstra um serviço de cotações completo.

// Separate source file: QuoteController.java, inside the application scan root.
// Servlet Spring MVC; compatible JSON converter available.
package com.acme.shipping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping(path = "/quotes", produces = "application/json")
public class QuoteController {
  public record QuoteInput(String sku, int units) {}

  @GetMapping("/{sku}")
  public QuoteInput inspect(
      @PathVariable("sku") String sku,
      @RequestParam("units") int units) {
    return new QuoteInput(sku, units);
  }
}
NA PRÁTICA

Interpreta GET /quotes/BOX-A?units=3 com Accept: application/json: o caminho fornece BOX-A e a query fornece 3. O DTO apenas mostra os argumentos. Não calcula preço, não reserva stock e ainda não impõe units positivo.

Armadilhas comuns

Confundir Accept com Content-Type; tratar parâmetros da query como corpo JSON; perder valores repetidos; anunciar criação apenas por escolher 201; concluir que todo o 400 corresponde à mesma falha.

Tópicos relacionados: Validação, erros e contratos de resposta · Contexto da aplicação e dependências · Âmbito de testes e evidência de integração

Leva esta ideia contigo

Identifica primeiro o pedido que pode chegar ao método, depois os dados que podem ser convertidos e finalmente a resposta que pode ser representada. O mapping não comprova autorização, persistência ou entrega.

Criar conta

Referência: Request mapping conditions · Spring Boot 4.1.1 / Spring Framework 7.0.9 / Java 25