📚 Pré-requisitos Teóricos: este projeto aplica conceitos ensinados em Especialização em Backend com Java e Spring Boot e na Matriz de Aplicações Full Stack. Recomendado revisar antes de começar.
Bem-vindo à Parte 01 da Trilha Prática de Aplicações Full Stack! Esta série é progressiva e acompanha a sua evolução ao longo dos módulos:
Nesta primeira etapa, o objetivo é compreender os fundamentos sem sobrecarga: você aprenderá o que é uma API, o que é REST, como o Spring Boot organiza o código em camadas (MVC) e como testar as operações de CRUD de forma 100% visual.
Antes de escrever qualquer linha de código em Java, um engenheiro de software profissional realiza o levantamento de requisitos. Criar software sem entender o que deve ser feito e quais critérios definem o sucesso leva a retrabalho, bugs e frustração.
Neste projeto didático, aplicamos as melhores práticas da Engenharia de Software para que você aprenda a relacionar cada necessidade de negócio a uma classe ou método no código.
Nosso cliente precisa de uma solução simples, centralizada e de alta performance para gerenciar afazeres cotidianos. No modelo ágil, expressamos essas necessidades no formato de Histórias de Usuário:
Os Requisitos Funcionais descrevem o que o sistema deve fazer, isto é, suas funcionalidades e comportamentos observáveis:
| ID | Nome do Requisito | Descrição Detalhada | Regra de Negócio Associada |
|---|---|---|---|
| RF-01 | Cadastrar Tarefa | O sistema deve criar um novo registro de tarefa a partir de uma descrição textual enviada. | A tarefa recém-criada deve iniciar com status concluida = false. O identificador (id) deve ser gerado automaticamente. |
| RF-02 | Listar Todas as Tarefas | O sistema deve retornar a coleção completa de tarefas cadastradas. | Se o banco estiver vazio, deve retornar uma lista vazia ([]) com status 200 OK. |
| RF-03 | Consultar Tarefa por ID | O sistema deve permitir a busca pontual de uma tarefa específica pelo seu ID. | Se o ID não existir, retornar erro 404 Not Found. |
| RF-04 | Atualizar Tarefa | O sistema deve permitir alterar a descrição e/ou o status de conclusão de uma tarefa existente. | Se o ID informado não existir na base, a operação deve ser rejeitada com 404 Not Found. |
| RF-05 | Alternar Status (Toggle) | O sistema deve fornecer uma operação simplificada para inverter o status de conclusão (true ⇄ false). |
Útil para checkboxes e ações de um clique em interfaces gráficas. |
| RF-06 | Excluir Tarefa | O sistema deve remover permanentemente uma tarefa a partir do seu identificador numérico. | Se o ID existir, remover e responder com 204 No Content. Se não existir, responder com 404 Not Found. |
Os Requisitos Não-Funcionais definem como o sistema deve se comportar, estabelecendo critérios de qualidade, arquitetura, usabilidade e tecnologia:
| ID | Categoria | Descrição do Critério | Justificativa Pedagógica & Técnica |
|---|---|---|---|
| RNF-01 | Arquitetura RESTful | A comunicação externa deve adotar o protocolo HTTP com URIs semânticas (/api/tarefas) e payloads JSON padrão. |
Permite que qualquer cliente (web, mobile, desktop) consuma a mesma API sem acoplamento. |
| RNF-02 | Separação de Camadas | O código deve seguir estritamente o padrão em camadas: Controller (exposição), Service (lógica), Repository (dados) e Entity (domínio). | Evita código espaguete e ensina a arquitetura corporativa padrão de mercado. |
| RNF-03 | Persistência Volátil (Zero-Config) | O sistema deve operar com banco de dados relacional em memória (H2 Database), sem exigir instalação de servidores MySQL/PostgreSQL locais. | Elimina barreiras de ambiente para alunos iniciantes; o banco sobe e desce com a aplicação. |
| RNF-04 | Dupla Interface (SSR + API) | A aplicação deve fornecer uma interface web renderizada no servidor (Thymeleaf) e uma API REST JSON pura no mesmo artefato executável. | Serve de ponte pedagógica para o aluno visualizar a aplicação funcionando antes de avançar para SPAs complexas como Angular. |
| RNF-05 | Feedback HTTP Semântico | Todas as respostas da API devem utilizar os códigos de status HTTP corretos (200 OK, 201 Created, 204 No Content, 404 Not Found). |
Garante aderência às boas práticas de Engenharia de Software para serviços web. |
O diagrama abaixo sintetiza como os atores (usuário via navegador ou cliente REST YARC) interagem com as fronteiras do sistema:
flowchart LR
User(["👤 Aluno / Usuário"])
subgraph Sistema ["📦 Sistema de Lista de Tarefas (Spring Boot)"]
direction TB
UC1(["UC-01: Cadastrar Tarefa (RF-01)"])
UC2(["UC-02: Listar Tarefas (RF-02)"])
UC3(["UC-03: Consultar por ID (RF-03)"])
UC4(["UC-04: Atualizar Tarefa (RF-04)"])
UC5(["UC-05: Alternar Conclusão (RF-05)"])
UC6(["UC-06: Excluir Tarefa (RF-06)"])
UC7(["UC-07: Inspecionar no H2 Console"])
end
User --> UC1
User --> UC2
User --> UC3
User --> UC4
User --> UC5
User --> UC6
User -.->|Perfil Admin| UC7
style Sistema fill:#f8fafc,stroke:#334155,stroke-width:2px
style UC1 fill:#e0f2fe,stroke:#0284c7
style UC2 fill:#e0f2fe,stroke:#0284c7
style UC3 fill:#e0f2fe,stroke:#0284c7
style UC4 fill:#e0f2fe,stroke:#0284c7
style UC5 fill:#e0f2fe,stroke:#0284c7
style UC6 fill:#fee2e2,stroke:#ef4444
style UC7 fill:#fef3c7,stroke:#f59e0b
A Matriz de Rastreabilidade garante que nenhum requisito fique sem implementação e que cada linha de código tenha uma justificativa clara de existência:
| Requisito | Camada de Domínio | Camada de Dados | Camada de Serviço | Exposição REST (API) | Interface Web (Thymeleaf) | Verificação / Teste |
|---|---|---|---|---|---|---|
| RF-01 | Tarefa.java |
TarefaRepository.save() |
TarefaService.criar() |
POST /api/tarefas |
Formulário <form method="post"> |
YARC: Teste POST (Status 201) |
| RF-02 | Tarefa.java |
TarefaRepository.findAll() |
TarefaService.listarTodas() |
GET /api/tarefas |
Tabela th:each="t : ${tarefas}" |
YARC: Teste GET (Status 200) |
| RF-03 | Tarefa.java |
TarefaRepository.findById() |
TarefaService.buscarPorId() |
GET /api/tarefas/{id} |
Não aplicável nesta fase | YARC: Teste GET/{id} (Status 200/404) |
| RF-04 | Tarefa.java |
TarefaRepository.save() |
TarefaService.atualizar() |
PUT /api/tarefas/{id} |
Modal / formulário de edição | YARC: Teste PUT (Status 200) |
| RF-05 | Tarefa.java |
TarefaRepository.save() |
TarefaService.alternarStatus() |
PUT /api/tarefas/{id} |
Link /tarefas/{id}/alternar |
Clique visual no checkbox/botão |
| RF-06 | Tarefa.java |
TarefaRepository.deleteById() |
TarefaService.deletar() |
DELETE /api/tarefas/{id} |
Link /tarefas/{id}/deletar |
YARC: Teste DELETE (Status 204) |
| RNF-01 | - | - | - | @RestController + JSON |
- | Inspecionar cabeçalho Content-Type |
| RNF-02 | @Entity |
@Repository |
@Service |
@RestController |
@Controller |
Inspeção visual da árvore de pacotes |
| RNF-03 | - | spring-boot-starter-data-jpa |
- | - | - | Acesso ao /h2-console |
| RNF-04 | - | - | - | TarefaRestController |
TarefaWebController + HTML |
Acesso a /tarefas no navegador |
Neste projeto, tudo roda dentro de um único serviço Spring Boot na porta 8080, oferecendo dois pontos de acesso que compartilham as mesmas regras de negócio:
flowchart TD
subgraph SpringBoot ["⚙️ Backend Spring Boot (Porta 8080)"]
direction TB
Service["🧠 TarefaService<br>(Regras de Negócio e CRUD)"]
Repo["🗄️ TarefaRepository<br>(Spring Data JPA)"]
DB[("💾 Banco H2<br>(Em Memória - testdb)")]
API_Ctrl["🔌 TarefaRestController<br>(@RestController - /api/tarefas)"]
Web_Ctrl["🖥️ TarefaWebController<br>(@Controller - /tarefas)"]
API_Ctrl -->|Chama| Service
Web_Ctrl -->|Chama| Service
Service -->|Consulta/Salva| Repo
Repo -->|SQL Automático| DB
end
subgraph Clientes ["👥 Como Você Irá Interagir"]
YARC["🧩 Extensão YARC (Chrome)<br>Dispara requisições e inspeciona JSON"]
Thymeleaf["🌐 Navegador Web<br>Interface gráfica HTML renderizada no servidor"]
H2Console["🔍 H2 Web Console<br>Visualiza a tabela TB_TAREFAS no banco"]
end
YARC <-->|HTTP JSON| API_Ctrl
Thymeleaf <-->|HTTP HTML| Web_Ctrl
H2Console -.->|SQL Direto| DB
style SpringBoot fill:#f9fbe7,stroke:#827717,stroke-width:2px
style Clientes fill:#e1f5fe,stroke:#0277bd,stroke-width:2px
/api/tarefas): Entrega dados puros em formato JSON. Ideal para clientes móveis, SPAs e integrações externas./tarefas): Renderiza páginas HTML dinâmicas com Thymeleaf diretamente no servidor (Server-Side Rendering)./h2-console): Interface administrativa para inspecionar as tabelas criadas na memória RAM.Antes de abrir o código, vamos esclarecer os quatro conceitos fundamentais da web moderna.
Imagine que você está em um restaurante:
/api/tarefas).JSON (JavaScript Object Notation) é o padrão universal que sistemas usam para conversar pela internet. É um formato de texto leve, legível por humanos e facilmente interpretado por qualquer linguagem (Java, TypeScript, Python, etc.).
Exemplo de uma tarefa representada em JSON:
{
"id": 1,
"descricao": "Aprender Spring Boot e REST",
"concluida": false
}
Toda aplicação de gerenciamento de dados realiza quatro operações básicas (Create, Read, Update, Delete). Na web, associamos cada operação a um método HTTP:
| Operação | Verbo HTTP | Ação na API | Analogia |
|---|---|---|---|
| Create (Criar) | POST |
Cadastra uma nova tarefa | Enviar um formulário preenchido |
| Read (Ler) | GET |
Consulta uma ou mais tarefas | Olhar a lista de tarefas |
| Update (Atualizar) | PUT |
Atualiza os dados de uma tarefa existente | Editar o texto ou marcar concluída |
| Delete (Excluir) | DELETE |
Remove uma tarefa do banco | Rasgar a anotação da tarefa |
No padrão arquitetural do Spring Boot, cada classe tem uma única responsabilidade:
Tarefa.java): Define a estrutura da tabela no banco (colunas id, descricao, concluida).TarefaRepository.java): Interface que conversa com o banco. O Spring Data JPA cria as consultas SQL (SELECT, INSERT, UPDATE, DELETE) automaticamente!TarefaService.java): Concentra a lógica de negócio. É o “cérebro” reutilizável.TarefaRestController.java e TarefaWebController.java): Recebe as requisições HTTP, chama o serviço e devolve a resposta apropriada (JSON ou página HTML).Neste tutorial prático autoguiado, cada passo possui um Ponto de Verificação (Checkpoint). Não avance para a etapa seguinte sem confirmar que o checkpoint atual foi atendido com sucesso.
Antes de gerar o projeto, abra o seu terminal (Prompt de Comando ou PowerShell no Windows) e verifique qual versão do JDK está instalada no seu computador:
java --version
A saída mostrará a versão ativa na sua máquina, por exemplo:
openjdk 17.0.10 ... $\rightarrow$ Você está usando o Java 17.openjdk 21.0.2 ... $\rightarrow$ Você está usando o Java 21.⚠️ Regra Fundamental de Compatibilidade: A versão do Java que você selecionar no Spring Initializr deve ser igual ou inferior à versão instalada na sua máquina! Se você selecionar Java 21 no site mas sua máquina só tiver o Java 17 instalado, a aplicação apresentará erro de compilação (
UnsupportedClassVersionError).
Acesse start.spring.io e configure os campos exatamente como indicado:
Maven (⚠️ Enfatizamos: escolha estritamente Maven, e NÃO Gradle, para utilizar o pom.xml e o wrapper mvnw padronizados neste curso).Java💡 Dica sobre Versões Estáveis: Evite versões com rótulos
(SNAPSHOT)ou(M1, M2 - Milestone), pois são versões em desenvolvimento. Escolha sempre a versão estável padrão marcada pelo site (atualmente na linha 4.0.x ou 4.1.x). Lembre-se que as versões 4.x exigem no mínimo Java 17 ou Java 21+.
br.com.cursoapi-springapi-springAPI REST e Interface Web Thymeleaf para Lista de Tarefasbr.com.curso.listadetarefas.apiJarjava --version.Dependências necessárias (adicione clicando em “ADD DEPENDENCIES”):
Spring Web (criação de endpoints REST e MVC)Spring Data JPA (persistência e mapeamento objeto-relacional)Thymeleaf (motor de templates para a interface web)H2 Database (banco de dados relacional em memória)Lombok (geração automática de getters, setters e construtores)Clique em GENERATE (ou pressione Ctrl + Enter) para baixar o arquivo compactado api-spring.zip.
Um dos erros mais comuns de quem está começando no Windows é criar “pastas gêmeas” ao extrair o .zip (como api-spring\api-spring):
api-spring.zip na sua pasta Downloads.C:\Projetos\ ou na pasta do curso).pom.xml, mvnw.cmd e a pasta src/. Se você abrir a pasta e encontrar outra pasta com o mesmo nome dentro dela, mova o conteúdo para que o pom.xml fique na raiz!pom.xmlO arquivo pom.xml gerado contém as dependências fundamentais:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>4.0.0</version> <!-- Ou a versao estavel 4.0.x / 4.1.x selecionada no site -->
<relativePath/>
</parent>
<groupId>br.com.curso</groupId>
<artifactId>api-spring</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>api-spring</name>
<description>API REST e Interface Web Thymeleaf para Lista de Tarefas</description>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>
🎯 Checkpoint de Verificação 1: Integridade da Fundação Maven
- O que você acabou de implementar: A casca do projeto com todas as dependências declaradas no
pom.xml.- Como validar agora: Abra o terminal na pasta do projeto (onde está o
pom.xml) e execute:.\mvnw.cmd compileO Maven baixará as dependências e emitirá a mensagem
[INFO] BUILD SUCCESS.- Reflexão de Engenharia de Software (RNF-02): Garantimos que o ecossistema Maven está integro antes de escrever código Java, evitando erros de compilação por pacotes ausentes.
O Spring Boot adota o padrão canônico do ecossistema Maven (Maven Standard Directory Layout). Cada pasta tem um papel estrito e fundamental:
| Pasta / Arquivo | Para que serve? | Regra de Ouro |
|---|---|---|
pom.xml |
O arquivo de manifesto do Maven. | Fica sempre na raiz do projeto. Declara dependências e versão do Java. |
mvnw e mvnw.cmd |
O Maven Wrapper executável. | Permite compilar e rodar o projeto sem que você precise instalar o Maven no Windows. |
src/main/java/ |
Onde mora todo o seu código-fonte Java. | Qualquer arquivo .java da sua aplicação DEVE ficar aqui dentro. |
src/main/resources/ |
Recursos de configuração e templates. | Contém o application.properties e a pasta templates/ do Thymeleaf. |
src/test/java/ |
Testes automatizados. | Onde ficam as classes de teste de validação. |
api-spring/
├── pom.xml ← Configurações e dependências Maven
├── mvnw ← Script de execução para Linux/macOS
├── mvnw.cmd ← Script de execução para Windows
├── .mvn/wrapper/maven-wrapper.properties
└── src/
├── main/
│ ├── java/
│ │ └── br/com/curso/listadetarefas/api/ ← Pacote Raiz da Aplicação
│ │ └── ListadetarefasApiApplication.java ← Classe Principal com o método main
│ └── resources/
│ └── application.properties ← Arquivo de propriedades e configuração
└── test/
└── java/
└── br/com/curso/listadetarefas/api/
└── ListadetarefasApiApplicationTests.java ← Teste básico de contexto
Observe com muita atenção onde fica o arquivo ListadetarefasApiApplication.java:
br.com.curso.listadetarefas.api.@SpringBootApplication presente nele possui um mecanismo embutido chamado Component Scan (Varredura de Componentes)..api.tarefa).⛔ O Erro nº 1 de Quem Começa com Spring Boot: Se você criar acidentalmente um pacote “fora” ou “ao lado” do pacote raiz (por exemplo:
br.com.curso.tarefaem vez debr.com.curso.listadetarefas.api.tarefa), o Spring Boot NÃO encontrará suas anotações@RestController,@Serviceou@Repository. A aplicação subirá sem erros, mas quando você tentar acessarhttp://localhost:8080/api/tarefas, receberá um erro404 Not Found! Regra: Todos os seus novos pacotes devem ser criados dentro debr.com.curso.listadetarefas.api.
Para ter a melhor experiência de desenvolvimento no VS Code, utilizaremos o pacote oficial da VMware:
Ctrl + Shift + X no Windows (ou clique no ícone de blocos/extensões na barra lateral esquerda).Spring Boot Extension Pack.application.properties: Conforme você digita spring.h2... ou server.port..., a extensão sugere os nomes válidos e exibe uma caixa explicando o que cada propriedade faz.@GetMapping("/api/tarefas"), a extensão exibe links diretos para testar o endpoint no navegador.File > Open Folder...).api-spring (aquela que contém o arquivo pom.xml).api-spring (ou selecione diretamente o arquivo pom.xml).src/main/java com um pequeno ícone azul de pacote, indicando que ela foi reconhecida como raiz de código-fonte.No Java, um pacote é escrito com notação de ponto: br.com.curso.listadetarefas.api.tarefa.
No disco rígido do Windows, isso corresponde a uma hierarquia de pastas aninhadas:
src\main\java\br\com\curso\listadetarefas\api\tarefa\
As IDEs modernas facilitam esse trabalho para que você não precise criar pasta por pasta manualmente.
tarefa:src > main > java > br.com.curso.listadetarefas.api.br.com.curso.listadetarefas.api.tarefa e aperte Enter.br.com.curso.listadetarefas.api.tarefa foi criado.br.com.curso.listadetarefas.api $\rightarrow$ New Package… $\rightarrow$ digite tarefa.src/main/java/br/com/curso/listadetarefas/api/, clique com o botão direito na pasta api $\rightarrow$ New Folder $\rightarrow$ digite tarefa.tarefa.Tarefa).TarefaRepository), clique na opção Interface na listinha logo abaixo da caixa de texto antes de apertar Enter.tarefa.NomeDaClasse.java).package br.com.curso.listadetarefas.api.tarefa;.application.propertiesO arquivo de configuração do Spring Boot fica sempre dentro de src/main/resources/. Localize-o na sua IDE e abra para edição:
api-spring/
└── src/
└── main/
└── resources/
└── application.properties ← [Configuração do Banco e Servidor]
Substitua todo o conteúdo do arquivo pelas configurações abaixo:
spring.application.name=api-spring
# Permite conexoes locais na porta padrao 8080
server.address=0.0.0.0
server.port=8080
# Habilita o console web do H2
spring.h2.console.enabled=true
spring.h2.console.path=/h2-console
# Configuracoes de conexao com o banco H2 em memoria
spring.datasource.url=jdbc:h2:mem:testdb
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=
# Dialeto Hibernate e inicializacao JPA
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
spring.jpa.defer-datasource-initialization=true
# Desabilita cache do Thymeleaf para atualizar paginas sem reiniciar
spring.thymeleaf.cache=false
🎯 Checkpoint de Verificação 2: Banco de Dados Volátil H2 e Porta do Servidor
- O que você acabou de implementar: A configuração do banco de dados em memória
jdbc:h2:mem:testdb, habilitação do console web (/h2-console) e definição da porta8080.- Como validar agora: Certifique-se de que o arquivo está salvo sem erros de digitação e localizado estritamente em
src/main/resources/application.properties.- Reflexão de Engenharia de Software (RNF-03): O banco de dados em memória garante portabilidade total — nenhum aluno precisa instalar MySQL, PostgreSQL ou Docker para este primeiro módulo.
Tarefa.java)Agora começaremos a criar as classes de negócio da aplicação.
Antes de escrever o código Java, vejamos a representação formal da arquitetura de dados segundo os padrões da Engenharia de Software:
classDiagram
direction TB
class Tarefa {
<<Entity>>
-Long id
-String descricao
-Boolean concluida
+getId() Long
+setId(Long id) void
+getDescricao() String
+setDescricao(String descricao) void
+isConcluida() Boolean
+setConcluida(Boolean concluida) void
}
note for Tarefa "Mapeada para a tabela 'tb_tarefas' via Jakarta Persistence (JPA)"
erDiagram
TB_TAREFAS {
bigint id PK "Identificador único (Auto Increment)"
varchar(255) descricao "Texto da tarefa (NOT NULL)"
boolean concluida "Estado de conclusão (DEFAULT false)"
}
tarefa dentro de br.com.curso.listadetarefas.api:
br.com.curso.listadetarefas.api $\rightarrow$ New > Package $\rightarrow$ digite tarefa.api $\rightarrow$ New Folder $\rightarrow$ digite tarefa.tarefa, crie o arquivo Tarefa.java:
tarefa $\rightarrow$ New > Java Class $\rightarrow$ Tarefa.tarefa $\rightarrow$ New File $\rightarrow$ Tarefa.java.src/main/java/br/com/curso/listadetarefas/api/
├── ListadetarefasApiApplication.java
└── tarefa/
└── Tarefa.java ← [NOVO: Entidade JPA / Tabela tb_tarefas]
Tarefa.javapackage br.com.curso.listadetarefas.api.tarefa;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
@Entity
@Table(name = "tb_tarefas")
public class Tarefa {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String descricao;
private boolean concluida;
}
@Entity: informa ao Spring Data JPA que esta classe é uma tabela relacional.@Table(name = "tb_tarefas"): define o nome físico da tabela no banco de dados.@Id e @GeneratedValue: definem a chave primária com auto-incremento (1, 2, 3…).@Data: anotação do Lombok que cria automaticamente getters, setters, equals, hashCode e toString.🎯 Checkpoint de Verificação 3: Mapeamento Objeto-Relacional (ORM)
- O que você acabou de implementar: A classe de modelo que representa o registro de uma tarefa no banco de dados.
- Como validar agora: Verifique se a sua IDE reconhece as anotações do Lombok (
@Data,@NoArgsConstructor,@AllArgsConstructor) sem erros de compilação ou linhas vermelhas.- Reflexão de Engenharia de Software (RF-01, RNF-02): A entidade encapsula o estado do domínio (
id,descricao,concluida). O Hibernate traduzirá essa classe automaticamente para uma tabela SQLTB_TAREFASna inicialização do sistema.
TarefaRepository.java)O repositório é uma Interface (e não uma classe comum) responsável por executar as operações no banco.
tarefa $\rightarrow$ New > Java Class $\rightarrow$ Digite TarefaRepository e clique na opção Interface na listinha logo abaixo.tarefa $\rightarrow$ New File $\rightarrow$ TarefaRepository.java.src/main/java/br/com/curso/listadetarefas/api/
├── ListadetarefasApiApplication.java
└── tarefa/
├── Tarefa.java
└── TarefaRepository.java ← [NOVO: Interface Spring Data JPA]
TarefaRepository.javapackage br.com.curso.listadetarefas.api.tarefa;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface TarefaRepository extends JpaRepository<Tarefa, Long> {
}
💡 A Mágica do Spring Data: Ao estender
JpaRepository<Tarefa, Long>, você ganha de graça métodos comofindAll(),findById(),save(),deleteById()eexistsById(), sem escrever uma única linha de SQL!
🎯 Checkpoint de Verificação 4: Abstração da Camada de Persistência
- O que você acabou de implementar: A interface de persistência que conecta o Java ao banco de dados H2.
- Como validar agora: Confirme que
TarefaRepositoryestendeJpaRepository<Tarefa, Long>e que o tipo do ID corresponde ao tipoLongda classeTarefa.- Reflexão de Engenharia de Software (RNF-02): Pelo padrão Repository Pattern, desacoplamos as regras de negócio dos comandos SQL brutos (
INSERT INTO...,SELECT *...). Se trocarmos de banco de dados no futuro, nenhuma linha de código acima desta interface precisará ser reescrita!
TarefaService.java)A camada de serviço é uma classe comum do Java anotada com @Service. Ela reúne as regras de negócio da nossa lista de tarefas.
tarefa $\rightarrow$ New > Java Class $\rightarrow$ TarefaService.tarefa $\rightarrow$ New File $\rightarrow$ TarefaService.java.src/main/java/br/com/curso/listadetarefas/api/
├── ListadetarefasApiApplication.java
└── tarefa/
├── Tarefa.java
├── TarefaRepository.java
└── TarefaService.java ← [NOVO: Camada de Serviço e Regras de Negócio]
TarefaService.javapackage br.com.curso.listadetarefas.api.tarefa;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Optional;
@Service
public class TarefaService {
private final TarefaRepository tarefaRepository;
// Injecao de dependencia via construtor (boa pratica recomendada)
public TarefaService(TarefaRepository tarefaRepository) {
this.tarefaRepository = tarefaRepository;
}
public List<Tarefa> listarTodas() {
return tarefaRepository.findAll();
}
public Optional<Tarefa> buscarPorId(Long id) {
return tarefaRepository.findById(id);
}
public Tarefa criar(Tarefa tarefa) {
return tarefaRepository.save(tarefa);
}
public Optional<Tarefa> atualizar(Long id, Tarefa dadosAtualizados) {
return tarefaRepository.findById(id).map(tarefaExistente -> {
tarefaExistente.setDescricao(dadosAtualizados.getDescricao());
tarefaExistente.setConcluida(dadosAtualizados.isConcluida());
return tarefaRepository.save(tarefaExistente);
});
}
public boolean deletar(Long id) {
if (tarefaRepository.existsById(id)) {
tarefaRepository.deleteById(id);
return true;
}
return false;
}
public Optional<Tarefa> alternarStatus(Long id) {
return tarefaRepository.findById(id).map(tarefa -> {
tarefa.setConcluida(!tarefa.isConcluida());
return tarefaRepository.save(tarefa);
});
}
}
🎯 Checkpoint de Verificação 5: Regras de Negócio e Encapsulamento
- O que você acabou de implementar: Todos os métodos de negócio que operam o CRUD de tarefas (
listarTodas,buscarPorId,criar,atualizar,deletarealternarStatus).- Como validar agora: Verifique se o construtor
public TarefaService(TarefaRepository tarefaRepository)injeta a dependência corretamente sem anotações desnecessárias.- Reflexão de Engenharia de Software (RF-01 a RF-06, RNF-02): A camada de serviço concentra a lógica de negócio independente de tecnologia de transporte. Se no futuro adicionarmos mensageria Kafka ou um cliente Desktop JavaFX, as regras de negócio permanecem intactas e reutilizáveis.
TarefaRestController.java)Agora criaremos o controlador REST, responsável por expor as rotas HTTP e devolver respostas puras em JSON para clientes como a extensão YARC.
tarefa $\rightarrow$ New > Java Class $\rightarrow$ TarefaRestController.tarefa $\rightarrow$ New File $\rightarrow$ TarefaRestController.java.src/main/java/br/com/curso/listadetarefas/api/
├── ListadetarefasApiApplication.java
└── tarefa/
├── Tarefa.java
├── TarefaRepository.java
├── TarefaService.java
└── TarefaRestController.java ← [NOVO: Endpoints REST JSON /api/tarefas]
TarefaRestController.javapackage br.com.curso.listadetarefas.api.tarefa;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/api/tarefas")
@CrossOrigin(origins = "*")
public class TarefaRestController {
private final TarefaService tarefaService;
public TarefaRestController(TarefaService tarefaService) {
this.tarefaService = tarefaService;
}
@GetMapping
public List<Tarefa> listar() {
return tarefaService.listarTodas();
}
@GetMapping("/{id}")
public ResponseEntity<Tarefa> buscarPorId(@PathVariable Long id) {
return tarefaService.buscarPorId(id)
.map(ResponseEntity::ok)
.orElse(ResponseEntity.notFound().build());
}
@PostMapping
public ResponseEntity<Tarefa> criar(@RequestBody Tarefa tarefa) {
Tarefa salva = tarefaService.criar(tarefa);
return ResponseEntity.status(HttpStatus.CREATED).body(salva);
}
@PutMapping("/{id}")
public ResponseEntity<Tarefa> atualizar(@PathVariable Long id, @RequestBody Tarefa tarefa) {
return tarefaService.atualizar(id, tarefa)
.map(ResponseEntity::ok)
.orElse(ResponseEntity.notFound().build());
}
@DeleteMapping("/{id}")
public ResponseEntity<Void> deletar(@PathVariable Long id) {
if (tarefaService.deletar(id)) {
return ResponseEntity.noContent().build();
}
return ResponseEntity.notFound().build();
}
}
🎯 Checkpoint de Verificação 6: Exposição da API REST Semântica
- O que você acabou de implementar: Os endpoints
/api/tarefascapazes de receber requisições HTTP (GET,POST,PUT,DELETE), serializar objetos Java para JSON e retornar códigos de status padronizados.- Como validar agora: Inicie a aplicação (veja a seção Como Executar) e verifique no log do console se a rota
Mapped [/api/tarefas]foi registrada pelo Spring MVC.- Reflexão de Engenharia de Software (RNF-01, RNF-05): Atendemos aos requisitos de arquitetura RESTful entregando respostas HTTP semânticas (
200 OK,201 Created,204 No Contente404 Not Found).
Em vez de memorizar comandos complexos de terminal no início, vamos usar uma ferramenta visual e intuitiva no próprio navegador para validar cada um dos Requisitos Funcionais (RF-01 a RF-06).
Inicie a aplicação Spring Boot (veja a seção Como Executar) e abra o YARC.
GEThttp://localhost:8080/api/tarefas200 OK e corpo de resposta com lista vazia: [].POSThttp://localhost:8080/api/tarefasContent-Type com valor application/json{
"descricao": "Estudar fundamentos de Spring Boot",
"concluida": false
}
201 Created e corpo com o ID gerado pelo banco:
{
"id": 1,
"descricao": "Estudar fundamentos de Spring Boot",
"concluida": false
}
POSThttp://localhost:8080/api/tarefasContent-Type: application/json{
"descricao": "Testar a API com a extensão visual YARC",
"concluida": true
}
201 Created com "id": 2.GEThttp://localhost:8080/api/tarefas200 OK trazendo o array JSON com as duas tarefas criadas:
[
{
"id": 1,
"descricao": "Estudar fundamentos de Spring Boot",
"concluida": false
},
{
"id": 2,
"descricao": "Testar a API com a extensão visual YARC",
"concluida": true
}
]
Vamos marcar a Tarefa 1 como concluída:
PUThttp://localhost:8080/api/tarefas/1Content-Type: application/json{
"descricao": "Estudar fundamentos de Spring Boot (Finalizado)",
"concluida": true
}
200 OK refletindo a alteração:
{
"id": 1,
"descricao": "Estudar fundamentos de Spring Boot (Finalizado)",
"concluida": true
}
Vamos remover a Tarefa 2:
DELETEhttp://localhost:8080/api/tarefas/2204 No Content (sem corpo de retorno, indicando sucesso na exclusão).Para comprovar que tudo o que você fez no YARC foi gravado no banco de dados real:
http://localhost:8080/h2-consolejdbc:h2:mem:testdbsaSELECT * FROM TB_TAREFAS;
| Checkpoint | Operação / Ação de Teste | Critério de Aceite | Status |
|---|---|---|---|
| CP-01 | GET /api/tarefas (Carga Inicial) |
Status HTTP 200 OK, retornando array JSON vazio []. |
PASS ✅ |
| CP-02 | POST /api/tarefas (Nova Tarefa) |
Status HTTP 201 Created, corpo com id: 1 gerado pelo H2. |
PASS ✅ |
| CP-03 | GET /api/tarefas (Listagem com Dados) |
Status HTTP 200 OK, lista contendo as tarefas cadastradas. |
PASS ✅ |
| CP-04 | PUT /api/tarefas/1 (Atualização / Conclusão) |
Status HTTP 200 OK, objeto retornado com concluida: true. |
PASS ✅ |
| CP-05 | DELETE /api/tarefas/2 (Exclusão) |
Status HTTP 204 No Content sem corpo de resposta. |
PASS ✅ |
| CP-06 | Auditoria SQL no H2 Console | Tabela TB_TAREFAS com dados 100% íntegros e consistentes. |
PASS ✅ |
Agora que você já compreendeu como a API REST opera com JSON, vamos construir uma interface visual amigável na mesma aplicação, usando Thymeleaf.
TarefaService que atende a API REST atende também a tela web.TarefaWebController.java)Agora que a API REST está pronta e testada no YARC, criaremos o controlador web responsável por entregar páginas HTML renderizadas no servidor pelo Thymeleaf.
tarefa $\rightarrow$ New > Java Class $\rightarrow$ TarefaWebController.tarefa $\rightarrow$ New File $\rightarrow$ TarefaWebController.java.src/main/java/br/com/curso/listadetarefas/api/
├── ListadetarefasApiApplication.java
└── tarefa/
├── Tarefa.java
├── TarefaRepository.java
├── TarefaService.java
├── TarefaRestController.java
└── TarefaWebController.java ← [NOVO: Controlador Web Thymeleaf /tarefas]
TarefaWebController.javapackage br.com.curso.listadetarefas.api.tarefa;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;
@Controller
@RequestMapping("/tarefas")
public class TarefaWebController {
private final TarefaService tarefaService;
public TarefaWebController(TarefaService tarefaService) {
this.tarefaService = tarefaService;
}
@GetMapping
public String listarTarefas(Model model) {
model.addAttribute("tarefas", tarefaService.listarTodas());
model.addAttribute("novaTarefa", new Tarefa());
return "tarefas"; // Nome do template: tarefas.html
}
@PostMapping
public String criarTarefa(@ModelAttribute("novaTarefa") Tarefa tarefa) {
if (tarefa.getDescricao() != null && !tarefa.getDescricao().trim().isEmpty()) {
tarefaService.criar(tarefa);
}
return "redirect:/tarefas";
}
@PostMapping("/{id}/alternar")
public String alternarStatus(@PathVariable Long id) {
tarefaService.alternarStatus(id);
return "redirect:/tarefas";
}
@PostMapping("/{id}/excluir")
public String excluirTarefa(@PathVariable Long id) {
tarefaService.deletar(id);
return "redirect:/tarefas";
}
}
src/main/resources/templates/tarefas.html)O Thymeleaf busca automaticamente seus arquivos HTML dentro da pasta src/main/resources/templates/.
src > main > resources.resources $\rightarrow$ New > Directory $\rightarrow$ digite templates e aperte Enter.templates $\rightarrow$ New > HTML File (ou File) $\rightarrow$ digite tarefas.html.src/main/resources.resources $\rightarrow$ New Folder $\rightarrow$ digite templates.templates $\rightarrow$ New File $\rightarrow$ digite tarefas.html.resources:src/main/resources/
├── application.properties
└── templates/
└── tarefas.html ← [NOVO: Página HTML com Thymeleaf e Bootstrap 5]
tarefas.html<!DOCTYPE html>
<html lang="pt-BR" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Lista de Tarefas — Spring Boot + Thymeleaf</title>
<!-- Bootstrap 5 CSS via CDN -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
<style>
body { background-color: #f8f9fa; }
.card-custom { border-radius: 12px; box-shadow: 0 4px 12px rgba(0,0,0,0.05); }
.completed-task { text-decoration: line-through; color: #6c757d; }
</style>
</head>
<body>
<div class="container py-5" style="max-width: 800px;">
<div class="text-center mb-4">
<h1 class="display-6 fw-bold text-primary">📝 Lista de Tarefas</h1>
<p class="text-muted">
Interface renderizada no servidor (SSR) com <strong>Thymeleaf</strong> e integrada ao mesmo <code>TarefaService</code> da API REST.
</p>
</div>
<!-- Formulario de Cadastro -->
<div class="card card-custom mb-4 border-0">
<div class="card-body p-4">
<h5 class="card-title fw-semibold mb-3">➕ Adicionar Nova Tarefa</h5>
<form th:action="@{/tarefas}" th:object="${novaTarefa}" method="post" class="d-flex gap-2">
<input type="text" th:field="*{descricao}" class="form-control form-control-lg"
placeholder="O que você precisa fazer?" required autofocus>
<button type="submit" class="btn btn-primary btn-lg px-4">Adicionar</button>
</form>
</div>
</div>
<!-- Tabela de Tarefas -->
<div class="card card-custom border-0 mb-4">
<div class="card-body p-4">
<h5 class="card-title fw-semibold mb-3">📋 Minhas Tarefas</h5>
<div th:if="${#lists.isEmpty(tarefas)}" class="alert alert-info mb-0">
ℹ️ Nenhuma tarefa cadastrada ainda! Adicione uma tarefa acima ou use o <strong>YARC</strong> para enviar um <code>POST</code> na API.
</div>
<div th:unless="${#lists.isEmpty(tarefas)}" class="table-responsive">
<table class="table table-hover align-middle mb-0">
<thead class="table-light">
<tr>
<th style="width: 80px;">#</th>
<th>Descrição</th>
<th style="width: 130px;">Status</th>
<th style="width: 180px;" class="text-end">Ações</th>
</tr>
</thead>
<tbody>
<tr th:each="tarefa : ${tarefas}">
<td class="fw-bold text-muted" th:text="${tarefa.id}">1</td>
<td>
<span th:text="${tarefa.descricao}"
th:classappend="${tarefa.concluida} ? 'completed-task' : ''">
Descrição
</span>
</td>
<td>
<span th:if="${tarefa.concluida}" class="badge bg-success">Concluída</span>
<span th:unless="${tarefa.concluida}" class="badge bg-warning text-dark">Pendente</span>
</td>
<td class="text-end">
<div class="d-inline-flex gap-1">
<form th:action="@{/tarefas/{id}/alternar(id=${tarefa.id})}" method="post" class="d-inline">
<button type="submit" class="btn btn-sm btn-outline-secondary"
th:text="${tarefa.concluida ? 'Reabrir' : 'Concluir'}">
Concluir
</button>
</form>
<form th:action="@{/tarefas/{id}/excluir(id=${tarefa.id})}" method="post" class="d-inline">
<button type="submit" class="btn btn-sm btn-outline-danger">Excluir</button>
</form>
</div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<!-- Links Uteis -->
<div class="row g-3">
<div class="col-md-6">
<div class="p-3 bg-white card-custom text-center">
<h6 class="fw-bold text-secondary">🔌 API REST (JSON)</h6>
<a href="/api/tarefas" target="_blank" class="btn btn-sm btn-outline-primary mt-1">Acessar /api/tarefas</a>
</div>
</div>
<div class="col-md-6">
<div class="p-3 bg-white card-custom text-center">
<h6 class="fw-bold text-secondary">🗄️ Console do H2</h6>
<a href="/h2-console" target="_blank" class="btn btn-sm btn-outline-secondary mt-1">Abrir /h2-console</a>
</div>
</div>
</div>
</div>
</body>
</html>
🎯 Checkpoint de Verificação 7: Interface Gráfica Monolítica e Renderização Server-Side
- O que você acabou de implementar: O template HTML Thymeleaf com Bootstrap 5 (
tarefas.html) e o controlador@Controller(TarefaWebController.java).- Como validar agora: Com a aplicação em execução, acesse
http://localhost:8080/tarefas. A página deve abrir com o visual estilizado, lista de tarefas e formulário funcional.- Reflexão de Engenharia de Software (RNF-04): A mesma base de código agora oferece uma experiência de usuário rica no navegador sem depender de frameworks JavaScript complexos, ao mesmo tempo em que mantém a API REST JSON ativa para clientes externos.
http://localhost:8080/tarefasGET http://localhost:8080/api/tarefas. A tarefa cadastrada pela interface Thymeleaf aparece no JSON da API, comprovando que ambas compartilham a mesma base de dados H2!Ao concluir todos os passos, a sua pasta api-spring deve estar organizada exatamente desta forma:
api-spring/
├── pom.xml ← Configurações e dependências Maven
├── mvnw ← Script executável do Maven para Linux/macOS
├── mvnw.cmd ← Script executável do Maven para Windows
├── .mvn/
│ └── wrapper/
│ └── maven-wrapper.properties
└── src/
├── main/
│ ├── java/
│ │ └── br/com/curso/listadetarefas/api/ ← Pacote Raiz da Aplicação (Component Scan)
│ │ ├── ListadetarefasApiApplication.java ← Ponto de entrada (método main do Spring Boot)
│ │ └── tarefa/ ← Subpacote com as classes do módulo de tarefas
│ │ ├── Tarefa.java ← Entidade JPA (tabela relacional tb_tarefas)
│ │ ├── TarefaRepository.java ← Interface Spring Data JPA para acesso ao banco
│ │ ├── TarefaService.java ← Camada de regras de negócio e operações CRUD
│ │ ├── TarefaRestController.java ← Endpoints da API REST em JSON (/api/tarefas)
│ │ └── TarefaWebController.java ← Rotas da Interface Web Thymeleaf (/tarefas)
│ └── resources/
│ ├── application.properties ← Propriedades do servidor, porta 8080 e banco H2
│ └── templates/ ← Pasta de templates do Thymeleaf
│ └── tarefas.html ← Página web renderizada no servidor (Bootstrap 5)
└── test/
└── java/
└── br/com/curso/listadetarefas/api/
└── ListadetarefasApiApplicationTests.java ← Teste automatizado de inicialização do contexto
🎯 Checklist de Validação da Estrutura:
- Todos os arquivos
.javaestão dentro desrc/main/java/br/com/curso/listadetarefas/api/(ou no subpacotetarefa)? ✅- O arquivo
application.propertiesestá dentro desrc/main/resources/? ✅- O arquivo
tarefas.htmlestá dentro desrc/main/resources/templates/? ✅- O arquivo
pom.xmlestá na raiz da pastaapi-spring? ✅
Você pode iniciar o servidor de três maneiras simples, dependendo de como prefere trabalhar:
Se você instalou o Spring Boot Extension Pack da VMware:
api-spring.ListadetarefasApiApplication.java dentro de src/main/java/br/com/curso/listadetarefas/api/.public static void main(String[] args), você verá um pequeno ícone de seta verde ▶️ na margem esquerda (gutter).Se preferir rodar via linha de comando:
pom.xml:
cd catalogo_projetos/fullstack_tarefas_01_spring_api/api-spring
.\mvnw.cmd spring-boot:run
./mvnw spring-boot:run
Quando aparecer nos logs a mensagem Started ListadetarefasApiApplication in X.XXX seconds, o servidor estará pronto em http://localhost:8080.
| Sintoma | Causa Mais Provável | Solução |
|---|---|---|
Port 8080 was already in use |
Outro processo ou servidor já está usando a porta 8080. | No application.properties, altere para server.port=8081 e acesse via http://localhost:8081. |
UnsupportedClassVersionError (class file version XX.0 vs YY.0) |
A versão do Java escolhida no site é mais nova que o JDK instalado na máquina. | Execute java --version no terminal. No pom.xml, altere <java.version> para corresponder ao seu JDK (ex: 17 ou 21). |
Database "mem:testdb" not found |
A URL digitada no console do H2 está incorreta. | No campo JDBC URL da tela de login, certifique-se de digitar exatamente jdbc:h2:mem:testdb. |
| O Lombok não gera os métodos | A IDE não está com o processamento de anotações habilitado. | No IntelliJ IDEA: vá em Settings > Build, Execution, Deployment > Compiler > Annotation Processors e marque Enable annotation processing. |
YARC retorna 415 Unsupported Media Type no POST |
Esqueceu de informar o cabeçalho Content-Type. |
No YARC, adicione o Header: Content-Type: application/json. |
Agora você compreende a diferença fundamental entre as duas formas de construir para a web:
| Critério | Thymeleaf (Server-Side Rendering) | API REST + Angular (Single Page Application) |
|---|---|---|
| Onde a tela é montada? | No servidor Java (Spring Boot gera o HTML completo). | No navegador do usuário (Angular roda em JavaScript). |
| O que trafega na rede? | Documentos HTML inteiros a cada clique. | Apenas dados brutos em JSON (muito mais leve e rápido). |
| Flexibilidade de Clientes | Atende apenas navegadores web. | Atende Web, Celular (Android/iOS), Desktop e relógios inteligentes. |
| Complexidade | Simples, tudo no mesmo projeto. | Maior, requer dois projetos separados (Backend + Frontend). |
Na Parte 02, daremos o próximo salto evolutivo: conectaremos uma SPA reativa e ultrarrápida em Svelte (JavaScript puro) consumindo exatamente a API REST que você acabou de criar!