Definições

REST

O termo REST é um acrônimo e significa Representational State Transfer. Rest é um paradigma arquitetônico usado no desenvolvimento de web services. Criado por Roy Fielding em 2000 em sua tese de doutorado defendida no ano de 2000 na Universidade da Califórnia, Irvine.

RESTful API

Também conhecido como um web service RESTful. Web service construído em conformidade com o estilo de arquitetura REST e usa métodos HTTP.

API

API é um conjunto de definições e protocolos usado no desenvolvimento e na integração de software de aplicações. API é um acrônimo em inglês que significa interface de programação de aplicações. As APIs costumam ser vistas como contratos, com documentações que representam um acordo entre as partes interessadas (referência). Como exemplo podemos citar a API java (referência).

Endpoint

Um endpoint de um web service é a URL onde seu serviço pode ser acessado por uma aplicação cliente (referência).

Aplicações que fornecem web services precisam de clientes que têm a capacidade de realizar requisições HTTP usando todos os tipos de métodos e, portanto, precisaremos de um cliente especial para testar nossas aplicações. Vamos usar uma extensão do VSCode chamada REST Client para testar nossos serviços.

A seguir mostramos exemplos de uso para cada um dos principais métodos de requisição HTTP:

1. GET

GET /posts/1 HTTP/1.1
Host: jsonplaceholder.typicode.com

Neste exemplo, estamos buscando o post com ID 123 na API.

2. POST

POST /posts HTTP/1.1
Host: jsonplaceholder.typicode.com
Content-Type: application/json

{
  "title": "foo",
  "body": "bar",
  "userId": 1
}

Aqui, estamos adicionando um novo usuário através de um modal de inscrição.

3. PUT

PUT /posts/1 HTTP/1.1
Host: jsonplaceholder.typicode.com
Content-Type: application/json

{
  "id": 1,
  "title": "foo",
  "body": "bar",
  "userId": 1
}

Neste caso, estamos atualizando o post com ID 123.

4. DELETE :

DELETE /posts/1 HTTP/1.1
Host: jsonplaceholder.typicode.com

Aqui, estamos excluindo o post com ID 123.

5. PATCH

PATCH /posts/1 HTTP/1.1
Host: jsonplaceholder.typicode.com
Content-Type: application/json

{
  "title": "foo"
}

Neste exemplo, estamos alterando apenas a senha do usuário com ID 456.

Referências

Idempotente

Um método HTTP é idempotente se uma requisição idêntica pode ser feita uma ou mais vezes em sequência com o mesmo efeito enquanto deixa o servidor no mesmo estado. Em outras palavras, um método idempotente não deveria possuir nenhum efeito colateral (exceto para manter estatísticas). Implementados corretamente, o GET, HEAD, PUT, e DELETE são métodos idempotentes, mas não o método POST.

Códigos de status de resposta HTTP

Servem para informar ao cliente o status de suas requisições. O servidor retorna códigos de status genéricos. As categorias de código são:

Em REST, a representação de dados primária é chamada de recurso. Como boa prática de nomenclatura de recursos REST vamos seguir as seguintes diretivas.

POST

Criar dados

GET

Buscar dados

PUT

Atualizar dados

DELETE

Excluir dados

Buscar dados

Não faça assim

GET /buscaItem

Para buscar todos os registros

GET /getTodosItens/12

Para buscar registros específicos

Faça assim

GET /itens

Para buscar todos os registros

GET /itens/101

Para buscar registros específicos

Criar dados

Não faça assim

POST /criaritem

Para criar um novo registro

Faça assim

POST /itens

Para criar um novo registro

Atualizar dados

Não faça assim

PUT /atualizaritem/2

Para atualizar um registro

POST /2/atualizaritem

Para atualizar um registro

Faça assim

PUT /itens/2

Para atualizar um registro

Excluir dados

Não faça assim

DELETE /excluiritem/201

Para excluir um registro

POST /201/removeitem

Para excluir um registro

Faça assim

DELETE /itens/201

Para excluir um registro

Coleções de recursos

Podemos identificar o recurso de coleção "clientes" usando o URI "/clientes". Podemos identificar um único recurso "cliente" usando o URI "/clientes/{id do cliente}".

Recursos de coleção e subcoleção

Um recurso também pode conter recursos de subcoleção. Por exemplo, o recurso de subcoleção "contas" de um "cliente" específico pode ser identificado usando o URI "/clientes/{id do cliente}/contas" (em um domínio bancário). Da mesma forma, um recurso singleton "conta" dentro do recurso de subcoleção "contas" pode ser identificado da seguinte forma: "/clientes/{id do cliente}/contas/{id da conta}".

Usando o modelo de camadas

Criando o projeto Spring

  1. Para criar novo projeto usando o VSCode, use o atalho Ctrl + Shift + P e digite spring init e escolha a opção mostrada abaixo:

  1. Siga as escolhas mostradas abaixo:

  1. Escolher as dependências MySQL Driver, Spring Data JPA e Spring Web

  1. Pressione Enter para criar o projeto e escolha uma pasta no seu computador onde serão gravados os arquivos do projeto:

  1. No meu computador os projetos Spring ficam na pasta c:/Spring. Este projeto será gravado na pasta C:\Spring\pessoa-api>
  2. Criar os pacotes ‘controller', ‘domain', ‘repository', ‘service'

  1. Entidade Pessoa
package br.iftm.edu.pessoaapi.domain;

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

@Entity
public class Pessoa {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;
    private String nome;

    public Pessoa() {
    }

    public Pessoa(String nome) {
        this.nome = nome;
    }

    public Integer getId() {
        return this.id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

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

    public void setNome(String nome) {
        this.nome = nome;
    }
}
  1. Repositório para entidades do tipo Pessoa
package br.iftm.edu.pessoaapi.repository;

import java.util.List;
import org.springframework.data.repository.CrudRepository;
import br.iftm.edu.pessoaapi.domain.Pessoa;

public interface PessoaRepository extends CrudRepository<Pessoa, Integer> {
    @Override
    List<Pessoa> findAll();
}
  1. Service
package br.iftm.edu.pessoaapi.service;

import java.util.List;
import java.util.Optional;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.PathVariable;
import br.iftm.edu.pessoaapi.domain.Pessoa;
import br.iftm.edu.pessoaapi.repository.PessoaRepository;

@Service
public class PessoaService {

    @Autowired
    PessoaRepository repository;

    public List<Pessoa> todos() {
        return repository.findAll();
    }

    public Pessoa novo(Pessoa pessoa) {
        return repository.save(pessoa);
    }

    public Optional<Pessoa> busca(Integer id) {
        return repository.findById(id);
    }

    public Pessoa atualiza(Pessoa pessoa, Integer id) {
        pessoa.setId(id);
        return repository.save(pessoa);
    }

    public void exclui(@PathVariable Integer id) {
        repository.deleteById(id);
    }
}
  1. Controlador REST
package br.iftm.edu.pessoaapi.controller;

import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import br.iftm.edu.pessoaapi.domain.Pessoa;
import br.iftm.edu.pessoaapi.service.PessoaService;

@RestController
@RequestMapping("/pessoa")
class PessoaController {

    @Autowired
    private PessoaService service;

    @GetMapping
    public List<Pessoa> recuperaTodosRegistros() {
        return service.todos();
    }

    @PostMapping
    public Pessoa criaNovoRegistro(@RequestBody Pessoa pessoa) {
        return service.novo(pessoa);
    }

    @GetMapping("/{id}")
    public Pessoa buscaUmRegistro(@PathVariable Integer id) {
        return service.busca(id).orElseThrow(() -> new PessoaNaoEncontradaException(id));
    }

    @PutMapping("/{id}")
    public Pessoa atualizaRegistro(@RequestBody Pessoa pessoa, @PathVariable Integer id) {
        return service.atualiza(pessoa, id);
    }

    @DeleteMapping("/{id}")
    void excluiRegistro(@PathVariable Integer id) {
        service.exclui(id);
    }
}
  1. Classe RuntimeException quando não encontrar uma pessoa no BD
package br.iftm.edu.pessoaapi.controller;

public class PessoaNaoEncontradaException extends RuntimeException {

    PessoaNaoEncontradaException(Integer id) {
        super("Não encontrei pessoa " + id);
    }

}
  1. Classe @ControllerAdvice
package br.iftm.edu.pessoaapi.controller;

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

@RestControllerAdvice
public class PessoaNaoEncontradaAdvice {
    
    @ExceptionHandler
    ResponseEntity<String> employeeNotFoundHandler(PessoaNaoEncontradaException ex) {
        return new ResponseEntity<String>(ex.getMessage(), HttpStatus.NOT_FOUND);
    }
}
  1. Altere o arquivo /src/main/resources/application.properties: (Você precisa criar um esquema no seu SGBD MySQL chamado teste)
spring.jpa.hibernate.ddl-auto=update
spring.datasource.url=jdbc:mysql://${MYSQL_HOST:localhost}:3306/teste
spring.datasource.username=root
spring.datasource.password=

spring.jpa.show-sql=true
logging.level.org.springframework.web=trace