Aula 03 - Introdução às APIS REST e ao Spring Boot


Nesta aula apresentaremos uma introdução aos conceitos basilares para construção de uma API REST para gerenciar uma lista de contatos utilizando Spring Boot. A ideia é demonstrar, passo a passo, como criar um projeto Spring Boot e expor métodos que permitem a criação, consulta, atualização e exclusão de contatos (operações conhecidas como CRUD). Antes disso, entretanto, é preciso definir alguns conceitos.

1. O que é uma API?

Uma API (Application Programming Interface) é um conjunto de regras e definições que permitem que diferentes sistemas de software se comuniquem entre si. Elas atuam como intermediárias que possibilitam que um sistema solicite e envie dados para outro, de maneira estruturada e padronizada.

Dentre os diversos estilos de arquitetura para APIs, uma das mais populares e amplamente utilizadas hoje em dia é a API REST (Representational State Transfer). Esse padrão se baseia nos princípios da web, utilizando o protocolo HTTP para a comunicação entre sistemas distribuídos.


1.1 O que é uma API REST?

O termo REST (Representational State Transfer) foi criado por Roy Fielding em sua tese de doutorado em 2000. Ele descreveu um conjunto de princípios arquiteturais que utilizam os padrões já estabelecidos na web para criar sistemas escaláveis e eficientes.

Uma API REST segue esses princípios, permitindo que os dados sejam manipulados através de recursos (resources), representados por URLs (Uniform Resource Locators) e acessados usando os métodos HTTP padrão:

Esses métodos garantem que a API siga uma estrutura previsível e intuitiva para desenvolvedores que a utilizam. Isso acontece pois o uso adequado dos métodos HTTP em APIs REST garante padronização, previsibilidade e intuitividade para os desenvolvedores que consomem a API. Ou seja, ao interagir com qualquer API REST bem projetada, um desenvolvedor já pode inferir como fazer requisições apenas conhecendo os métodos e os recursos expostos.

Se uma API segue corretamente os padrões REST, um desenvolvedor não precisa consultar a documentação sempre que for fazer uma chamada, pois os métodos HTTP seguem uma convenção universal.

1.2 Exemplo de Convenção em APIs REST

Vamos imaginar uma API para gerenciar usuários (users). Seguindo a convenção REST, os endpoints e métodos HTTP devem funcionar assim:

Ação Desejada Método HTTP Caminho (URI) Corpo da Requisição Resposta Esperada
Obter todos os usuários GET /users ❌ (sem corpo) Lista de usuários (JSON)
Obter um usuário pelo ID GET /users/{id} ❌ (sem corpo) JSON do usuário
Criar um novo usuário POST /users ✅ JSON com os dados do usuário JSON do usuário criado com ID gerado
Atualizar todo um usuário existente PUT /users/{id} ✅ JSON com todos os campos do usuário JSON do usuário atualizado
Atualizar parcialmente um usuário PATCH /users/{id} ✅ JSON com apenas os campos alterados JSON do usuário atualizado
Excluir um usuário DELETE /users/{id} ❌ (sem corpo) Resposta vazia (status HTTP 204)

Por que isso faz sentido?

Se um desenvolvedor encontrar uma API com o recurso products (produtos), ele já pode inferir como a API funciona sem ler a documentação, pois seguirá o mesmo padrão:

Ação Desejada Método HTTP Caminho (URI)
Obter todos os produtos GET /products
Obter um produto pelo ID GET /products/{id}
Criar um novo produto POST /products
Atualizar um produto PUT /products/{id}
Atualizar um campo do produto PATCH /products/{id}
Excluir um produto DELETE /products/{id}

Isso garante que, se um desenvolvedor aprender a consumir uma API REST, ele poderá consumir qualquer outra API REST bem estruturada sem precisar reaprender cada API do zero. Na seção 2.5 é apresentado o código fonte da implementação de um Controller de uma aplicação Spring Boot que segue a convenção descrita acima.

Ainda em relação à convenção, é importante o uso adequado dos verbos PUT e PATCH.

1.3 Diferença entre PUT e PATCH

Tanto o PUT quanto o PATCH são usados para atualizar recursos, mas com diferenças importantes:

Método Propósito Tipo de Atualização Exemplo de Uso
PUT Atualiza um recurso inteiro Substitui completamente os dados Atualizar todas as informações de um usuário
PATCH Atualiza parcialmente um recurso Modifica apenas os campos enviados na requisição Atualizar apenas o e-mail de um usuário

Exemplo de Requisição PUT

Se temos um usuário cadastrado assim:

{
  "id": 1,
  "nome": "João Silva",
  "email": "joao@email.com",
  "telefone": "9999-9999"
}

E enviamos um PUT com este corpo:

{
  "id": 1,
  "nome": "Maria Silva",
  "email": "maria@email.com",
  "telefone": "8888-8888"
}

O registro original será completamente substituído, mesmo que não tenha havido mudanças no ID.

Exemplo de Requisição PATCH

Se enviarmos um PATCH apenas com:

{
  "email": "email_atualizado@email.com"
}

Apenas o campo email será alterado, e os outros campos permanecerão inalterados.


1.4 Utilização de APIs REST

Em relação à aplicabilidade, as APIs REST são amplamente utilizadas em diversos cenários do desenvolvimento de software, incluindo:

  1. Aplicações Web e Mobile:

    • APIs REST são a base para aplicativos móveis e front-ends modernos, permitindo que o cliente (navegador ou aplicativo) se comunique com servidores back-end.
    • Exemplo: O aplicativo de um banco acessa os dados do usuário através de uma API REST.
  2. Integração entre Sistemas:

    • Diferentes sistemas podem se comunicar via APIs REST, eliminando a necessidade de compartilhamento de banco de dados.
    • Exemplo: Um ERP de uma empresa pode se integrar ao sistema de contabilidade por meio de uma API.
  3. Serviços em Nuvem e IoT:

    • Dispositivos IoT (Internet das Coisas) frequentemente usam APIs REST para enviar dados para servidores.
    • Exemplo: Um smartwatch pode enviar informações de batimentos cardíacos para uma API REST na nuvem.
  4. Plataformas de Terceiros:

    • Muitas empresas oferecem APIs REST para que terceiros utilizem seus serviços de forma programática.
    • Exemplo: O Google Maps fornece APIs REST para que desenvolvedores integrem mapas em seus aplicativos.

1.5 Vantagens das APIs REST

  1. Simplicidade e Facilidade de Uso

    • As APIs REST utilizam o protocolo HTTP, que é amplamente conhecido e usado na web.
    • O uso de JSON como formato de dados facilita a leitura e escrita, tornando a comunicação leve e eficiente.
  2. Escalabilidade

    • Por serem stateless (não armazenam estado entre requisições), as APIs REST facilitam a escalabilidade horizontal, permitindo múltiplos servidores processando requisições simultaneamente.
  3. Flexibilidade e Independência

    • REST permite que clientes em diferentes tecnologias (React, Angular, iOS, Android) consumam a mesma API sem alterações no servidor.
  4. Padronização e Interoperabilidade

    • Como segue padrões bem definidos, uma API REST pode ser usada por qualquer cliente que compreenda HTTP.
  5. Cacheável

    • APIs REST permitem o uso eficiente de cache, reduzindo a carga no servidor e melhorando a performance.

É importante notar, entretanto, que não basta fazer as chamadas dos métodos HTTP para que se tenha uma API REST.

1.6 Modelo de Maturidade de Richardson

Nesse sentido, o Modelo de Maturidade de Richardson (RMM - Richardson Maturity Model) foi criado pelo cientista da computação Leonard Richardson e apresentado em 2008 durante uma palestra na QCon, uma conferência sobre desenvolvimento de software. Esse modelo propõe uma forma de avaliar o nível de conformidade de uma API com os princípios REST, ajudando a medir quão bem uma API segue a filosofia RESTful descrita por Roy Fielding em sua tese de doutorado em 2000.

A motivação principal desse modelo é a observação de que nem todas as APIs chamadas de "REST" realmente seguem os princípios REST. Muitas APIs usam apenas HTTP como meio de transporte, mas continuam operando como sistemas antigos, sem aproveitar os benefícios reais do REST. O modelo de Richardson classifica APIs em quatro níveis de maturidade, destacando aspectos como a organização de recursos, o uso correto dos métodos HTTP e a adoção de hipermídia (HATEOAS).


Os Quatro Níveis do Modelo de Richardson

O modelo define quatro níveis de maturidade, numerados de 0 a 3, onde cada nível indica uma progressão rumo a uma API verdadeiramente RESTful.

Nível 0 - "O Ponto de Entrada Único" (The Swamp of POX - Plain Old XML)

Neste nível, a API não utiliza os conceitos REST. Em vez disso, ela usa HTTP apenas como meio de transporte para mensagens genéricas, muitas vezes enviando e recebendo dados em formatos como XML ou JSON, sem aproveitar a estrutura do protocolo HTTP.

Exemplo Histórico: APIs Baseadas em RPC ou SOAP


Nível 1 - "Recursos" (Resources)

No Nível 1, a API começa a organizar seus dados em recursos individuais e cada recurso recebe uma URL única. No entanto, os métodos HTTP ainda não são utilizados corretamente, e a API continua tratando HTTP apenas como um meio de transporte.

Exemplo Histórico: APIs Baseadas em XML-RPC


Nível 2 - "Uso Correto de Verbos HTTP" (HTTP Verbs)

Aqui, a API começa a utilizar corretamente os métodos HTTP (GET, POST, PUT, DELETE, PATCH). Isso significa que as operações são realizadas de maneira semântica:

A partir deste nível, a API se torna mais intuitiva e previsível, pois os clientes podem deduzir como interagir com ela sem precisar consultar documentação detalhada.

Exemplo Histórico: APIs RESTful do Twitter e Facebook


Nível 3 - "HATEOAS" (Hypermedia as the Engine of Application State)

O nível mais avançado da maturidade REST adiciona um conceito chamado HATEOAS (Hypermedia as the Engine of Application State). Isso significa que a API não apenas expõe recursos, mas também fornece informações sobre como interagir com esses recursos dinamicamente.

Exemplo Histórico: API REST da Amazon (2011)


Ou seja...

O Modelo de Maturidade de Richardson nos ajuda a avaliar o quão RESTful uma API realmente é. Ele permite entender como a evolução das APIs foi impulsionada pela necessidade de escalabilidade, flexibilidade e simplicidade.

Nível Característica Exemplo Histórico
0 Apenas HTTP como transporte (SOAP/XML-RPC) APIs SOAP do governo dos EUA (anos 2000)
1 Recursos identificáveis por URL, mas sem uso adequado de métodos HTTP XML-RPC do Movable Type (2001)
2 Uso correto dos métodos HTTP (GET, POST, PUT, DELETE, PATCH) API REST do Twitter (2006)
3 HATEOAS, permitindo descoberta dinâmica API REST da Amazon (2011)

Hoje, a maioria das APIs modernas opera no nível 2, mas algumas empresas estão adotando nível 3 com HATEOAS, especialmente em ambientes complexos, como microserviços.


1.7 Comparação: REST vs SOAP vs RPC

REST vs SOAP (Simple Object Access Protocol)

Característica REST SOAP
Protocolo HTTP HTTP, SMTP, TCP
Formato de Dados JSON, XML Apenas XML
Facilidade de Uso Simples e flexível Estruturado e verboso
Desempenho Alto (leve) Baixo (mais pesado)
Escalabilidade Fácil de escalar Difícil de escalar
Cache Sim Não
Segurança Depende da implementação (HTTPS, JWT, OAuth) Segurança robusta integrada (WS-Security)
Utilização Aplicações web modernas, microserviços Sistemas bancários, integração empresarial

REST vs RPC (Remote Procedure Call)

Característica REST RPC
Conceito Manipulação de recursos Chamada direta de funções remotas
Formato de Comunicação JSON, XML Pode ser binário (gRPC, Thrift) ou JSON
Independência de Plataforma Alta Média (pode exigir bibliotecas específicas)
Simplicidade Simples, segue HTTP Pode ser complexo
Utilização Aplicações web, microsserviços Comunicação de alto desempenho entre serviços

1.8 Boas Práticas ao Criar APIs REST 📌

Criar uma API REST eficiente, intuitiva e escalável vai além de simplesmente expor endpoints HTTP. Seguir boas práticas melhora a usabilidade, manutenção, segurança e desempenho da API, tornando-a mais fácil de integrar com outras aplicações. A seguir, apresentamos algumas diretrizes fundamentais para a construção de APIs REST profissionais:

1️. Use Substantivos nos Endpoints e Evite Verbos

Os endpoints representam recursos, portanto, devem ser nomes de substantivos no plural, e não ações ou verbos.

Certo:

GET /contacts        → Obtém todos os contatos  
GET /contacts/{id}   → Obtém um contato específico  
POST /contacts       → Cria um novo contato  
DELETE /contacts/{id} → Remove um contato  

Errado:

GET /getContacts      → Não precisa do verbo "get", pois o método HTTP já indica a ação  
POST /createContact   → O verbo "create" é desnecessário, pois o método POST já sugere criação  
DELETE /removeContact → O verbo "remove" também é desnecessário  

💡 Regra geral: O método HTTP já indica a ação (GET para buscar, POST para criar, DELETE para remover, etc.), então o endpoint deve apenas representar o recurso.

2️. Use os Códigos de Status HTTP Corretamente

Os códigos de status HTTP ajudam o cliente da API a entender o resultado da requisição. Usar códigos corretos torna a API mais intuitiva e facilita a depuração.

Abaixo vamos apresentar alguns dos principais códigos de Status HTTP. Para mais informações, consulte o site https://http.cat/ (sugestão da Caroliny!).

Principais códigos de status HTTP:

Código Significado Quando Usar?
200 OK Sucesso Quando uma requisição GET, PUT ou DELETE for bem-sucedida
201 Created Recurso Criado Quando um novo recurso é criado via POST
204 No Content Sem Conteúdo Quando um recurso é excluído com sucesso
400 Bad Request Requisição Inválida Quando os dados enviados são inválidos
401 Unauthorized Não Autenticado Quando o usuário não está autenticado
403 Forbidden Acesso Negado Quando o usuário não tem permissão para acessar o recurso
404 Not Found Recurso Não Encontrado Quando o recurso solicitado não existe
409 Conflict Conflito Quando há um conflito de dados (ex.: tentativa de criar um registro duplicado)
500 Internal Server Error Erro Interno Quando ocorre um erro inesperado no servidor

💡 Exemplo Prático: Se um usuário tenta buscar um contato que não existe:

Errado:

{
  "message": "Contato não encontrado"
}

Certo (Retorna o código 404):

HTTP/1.1 404 Not Found
{
  "error": "Contato não encontrado"
}

3️. Evite Expor Detalhes Internos da API

Nunca retorne informações sensíveis sobre a API ou stack traces detalhadas em respostas de erro. Isso pode expor vulnerabilidades para possíveis ataques.

Errado (Expondo detalhes internos):

{
  "error": "java.lang.NullPointerException at ContactService.java:34"
}

Certo (Mensagem amigável e segura):

{
  "error": "Erro ao processar a requisição. Tente novamente mais tarde."
}

💡 Dica: Sempre trate exceções e retorne mensagens amigáveis para o cliente, sem expor detalhes da implementação.

4️. Implemente Paginação em Grandes Listas

Quando a API retorna uma grande quantidade de dados, a paginação evita sobrecarregar o servidor e melhora o desempenho.

Exemplo de Paginação:

GET /contacts?page=1&size=10

Resposta JSON com metadados de paginação:

{
  "data": [
    { "id": 1, "nome": "João" },
    { "id": 2, "nome": "Maria" }
  ],
  "page": 1,
  "size": 10,
  "totalPages": 5,
  "totalItems": 50
}

💡 Dica: Utilize frameworks como o Spring Data Pageable para implementar paginação de forma eficiente.

5️. Mantenha a API Intuitiva e Consistente

Uma API bem projetada deve ser fácil de usar, padronizada e previsível, permitindo que os desenvolvedores consigam integrá-la sem precisar consultar constantemente a documentação.

Boas práticas para manter a API intuitiva:

6️. Use Versionamento na API

Com o tempo, APIs evoluem e podem quebrar compatibilidade com versões antigas. Para evitar problemas, sempre versione a API.

Exemplo de versionamento:

GET /v1/contacts   → Versão 1  
GET /v2/contacts   → Versão 2  

💡 Dica: Se a API passar por mudanças grandes, mantenha versões anteriores disponíveis para evitar que clientes antigos quebrem.

7️. Documente sua API de Forma Clara

A documentação é essencial para que outros desenvolvedores entendam como usar sua API. Utilize ferramentas como:

Exemplo de documentação com Swagger:

import io.swagger.v3.oas.annotations.Operation;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/contacts")
public class ContactController {

    @Operation(summary = "Lista todos os contatos", description = "Retorna uma lista paginada de contatos")
    @GetMapping
    public List<Contact> getAllContacts() {
        return contactRepository.findAll();
    }
}

Seguir essas boas práticas ao criar APIs REST melhora a usabilidade, segurança e desempenho da aplicação. Implementar um design padronizado facilita a adoção da API por outros desenvolvedores e reduz a necessidade de documentação extensiva.

Checklist de Boas Práticas:Nomes de endpoints no plural e sem verbos
Uso correto dos métodos HTTP e códigos de status
Mensagens de erro claras e sem detalhes internos
Paginação para grandes listas
API intuitiva e fácil de entender
Versionamento para evitar quebra de compatibilidade
Documentação clara e bem estruturada

Aplique essas práticas para construir APIS REST mais robustas, escaláveis e fáceis de usar! 🚀

1.9 Conclusão

As APIs REST se tornaram o padrão para integração de sistemas modernos devido à sua simplicidade, flexibilidade e eficiência. Elas permitem que diferentes aplicações, independentemente da tecnologia utilizada, possam se comunicar utilizando os princípios da web.

Vamos colocar as mãos na massa e construir uma API REST com Spring Boot, explorando como criar, expor e manipular recursos de forma eficiente. Nosso objetivo é compreender os fundamentos e boas práticas do desenvolvimento de APIs REST para aplicações reais.



2. Introdução ao Spring Boot

O Spring Boot é um framework que facilita a criação de aplicativos em Java, fornecendo uma estrutura de projeto pré-configurada que agiliza o desenvolvimento.

O Spring Boot segue o princípio de Convention Over Configuration (Convenção sobre Configuração), que é um princípio de desenvolvimento que reduz a necessidade de configurações explícitas, fornecendo valores e comportamentos padrão sensíveis. O desenvolvedor só precisa configurar explicitamente algo quando deseja modificar o comportamento padrão.

Isso significa que ele vem com configurações padrão inteligentes que permitem aos desenvolvedores começarem rapidamente sem precisar definir manualmente cada detalhe da aplicação, o que elimina grande parte da configuração manual tradicional do Spring, permitindo que o desenvolvedor foque mais na lógica de negócio.

Sim, a explicação está clara e bem estruturada! Ela destaca os principais pontos sobre o Spring Boot e seu uso do princípio Convention Over Configuration de forma objetiva. No entanto, se quiser torná-la ainda mais acessível, você pode adicionar um exemplo rápido para ilustrar a ideia.

📌 Exemplo prático:
No Spring tradicional, para configurar um banco de dados, seria necessário definir manualmente um DataSource, gerenciar transações e configurar o Hibernate.

Com o Spring Boot, basta adicionar as dependências necessárias e incluir algumas linhas no application.properties:

spring.datasource.url=jdbc:mysql://localhost:3306/meubanco
spring.datasource.username=root
spring.datasource.password=senha

Ou seja, o Spring Boot detecta automaticamente o banco de dados e configura o Hibernate sem necessidade de configurações adicionais.

Criando o Projeto Spring Boot

Antes de iniciarmos a implementação da nossa API REST, precisamos criar o projeto Spring Boot. Podemos fazer isso de diversas maneiras, mas utilizaremos a abordagem mais prática: o Spring Initializr.

🔹 Criando o Projeto com Spring Initializr

O Spring Initializr é uma ferramenta online que permite configurar e gerar rapidamente um projeto Spring Boot com as dependências necessárias.

Passo a passo para criar o projeto:

  1. Acesse o Spring Initializr

  2. Configure o projeto

    • Project: Maven (ou Gradle, se preferir)
    • Language: Java
    • Spring Boot Version: Escolha a versão mais recente estável
    • Group: br.ifsp
    • Artifact: contacts-api
    • Name: contacts-api
    • Description: API para gerenciamento de contatos
    • Package Name: br.ifsp.contacts
    • Packaging: Jar
    • Java Version: 11 ou superior (recomendado)
  3. Adicione as dependências essenciais

    • Spring Web → Para criar a API REST
    • Spring Boot DevTools → Para recarregamento automático durante o desenvolvimento
    • Spring Data JPA → Para interação com banco de dados
    • H2 Database → Banco de dados em memória para testes
  4. Gerar e baixar o projeto

    • Clique em "Generate" para baixar o projeto .zip
    • Extraia o .zip em uma pasta de sua preferência.

🔹 Importando o Projeto na IDE

Após baixar e extrair o projeto, é hora de importá-lo para uma IDE (como IntelliJ IDEA, Eclipse ou VS Code com extensão Java).

Se estiver usando IntelliJ IDEA:

  1. Abra o IntelliJ IDEA.
  2. Clique em File > Open.
  3. Selecione a pasta do projeto (contacts-api) e clique em Open.
  4. Aguarde o Maven ou Gradle baixar as dependências automaticamente.

Se estiver usando Eclipse:

  1. Vá até File > Import.
  2. Escolha Existing Maven Projects.
  3. Selecione a pasta onde extraiu o projeto.
  4. Clique em Finish e aguarde a configuração.

🔹 Rodando o Projeto pela Primeira Vez

Agora que temos o projeto configurado, podemos rodá-lo pela primeira vez.

  1. Abra o terminal na pasta do projeto.
  2. Se estiver usando Maven, execute:
    mvn spring-boot:run
    
    Se estiver usando Gradle, execute:
    ./gradlew bootRun
    
  3. Aguarde até que a aplicação inicie. Se tudo estiver correto, você verá algo como:
    Tomcat started on port(s): 8080 (http)
    

Agora sua API está rodando localmente na porta 8080 e pronta para receber requisições! 🎉

Caso queira testar se está funcionando, abra o navegador e acesse:

http://localhost:8080

A princípio, receberá uma resposta Whitelabel Error Page, pois ainda não criamos nenhuma rota. Nas próximas etapas, adicionaremos os endpoints REST.

2.1 Estrutura do Projeto

Ao criar um projeto com o Spring Boot, é comum utilizar o padrão de pastas do Maven, mesmo que o seu build tool seja o Maven ou Gradle. Essa estrutura fica geralmente assim:

.
├── src
│   ├── main
│   │   ├── java
│   │   │   └── br
│   │   │       └── ifsp
│   │   │           └── contacts
│   │   │               ├── ContactsApplication.java
│   │   │               ├── controller
│   │   │               │   └── ContactController.java
│   │   │               ├── model
│   │   │               │   └── Contact.java
│   │   │               └── repository
│   │   │                   └── ContactRepository.java
│   │   └── resources
│   │       └── application.properties
│   └── test
│       └── java
└── pom.xml (ou build.gradle)

A seguir, veremos cada arquivo principal e as explicações associadas.


2.2 Arquivo de Inicialização: ContactsApplication.java

Este é o ponto de entrada da aplicação Spring Boot. É nele que colocamos a anotação @SpringBootApplication, que habilita diversas configurações automáticas:

package br.ifsp.contacts;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

/**
 * Classe principal da nossa aplicação Spring Boot.
 * 
 * A anotação @SpringBootApplication habilita as configurações
 * automáticas do Spring (auto-configuration) e também indica 
 * que esta é a classe que deve ser executada para iniciar 
 * a aplicação.
 */
@SpringBootApplication
public class ContactsApplication {

    public static void main(String[] args) {
        // Método main: ponto de entrada de uma aplicação Java.
        // SpringApplication.run() inicia a aplicação Spring Boot.
        SpringApplication.run(ContactsApplication.class, args);
    }

}

Explicação:

Quando executamos o método main, o Spring Boot levanta um servidor embutido (por padrão, o Tomcat) e passa a escutar as requisições HTTP.


2.3 Modelo de Dados: Contact.java

Para representar cada contato, precisamos de uma classe que contenha seus atributos. Chamamos essa classe de modelo, ou entidade, quando falamos de persistência de dados.

package br.ifsp.contacts.model;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;

/**
 * Classe que representa o modelo de dados para um Contato.
 * 
 * @Entity indica que este objeto será mapeado para uma tabela
 * no banco de dados (em cenários de persistência com JPA).
 */
@Entity
public class Contact {

    /**
     * @Id indica que este campo é a chave primária (primary key) da entidade.
     * @GeneratedValue permite que o banco de dados (ou o provedor JPA) 
     * gere automaticamente um valor único para cada novo registro.
     */
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String nome;
    private String telefone;
    private String email;

    // Construtor vazio exigido pelo JPA
    public Contact() {}

    // Construtor para facilitar a criação de objetos
    public Contact(String nome, String telefone, String email) {
        this.nome = nome;
        this.telefone = telefone;
        this.email = email;
    }

    // Getters e Setters
    public Long getId() {
        return id;
    }

    public String getNome() {
        return nome;
    }
    public void setNome(String nome) {
        this.nome = nome;
    }

    public String getTelefone() {
        return telefone;
    }
    public void setTelefone(String telefone) {
        this.telefone = telefone;
    }

    public String getEmail() {
        return email;
    }
    public void setEmail(String email) {
        this.email = email;
    }
}

Explicação:

Mesmo que ainda não estejamos aprofundando no uso de banco de dados real, este exemplo já está preparado para persistência caso seja configurado um banco H2 ou outro SGBD.


2.4 Repositório de Dados: ContactRepository.java

O repositório é responsável por realizar as operações de acesso aos dados (criar, ler, atualizar, excluir). Vamos utilizar a interface JpaRepository do Spring Data JPA, que já oferece as implementações básicas de CRUD, bastando nós definirmos a interface corretamente.

package br.ifsp.contacts.repository;

import org.springframework.data.jpa.repository.JpaRepository;
import br.ifsp.contacts.model.Contact;

/**
 * Esta interface extende JpaRepository, que nos fornece métodos prontos 
 * para acessar e manipular dados no banco de dados. Basta especificar 
 * a classe de entidade (Contact) e o tipo da chave primária (Long).
 */
public interface ContactRepository extends JpaRepository<Contact, Long> {
    // Podemos adicionar métodos personalizados se necessário.
}

Explicação:


2.5 Controlador (Controller): ContactController.java

O controlador é a classe que “escuta” as requisições HTTP e define como o sistema vai responder. Nele, utilizamos anotações como @RestController (que indica um controller REST) e @RequestMapping (para definir o caminho base dos endpoints, também chamados de “URIs” ou “rotas”).

package br.ifsp.contacts.controller;

import br.ifsp.contacts.model.Contact;
import br.ifsp.contacts.repository.ContactRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.util.List;

/**
 * Classe responsável por mapear as rotas/endpoints relacionados
 * aos contatos. Cada método abaixo corresponde a uma operação
 * em nosso sistema.
 * 
 * @RestController: Indica que esta classe é um controlador 
 *                  responsável por responder requisições REST.
 * @RequestMapping("/api/contacts"): Indica o caminho base.
 */
@RestController
@RequestMapping("/api/contacts")
public class ContactController {

    /**
     * @Autowired permite que o Spring "injete" automaticamente
     * uma instância de ContactRepository aqui, 
     * sem que precisemos criar manualmente.
     */
    @Autowired
    private ContactRepository contactRepository;

    /**
     * Método para obter todos os contatos.
     * 
     * @GetMapping indica que este método vai responder a chamadas HTTP GET.
     * Exemplo de acesso: GET /api/contacts
     */
    @GetMapping
    public List<Contact> getAllContacts() {
        return contactRepository.findAll();
    }

    /**
     * Método para obter um contato específico pelo seu ID.
     * 
     * @PathVariable "amarra" a variável {id} da URL 
     * ao parâmetro do método.
     * Exemplo de acesso: GET /api/contacts/1
     */
    @GetMapping("/{id}")
    public Contact getContactById(@PathVariable Long id) {
        // findById retorna um Optional, então usamos orElseThrow
        return contactRepository.findById(id)
                .orElseThrow(() -> new RuntimeException("Contato não encontrado: " + id));
    }

    /**
     * Método para criar um novo contato.
     * 
     * @PostMapping indica que este método responde a chamadas HTTP POST.
     * @RequestBody indica que o objeto Contact será preenchido 
     * com os dados JSON enviados no corpo da requisição.
     */
    @PostMapping
    public Contact createContact(@RequestBody Contact contact) {
        return contactRepository.save(contact);
    }

    /**
     * Método para atualizar um contato existente.
     * 
     * @PutMapping indica que este método responde a chamadas HTTP PUT.
     * Exemplo de acesso: PUT /api/contacts/1
     */
    @PutMapping("/{id}")
    public Contact updateContact(@PathVariable Long id, @RequestBody Contact updatedContact) {
        // Buscar o contato existente
        Contact existingContact = contactRepository.findById(id)
                .orElseThrow(() -> new RuntimeException("Contato não encontrado: " + id));

        // Atualizar os campos
        existingContact.setNome(updatedContact.getNome());
        existingContact.setTelefone(updatedContact.getTelefone());
        existingContact.setEmail(updatedContact.getEmail());

        // Salvar alterações
        return contactRepository.save(existingContact);
    }

    /**
     * Método para excluir um contato pelo ID.
     * 
     * @DeleteMapping indica que este método responde a chamadas HTTP DELETE.
     * Exemplo de acesso: DELETE /api/contacts/1
     */
    @DeleteMapping("/{id}")
    public void deleteContact(@PathVariable Long id) {
        contactRepository.deleteById(id);
    }
}

Explicação:

  1. @RestController:

    • Indica que esta classe processa requisições REST (ou seja, retorna dados em JSON ou outro formato) e não retorna diretamente páginas HTML.
  2. @RequestMapping("/api/contacts"):

    • Define que todos os métodos dentro deste controller estarão acessíveis a partir de URIs que começam com /api/contacts. Por exemplo, se o método for anotado com @GetMapping, isso virará GET /api/contacts.
  3. Métodos HTTP:

    • @GetMapping → Método GET do protocolo HTTP. Para obter (ler) dados.
    • @PostMapping → Método POST do protocolo HTTP. Para criar (inserir) dados.
    • @PutMapping → Método PUT do protocolo HTTP. Para atualizar (modificar) dados.
    • @DeleteMapping → Método DELETE do protocolo HTTP. Para excluir dados.
  4. @PathVariable:

    • Indica que o valor do parâmetro id virá diretamente do segmento correspondente na URL. Por exemplo, ao acessar GET /api/contacts/10, o valor 10 será atribuído ao parâmetro id.
  5. @RequestBody:

    • Indica que o parâmetro do método (Contact contact) deve ser populado com o corpo da requisição. Em uma chamada POST com corpo JSON, os campos nome, telefone, email (por exemplo) serão convertidos em um objeto Contact.
  6. Injeção de Dependência (@Autowired):

    • O Spring cria e gerencia o objeto ContactRepository. Ao colocar @Autowired, estamos dizendo ao Spring para injetar nesse campo a instância do nosso repositório, economizando a tarefa de instanciá-lo manualmente.

2.6 Arquivo de Configuração: application.properties

Caso quiséssemos usar o banco H2 em memória para testes ou configurações adicionais, poderíamos adicionar alguns parâmetros no application.properties. Por exemplo:

spring.datasource.url=jdbc:h2:mem:contacts_db
spring.datasource.driverClassName=org.h2.Driver
spring.jpa.hibernate.ddl-auto=update
spring.h2.console.enabled=true

Explicação:

Se não quisermos usar H2, ainda assim podemos manter o projeto como está, pois o Spring Boot, por padrão, tentará criar um banco em memória se não for configurado outro banco real.


2.7 Build e Execução

Usando Maven

Se estivermos utilizando Maven, teremos um arquivo pom.xml na raiz do projeto. Ele conterá, entre outras coisas, as dependências:

<dependencies>
    <!-- Dependência do Spring Web, para criar a API REST -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <!-- Dependência do Spring Data JPA, para acesso a dados -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>

    <!-- Dependência para banco H2 em memória (opcional) -->
    <dependency>
        <groupId>com.h2database</groupId>
        <artifactId>h2</artifactId>
    </dependency>
</dependencies>

Para rodar, podemos simplesmente executar:

mvn spring-boot:run

ou, se gerarmos um .jar, podemos rodar:

java -jar target/contacts-0.0.1-SNAPSHOT.jar

Usando Gradle

Caso nosso projeto fosse em Gradle, teríamos um arquivo build.gradle. O conteúdo principal para as dependências seria algo como:

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-web'
    implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
    runtimeOnly 'com.h2database:h2'
}

Para iniciar a aplicação:

./gradlew bootRun

Maven vs. Gradle: Comparação e Diferenças

Ao desenvolver aplicações com Spring Boot, é necessário um gerenciador de dependências e build para automatizar tarefas como compilação, empacotamento, testes e execução do projeto. Os dois principais gerenciadores no ecossistema Java são Maven e Gradle.

Estrutura e Configuração

Desempenho e Eficiência

Sintaxe e Facilidade de Uso

Popularidade e Comunidade

Ou seja...

Ambas as ferramentas são compatíveis com Spring Boot, então a escolha depende das necessidades do projeto e do conhecimento prévio da equipe.

Ao longo da disciplina utilizaremos o Maven como ferramenta padrão, dada sua maior popularidade.


2.8 Testando a API

Ao desenvolver uma API REST, é essencial testar suas funcionalidades para verificar se os endpoints estão respondendo corretamente. Para isso, utilizamos clients de API, que permitem enviar requisições HTTP e visualizar as respostas do servidor. Dentre os mais populares, temos as seguintes opções.

Postman 📨

O Postman é um dos clientes de API mais populares e oferece uma interface gráfica para:
✅ Enviar requisições GET, POST, PUT, PATCH, DELETE com facilidade.
✅ Adicionar headers, parâmetros e autenticação às requisições.
✅ Salvar coleções de requisições para testes automatizados.
✅ Simular corpos JSON e XML no envio de dados.

👉 Exemplo:

  1. Abra o Postman e crie uma nova requisição.
  2. Escolha GET e insira http://localhost:8080/api/contacts.
  3. Clique em Send e visualize a resposta JSON da API.

💡 O Postman também suporta testes automatizados e integração com CI/CD.

Insomnia 🌙

O Insomnia é uma alternativa ao Postman, mais leve e focada na simplicidade. Ele permite:
✅ Criar e organizar requisições de forma intuitiva.
✅ Testar APIs REST, GraphQL e WebSockets.
✅ Gerenciar variáveis de ambiente para diferentes contextos (desenvolvimento, produção).

💡 É recomendado para quem busca uma experiência mais fluida e rápida, sem tantas funcionalidades avançadas.

cURL 🖥️

O cURL é uma ferramenta de linha de comando para testar APIs diretamente no terminal. É útil para desenvolvedores que preferem scripts e automação.

👉 Exemplo de requisição GET com cURL:

curl -X GET http://localhost:8080/api/contacts

👉 Exemplo de requisição POST com JSON:

curl -X POST http://localhost:8080/api/contacts \
     -H "Content-Type: application/json" \
     -d '{"nome": "Maria", "telefone": "9999-9999", "email": "maria@email.com"}'

💡 cURL é embutido no Linux e macOS e pode ser usado em scripts para testes automatizados.

Alternativas e Ferramentas Adicionais

Independentemente da ferramenta, testar a API é fundamental para garantir que os endpoints funcionam conforme esperado! 🚀

Com o client em mãos, vamos ao teste da API!

Depois que a aplicação estiver em execução (por padrão, na porta 8080), podemos testar as rotas usando alguma das ferramentas descritas acima. Ao longo da disciplina utilizaremos principalmente o Postman.

  1. Criar contato (POST)

    POST /api/contacts
    Body (JSON):
    {
      "nome": "João da Silva",
      "telefone": "9999-9999",
      "email": "joao@email.com"
    }
    
    • Resposta: Objeto JSON do contato criado, incluindo o id atribuído.
  2. Obter todos os contatos (GET)

    GET /api/contacts
    
    • Resposta: Lista de contatos em formato JSON.
  3. Obter contato específico (GET)

    GET /api/contacts/1
    
    • Resposta: Objeto JSON com o contato de ID 1 (caso exista).
  4. Atualizar contato (PUT)

    PUT /api/contacts/1
    Body (JSON):
    {
      "nome": "Maria da Silva",
      "telefone": "8888-8888",
      "email": "maria@email.com"
    }
    
    • Resposta: Objeto JSON com o contato atualizado.
  5. Excluir contato (DELETE)

    DELETE /api/contacts/1
    
    • Resposta: Sem corpo (status HTTP 200 ou 204).

2.9 Recapitulando

Neste projeto, apresentamos:

  1. O que é uma API REST e como o Spring Boot facilita sua criação.
  2. Os principais verbos HTTP (GET, POST, PUT, DELETE) e como eles se relacionam com operações de leitura, criação, atualização e exclusão de dados.
  3. Como estruturar um projeto Spring Boot com:
    • Classe principal (ContactsApplication)
    • Entidade de modelo (Contact)
    • Repositório de dados (ContactRepository)
    • Controlador REST (ContactController)
    • Configurações no application.properties e dependências no pom.xml ou build.gradle.

Essa base serve para, gradualmente, adicionar mais funcionalidades, como:

O que aprendemos até aqui?

O que são APIs REST e seus princípios fundamentais.
Os métodos HTTP (GET, POST, PUT, PATCH, DELETE) e seu uso correto.
A importância da padronização e do modelo de maturidade de Richardson.
A estrutura de um projeto Spring Boot e como criar uma API básica.
Como testar APIs usando ferramentas como Postman, Insomnia e cURL.

Por enquanto, nossa aplicação já exemplifica o essencial do desenvolvimento de uma API REST com o Spring Boot.

Sigam este passo-a-passo e executem o código-fonte como ponto de partida para os exercícios posteriores.

3. Conclusão e Próximos Passos

Nesta aula vimos que uma API REST segue um conjunto de padrões baseados no protocolo HTTP, permitindo a comunicação entre sistemas de maneira padronizada, previsível e escalável. Para garantir que uma API seja realmente RESTful, entendemos a importância do modelo de maturidade de Richardson, que nos ajuda a avaliar a conformidade de uma API com os princípios REST.

Além disso, iniciamos a introdução ao Spring Boot, uma tecnologia que simplifica o desenvolvimento de aplicações Java, eliminando a necessidade de configurações manuais complexas e permitindo que possamos rapidamente criar e expor endpoints RESTful. Aprendemos a estrutura de um projeto Spring Boot e implementamos os principais componentes necessários para a construção de uma API de lista de contatos.

Nas próximas aulas avançaremos na construção de APIs mais robustas e completas. Trabalharemos com novos conceitos essenciais para um desenvolvimento profissional, incluindo:

🔹 Validações e Tratamento de Erros: Garantindo que nossa API retorne respostas consistentes e evite entradas inválidas.
🔹 Persistência e Banco de Dados: Integração da API com um banco de dados real para armazenar os contatos de forma permanente.
🔹 Autenticação e Segurança: Protegendo a API com mecanismos de autenticação, como OAuth e JWT.
🔹 Boas Práticas e Documentação: Como tornar nossa API mais compreensível e fácil de usar para outros desenvolvedores.

Antes disso, entretanto, é necessário solidificar os conteúdos vistos até aqui.

4. Exercícios

Os exercícios a seguir têm como objetivo reforçar os conceitos apresentados na aula e permitir que você pratique o desenvolvimento de APIs REST com Spring Boot.

📌 Instruções:

  • Os exercícios devem ser entregues no Moodle dentro do prazo estipulado.
  • Sempre comente o código explicando as principais partes da implementação.
  • Utilize Postman, Insomnia ou cURL para testar os endpoints criados.
  • O código-fonte deve ser enviado em um repositório no GitHub (ou em anexo no Moodle, se preferir).

1️⃣ Exercício 1 - Criando um Novo Endpoint GET

Crie um novo endpoint GET em ContactController que permita buscar contatos pelo nome.

📌 Requisitos:

📌 Dicas:

📝 Saída esperada (JSON):

[
  {
    "id": 1,
    "nome": "João Silva",
    "telefone": "9999-9999",
    "email": "joao@email.com"
  }
]

2️⃣ Exercício 2 - Implementando um Método PATCH

Adicione um novo método PATCH à API, permitindo que o usuário atualize apenas um ou mais campos de um contato, sem precisar enviar todos os dados.

📌 Requisitos:

📌 Exemplo de chamada:

PATCH /api/contacts/1
{
  "email": "novoemail@email.com"
}

📝 Saída esperada (JSON):

{
  "id": 1,
  "nome": "João Silva",
  "telefone": "9999-9999",
  "email": "novoemail@email.com"
}

3️⃣ Exercício 3 - REST e SOAP

Agora que você já conhece APIs REST, faça uma pesquisa sobre APIs SOAP e responda às perguntas abaixo com suas próprias palavras.

📌 Questões:

  1. Qual a principal diferença entre REST e SOAP?
  2. Em quais cenários SOAP ainda é utilizado?
  3. Quais são as vantagens e desvantagens de usar REST ao invés de SOAP?
  4. O que é WS-Security e como ele se compara à segurança em APIs REST?
  5. Explique o modelo de maturidade de Richardson.
  6. O que é GraphQL? Pesquisa e relacione com os conceitos vistos em aula.

1️⃣ Desafio 1 - Criando um Novo Modelo de Dados

Atualmente, nossa API gerencia apenas contatos. Agora, queremos adicionar um novo recurso: endereços.

📌 Tarefas:

  1. Crie uma nova entidade Address, com os seguintes atributos:
    • id (Long, auto-increment)
    • rua (String)
    • cidade (String)
    • estado (String)
    • cep (String)
    • contactId (Long) - Chave estrangeira referenciando um contato.
  2. Crie uma relação bidirecional entre contatos e endereços.
  3. Crie um repositório (AddressRepository) para gerenciar os endereços.
  4. Implemente um novo AddressController para adicionar e recuperar endereços.
  5. Crie uma rota GET /api/contacts/{id}/addresses para listar todos os endereços de um contato específico.

2️⃣ Desafio 2 - Melhorando a Validação dos Dados

Atualmente, nossa API aceita qualquer valor no cadastro de contatos. Precisamos garantir que os dados sejam válidos antes de inseri-los no banco de dados.

📌 Requisitos:

📌 Exemplo de resposta para entrada inválida:
Se tentarmos criar um contato com um telefone inválido:

{
  "nome": "Maria",
  "telefone": "123",
  "email": "maria@email.com"
}

A API deve retornar HTTP 400 e uma mensagem de erro:

{
  "erro": "O telefone deve ter entre 8 e 15 caracteres"
}

📌 Instruções Finais

Bons estudos e mãos à obra! 🛠🔥