DEV Community

Renato Cardoso Alves
Renato Cardoso Alves

Posted on • Updated on

A melhor forma de estruturar respostas de API no Spring Boot

Olá, desenvolvedores! Se você já se viu envolvido em respostas de API desorganizadas, está no lugar certo. Hoje, vamos conversar sobre as melhores maneiras de lidar e estruturar respostas de API no Spring Boot. Ao final deste artigo, você terá um plano claro e acionável para tornar suas APIs mais limpas, consistentes e amigáveis para os usuários.

Por que se importar com a estrutura da resposta da API?

Antes de mergulharmos nos detalhes, vamos abordar por que ter uma resposta de API bem estruturada é crucial. Uma estrutura de resposta consistente:

  • Melhora o tratamento de erros no lado do cliente: Sua equipe de frontend vai agradecer.
  • Aumenta a legibilidade e a manutenibilidade: Você (ou sua equipe) no futuro vai apreciar a clareza.
  • Simplifica o debug e o registro de logs: Identifique problemas de forma rápida e eficiente.

O que torna uma boa resposta de API?

Uma resposta de API bem estruturada deve ser:

  • Consistente: Formato uniforme em diferentes endpoints.
  • Informativa: Inclui dados relevantes, mensagens, códigos de status e códigos de erro.
  • Simples: Fácil de analisar e entender.

Criando a Estrutura de Resposta Ideal

1. Defina um Formato de Resposta Padrão

Comece criando um formato de resposta padrão que todas as suas APIs seguirão. Aqui está um formato simples e eficaz:

public class ApiResponse<T> {
    private boolean success;
    private String message;
    private T data;
    private List<String> errors;
    private int errorCode;
    private long timestamp;  // Adding timestamp for tracking
    private String path;     // Adding path to identify the API endpoint

    // Constructors, getters, and setters
}
Enter fullscreen mode Exit fullscreen mode

Entendendo Cada Campo:

1. success

  • Tipo: booleano
  • Descrição: Indica se a chamada à API foi bem-sucedida ou não
  • Por que usar: Determina rapidamente o resultado da requisição, simplificando a lógica no lado do cliente

2. message

  • Tipo: String
  • Descrição: Fornece uma mensagem legível para o resultado da chamada à API
  • Por que usar: Ajuda a fornecer feedback contextual ao cliente, sendo útil tanto em cenários de sucesso quanto de erro

3. data

  • Tipo: T
  • Descrição: Contém o payload da resposta, que pode ser qualquer tipo de dado
  • Por que usar: Entrega os dados reais solicitados pelo cliente

4. errors

  • Tipo: List
  • Descrição: Uma lista de mensagens de erro caso a chamada à API não tenha sido bem-sucedida
  • Por que usar: Fornece informações detalhadas sobre o que deu errado, sendo útil para o debug e feedback ao usuário

5. errorCode

  • Tipo: int
  • Descrição: Um código específico que representa o tipo de erro
  • Por que usar: Ajuda a categorizar erros de forma programática e a responder de maneira adequada

6. timestamp

  • Tipo: long
  • Descrição: O timestamp de quando a resposta foi gerada
  • Por que usar: Útil para registro de logs e acompanhamento do tempo das respostas, o que pode ajudar no debug e monitoramento

7. path

  • Tipo: String
  • Descrição: O endpoint da API que foi chamado
  • Por que usar: Ajuda a identificar qual endpoint da API gerou a resposta, sendo útil para depuração e registro de logs

Crie Métodos Utilitários para Respostas

Para manter o código DRY (Don't Repeat Yourself), vamos criar métodos utilitários para gerar respostas. Isso garante consistência e reduz a repetição de código boilerplate.

public class ResponseUtil {

    public static <T> ApiResponse<T> success(T data, String message, String path) {
        ApiResponse<T> response = new ApiResponse<>();
        response.setSuccess(true);
        response.setMessage(message);
        response.setData(data);
        response.setErrors(null);
        response.setErrorCode(0); // No error
        response.setTimestamp(System.currentTimeMillis());
        response.setPath(path);
        return response;
    }

    public static <T> ApiResponse<T> error(List<String> errors, String message, int errorCode, String path) {
        ApiResponse<T> response = new ApiResponse<>();
        response.setSuccess(false);
        response.setMessage(message);
        response.setData(null);
        response.setErrors(errors);
        response.setErrorCode(errorCode);
        response.setTimestamp(System.currentTimeMillis());
        response.setPath(path);
        return response;
    }

    public static <T> ApiResponse<T> error(String error, String message, int errorCode, String path) {
        return error(Arrays.asList(error), message, errorCode, path);
    }
}
Enter fullscreen mode Exit fullscreen mode

Implemente o Tratamento Global de Exceções

Tratar exceções de forma global garante que qualquer erro não tratado seja capturado e retornado no seu formato de resposta padrão. Utilize as anotações @ControllerAdvice e @ExceptionHandler para isso.

@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(Exception.class)
    public ResponseEntity<ApiResponse<Void>> handleException(HttpServletRequest request, Exception ex) {
        List<String> errors = Arrays.asList(ex.getMessage());
        ApiResponse<Void> response = ResponseUtil.error(errors, "An error occurred", 1000, request.getRequestURI()); // General error
        return new ResponseEntity<>(response, HttpStatus.INTERNAL_SERVER_ERROR);
    }

    @ExceptionHandler(ResourceNotFoundException.class)
    public ResponseEntity<ApiResponse<Void>> handleResourceNotFoundException(HttpServletRequest request, ResourceNotFoundException ex) {
        ApiResponse<Void> response = ResponseUtil.error(ex.getMessage(), "Resource not found", 1001, request.getRequestURI()); // Resource not found error
        return new ResponseEntity<>(response, HttpStatus.NOT_FOUND);
    }

    @ExceptionHandler(ValidationException.class)
    public ResponseEntity<ApiResponse<Void>> handleValidationException(HttpServletRequest request, ValidationException ex) {
        ApiResponse<Void> response = ResponseUtil.error(ex.getErrors(), "Validation failed", 1002, request.getRequestURI()); // Validation error
        return new ResponseEntity<>(response, HttpStatus.BAD_REQUEST);
    }

    // Handle other specific exceptions similarly
}
Enter fullscreen mode Exit fullscreen mode

Use o Formato de Resposta em Seus Controladores

Agora, vamos utilizar nossa estrutura de resposta padronizada em um controlador de exemplo.

@RestController
@RequestMapping("/api/products")
public class ProductController {

    @GetMapping("/{id}")
    public ResponseEntity<ApiResponse<Product>> getProductById(@PathVariable Long id, HttpServletRequest request) {
        // Fetch product by id (dummy code)
        Product product = productService.findById(id);
        if (product == null) {
            throw new ResourceNotFoundException("Product not found with id " + id);
        }
        ApiResponse<Product> response = ResponseUtil.success(product, "Product fetched successfully", request.getRequestURI());
        return new ResponseEntity<>(response, HttpStatus.OK);
    }

    @PostMapping
    public ResponseEntity<ApiResponse<Product>> createProduct(@RequestBody Product product, HttpServletRequest request) {
        // Create new product (dummy code)
        Product createdProduct = productService.save(product);
        ApiResponse<Product> response = ResponseUtil.success(createdProduct, "Product created successfully", request.getRequestURI());
        return new ResponseEntity<>(response, HttpStatus.CREATED);
    }

    // More endpoints...
}
Enter fullscreen mode Exit fullscreen mode

Códigos de Erro Comuns

Aqui está uma referência rápida para códigos de erro comuns que você pode usar (estes são apenas exemplos; você pode personalizá-los de acordo com seu projeto):

  • 1000: Erro geral
  • 1001: Recurso não encontrado
  • 1002: Falha na validação
  • 1003: Acesso não autorizado
  • 1004: Acesso proibido
  • 1005: Conflito (por exemplo, recurso duplicado)

Esses códigos de erro podem ser mantidos tanto no frontend quanto no backend para garantir um tratamento consistente de erros e fornecer feedback significativo aos usuários. Ao padronizar os códigos de erro, você simplifica o processo de tratamento de erros em diferentes camadas da sua aplicação, tornando mais fácil gerenciar e depurar problemas.

Concluindo

E aí está! Uma maneira clara e consistente de lidar com as respostas de API no Spring Boot. Implementando esses passos, você tornará suas APIs mais limpas e mais fáceis de manter. Além disso, sua equipe de frontend (e seu eu futuro) serão eternamente gratos.

Top comments (0)