🍃 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:
- Integrar todos os conceitos da Fase 09 (Spring Boot 3+, Spring Data JPA, PostgreSQL/H2, DTOs com Bean Validation, Spring Security e JWT).
- Construir uma REST API profissional em camadas (
Controller,Service,Repository,EntityeDTO). - Proteger endpoints da API com filtro de autenticação Stateless baseado em Tokens JWT (JSON Web Tokens).
- Empacotar a aplicação em um arquivo auto-contido
.jarusando 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)
- Para que serve a anotação
@Transactional(readOnly = true)nos métodos de busca da classe de serviço Spring? - Como funciona o processo de empacotamento Maven (
mvn clean package) ao gerar o arquivo executável.jar?
🥈 Nível Prata (Aplicação)
- Crie a classe DTO
CriarAlunoDTO.javautilizando 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 endpointPOST /api/v1/alunos.
🥇 Nível Ouro (Desafio)
- Implemente a classe de tratamento global de exceções
@RestControllerAdvicecontendo um método com@ExceptionHandler(RegraNegocioException.class)que intercepte erros de negócio e retorne o código HTTP400 Bad Requestem um payload JSON padronizado.
💡 Gabaritos Sanfonados de Resposta
💡 Ver Gabarito do Nível Bronze
- 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. - O Maven limpa a pasta
target/, compila todas as classes.javaem 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) ➡️