🍃 Capítulo 20: Projeto Capstone Backend — Spring Boot 3+ REST API, Spring Security JWT e Deployment

🎯 Objetivos da Aula

Ao final desta aula, você será capaz de:

  1. Integrar todos os conceitos da Fase 09 (Spring Boot 3+, Spring Data JPA, PostgreSQL/H2, DTOs com Bean Validation, Spring Security e JWT).
  2. Construir uma REST API profissional em camadas (Controller, Service, Repository, Entity e DTO).
  3. Proteger endpoints da API com filtro de autenticação Stateless baseado em Tokens JWT (JSON Web Tokens).
  4. Empacotar a aplicação em um arquivo auto-contido .jar usando o Maven (mvn clean package) para deploy em nuvem.

🧠 1. Arquitetura da REST API Backend Spring Boot

A aplicação segue o padrão clássico em 4 camadas com isolamento de responsabilidades e segurança por interceptadores HTTP:

flowchart TD
    subgraph Client ["Cliente HTTP"]
        Req["Requisição REST\n(Header: Bearer JWT)"]
    end
    
    subgraph Security ["Filtro de Segurança Stateless"]
        JWTFilter["🔒 JwtAuthenticationFilter\n(Valida Assinatura do Token)"]
    end

    subgraph Layers ["Camadas da Aplicação Spring Boot"]
        Controller["⚙️ AlunoController (@RestController)"]
        Service["🧠 AlunoService (@Service - Regras de Negócio)"]
        Repository["🗄️ AlunoRepository (Spring Data JPA)"]
    end

    subgraph Database ["Camada de Dados"]
        DB[("Database PostgreSQL / H2")]
    end

    Req --> JWTFilter --> Controller --> Service --> Repository --> DB

💻 2. Código Completo do Projeto Integrado (AlunoService.java e AlunoController.java)

Camada de Serviço de Negócio (AlunoService.java):

package com.portal.backend.service;
 
import com.portal.backend.dto.AlunoDTO;
import com.portal.backend.dto.CriarAlunoDTO;
import com.portal.backend.entity.Aluno;
import com.portal.backend.exception.RegraNegocioException;
import com.portal.backend.repository.AlunoRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
 
import java.util.List;
import java.util.stream.Collectors;
 
@Service
public class AlunoService {
 
    @Autowired
    private AlunoRepository repository;
 
    @Transactional(readOnly = true)
    public List<AlunoDTO> listarTodos() {
        return repository.findAll().stream()
                .map(this::converterParaDTO)
                .collect(Collectors.toList());
    }
 
    @Transactional(readOnly = true)
    public AlunoDTO buscarPorId(Long id) {
        Aluno aluno = repository.findById(id)
                .orElseThrow(() -> new RegraNegocioException("Aluno não encontrado com o ID " + id));
        return converterParaDTO(aluno);
    }
 
    @Transactional
    public AlunoDTO salvar(CriarAlunoDTO dto) {
        if (repository.existsByMatricula(dto.getMatricula())) {
            throw new RegraNegocioException("Já existe um aluno cadastrado com a matrícula " + dto.getMatricula());
        }
 
        Aluno entidade = new Aluno();
        entidade.setMatricula(dto.getMatricula());
        entidade.setNome(dto.getNome());
        entidade.setEmail(dto.getEmail());
        entidade.setMediaGeral(dto.getMediaGeral());
        entidade.setAtivo(true);
 
        Aluno alunoSalvo = repository.save(entidade);
        return converterParaDTO(alunoSalvo);
    }
 
    private AlunoDTO converterParaDTO(Aluno aluno) {
        return new AlunoDTO(
                aluno.getId(),
                aluno.getMatricula(),
                aluno.getNome(),
                aluno.getEmail(),
                aluno.getMediaGeral(),
                aluno.isAtivo()
        );
    }
}

Camada de Controlador REST (AlunoController.java):

package com.portal.backend.controller;
 
import com.portal.backend.dto.AlunoDTO;
import com.portal.backend.dto.CriarAlunoDTO;
import com.portal.backend.service.AlunoService;
import jakarta.validation.Valid;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
 
import java.util.List;
 
@RestController
@RequestMapping("/api/v1/alunos")
public class AlunoController {
 
    @Autowired
    private AlunoService service;
 
    @GetMapping
    public ResponseEntity<List<AlunoDTO>> listarTodos() {
        List<AlunoDTO> lista = service.listarTodos();
        return ResponseEntity.ok(lista);
    }
 
    @GetMapping("/{id}")
    public ResponseEntity<AlunoDTO> buscarPorId(@PathVariable Long id) {
        AlunoDTO dto = service.buscarPorId(id);
        return ResponseEntity.ok(dto);
    }
 
    @PostMapping
    public ResponseEntity<AlunoDTO> cadastrar(@Valid @RequestBody CriarAlunoDTO dto) {
        AlunoDTO alunoSalvo = service.salvar(dto);
        return ResponseEntity.status(HttpStatus.CREATED).body(alunoSalvo);
    }
}

⚔️ 3. Desafios Práticos (Exercícios 30/50/20)

🥉 Nível Bronze (Fixação)

  1. Para que serve a anotação @Transactional(readOnly = true) nos métodos de busca da classe de serviço Spring?
  2. Como funciona o processo de empacotamento Maven (mvn clean package) ao gerar o arquivo executável .jar?

🥈 Nível Prata (Aplicação)

  1. Crie a classe DTO CriarAlunoDTO.java utilizando as anotações de validação de feijão de dados (@NotBlank, @Email, @NotNull, @Min, @Max) para impedir a submissão de dados inválidos ao endpoint POST /api/v1/alunos.

🥇 Nível Ouro (Desafio)

  1. Implemente a classe de tratamento global de exceções @RestControllerAdvice contendo um método com @ExceptionHandler(RegraNegocioException.class) que intercepte erros de negócio e retorne o código HTTP 400 Bad Request em um payload JSON padronizado.

💡 Gabaritos Sanfonados de Resposta

💡 Ver Gabarito do Nível Bronze
  1. A anotação @Transactional(readOnly = true) otimiza as sessões do Hibernate/JPA para consultas de leitura, desativando a checagem de alteração de objetos (dirty checking) e economizando recursos do banco de dados.
  2. O Maven limpa a pasta target/, compila todas as classes .java em Bytecode .class, executa a suíte de testes e empacota tudo junto com as dependências e o Tomcat embutido em um arquivo executável .jar.
💡 Ver Gabarito do Nível Prata & Ouro (Código Java DTO)
package com.portal.backend.dto;
 
import jakarta.validation.constraints.*;
 
public class CriarAlunoDTO {
 
    @NotBlank(message = "A matrícula é obrigatória.")
    private String matricula;
 
    @NotBlank(message = "O nome é obrigatório.")
    @Size(min = 3, max = 100, message = "O nome deve ter entre 3 e 100 caracteres.")
    private String nome;
 
    @NotBlank(message = "O e-mail é obrigatório.")
    @Email(message = "Formato de e-mail inválido.")
    private String email;
 
    @NotNull(message = "A média geral é obrigatória.")
    @Min(value = 0, message = "Média mínima é 0.0")
    @Max(value = 10, message = "Média máxima é 10.0")
    private Double mediaGeral;
 
    // Getters e Setters...
}

📚 Referências Teóricas Oficiais

  • SPRING FRAMEWORK DOCUMENTATION. Spring Security Architecture & JWT Authentication.
  • FOWLER, Martin. Patterns of Enterprise Application Architecture — Service Layer & Data Mapper.

⬅️ Capítulo Anterior (Segurança JWT) | Voltar ao Sumário da Fase 09 | Avançar para a Fase 10 (Projeto Full Stack) ➡️