Criando as primeiras funcionalidades da nossa API

Criando as primeiras funcionalidades da nossa API

Olá pessoal!

Hoje vamos continuar nossa série de tutoriais sobre como construir uma API em Java com Spring Boot. Se você ainda não leu os artigos anteriores, clique aqui para entender melhor o contexto e preparar seu ambiente de desenvolvimento.

O que vamos fazer hoje?

Nosso objetivo é criar o "esqueleto" da API, ou seja, a estrutura básica que será a base do desenvolvimento e para isso vamos usar uma site chamado Spring Initializr, que gera um projeto Spring Boot já configurado com as dependências que você precisa. Isso torna o início do desenvolvimento muito mais simples!

Configurações do projeto

No nosso projeto, vamos utilizar

  • Maven como gerenciador de dependências

  • Java 23 como linguagem

  • E as dependências abaixo:

Spring Boot Dev Tools

Essa dependência facilita o desenvolvimento, permitindo que a aplicação reinicie automaticamente sempre que você atualizar o código. Também oferece ferramentas úteis de depuração, agilizando seu trabalho.

Spring Web

Essa é a dependência que permite criar aplicações web, incluindo APIs RESTful. Com ela, você consegue criar servidores, controladores, e trabalhar facilmente com dados em formato JSON ou XML.

Lombok

Lombok é uma biblioteca que ajuda a reduzir código repetitivo no Java. Esse tipo de código, chamado de boilerplate, como getters, setters, métodos equals, hashCode e construtores, que você geralmente escreve várias vezes. Com o Lombok, você pode adicionar anotações como @Getter e @Builder, e ele gera esses métodos automaticamente. Isso deixa seu código mais simples e fácil de manter!

Antes de começar

Confira se todas as configurações estão corretas e, quando estiver tudo pronto, clique em Generate para baixar o arquivo zip com o projeto configurado.

Após baixar o arquivo, extraia-o para uma pasta de sua escolha e abra o projeto na sua IDE favorita. Antes de começar a programar, é importante verificar se tudo está funcionando corretamente. Você pode fazer isso de duas maneiras:

  1. Pelo terminal

    Abra o terminal no diretório do projeto e digite o seguinte comando:

    Para encerrar o programa, pressione CTRL + C e depois "S".

mvn spring-boot:run
  1. Utilizando a IDE Basta seguir o passo a passo abaixo

Obs.: Na primeira vez que você executar o projeto no IntelliJ, é comum que ele solicite a configuração do JDK. Para resolver isso, basta selecionar o JDK 23 ou seguir o caminho File > Project Structure e definir a versão do JDK conforme mostrado na imagem abaixo.

Em ambos os cenários, a saída esperada é parecida com a imagem abaixo.

Com o projeto em execução, abra seu navegador e digite na barra de endereços: http://localhost:8080/. Se a página exibida for semelhante à imagem abaixo, tudo está funcionando corretamente. O erro mostrado é normal, já que ainda não configuramos nenhuma rota na aplicação.

Configurando as Rotas da API

Agora que o projeto está configurado, vamos começar a criar as rotas da nossa API. Para que o código fique mais organizado e fácil de entender, é importante estruturar as pastas corretamente. Assim, podemos separar as responsabilidades das classes que vamos criar e facilitar o trabalho no futuro.

Vamos criar três pastas principais: model, controller e service. Cada uma terá uma função específica no nosso código.

Controller

A pasta controller irá armazenar as classes responsáveis por gerenciar as requisições HTTP. Ou seja, quando um usuário fizer uma solicitação para nossa API, a classe SerieController (que será responsável por gerenciar as rotas relacionadas às séries, como listar todas as séries, adicionar uma nova, entre outras) processará a solicitação e a direcionará para a parte adequada da aplicação (geralmente para o service). No Spring Boot, essas classes são anotadas com @RestController ou @Controller, o que permite ao Spring identificá-las como responsáveis por lidar com rotas e a lógica das requisições.

A nossa classe SerieController terá 5 rotas:

  • @GetMapping("/series"): para listar todas as séries.

  • @GetMapping("/series/{id}"): para listar uma série específica, usando o id.

  • @PostMapping("/series"): para criar uma nova série.

  • @PatchMapping("/series/{id}"): para atualizar uma série específica, usando o id.

  • @DeleteMapping("/series/{id}"): para deletar uma série específica, usando o id.

Essas rotas irão chamar os métodos da classe SerieService para executar as ações.

Classe SeriesController

@RestController
@RequestMapping("/series")
@Slf4j
@RequiredArgsConstructor
public class SerieController {

    private final SeriesService seriesService;

    @PostMapping
    public ResponseEntity<Series> createSeries(@RequestBody Series series) {
        Series createdSeries = seriesService.createANewSeries(series);
        return ResponseEntity.ok(createdSeries);
    }

    @GetMapping
    public ResponseEntity<List<Series>> getAllSeries() {
        List<Series> seriesList = seriesService.listAllSeries();
        return ResponseEntity.ok(seriesList);
    }

    @GetMapping("/{id}")
    public ResponseEntity<Series> getSeriesById(@PathVariable Long id) {
        Optional<Series> series = seriesService.findById(id);
        return series.map(ResponseEntity::ok)
                .orElse(ResponseEntity.notFound().build());
    }

    @PatchMapping("/{id}")
    public ResponseEntity<Series> updateSeries(@PathVariable Long id, @RequestBody Series updatedSeries) {
        Optional<Series> updated = seriesService.updateSeries(id, updatedSeries);
        return updated.map(ResponseEntity::ok)
                .orElse(ResponseEntity.notFound().build());
    }

    @DeleteMapping("/{id}")
    public ResponseEntity<Void> deleteSeries(@PathVariable Long id) {
        boolean deleted = seriesService.deleteSeries(id);
        return deleted ? ResponseEntity.noContent().build() : ResponseEntity.notFound().build();
    }
}

Model

A pasta model é onde vamos colocar as classes que representam as entidades do nosso sistema. Essas entidades são os objetos que vamos armazenar no banco de dados. No Spring Boot, geralmente usamos a anotação @Entity para indicar que uma classe é uma entidade, ou seja, ela será mapeada para uma tabela (se o banco for relacional) ou uma coleção (se for NoSQL) no banco de dados.

Embora o tipo de banco de dados (relacional ou NoSQL) mude a forma de como os dados são armazenados, o conceito de model é o mesmo. Vamos criar duas classes principais para representar as séries e temporadas:

  1. Series: Esta classe vai representar uma série de TV. Ela pode ter atributos como id, nome, gênero, ano de lançamento e temporadas.

  2. Season: Esta classe vai representar uma temporada da série. Ela pode ter atributos como número da temporadora e a quantidade de episódios.

Cada instância dessas classes será armazenada no banco de dados como um registro (no caso de banco de dados relacionais) ou um documento (no caso de bancos NoSQL), dependendo da configuração do seu banco de dados.

Classe Series

@Setter
@Getter
@Builder
public class Series {

    private Long id;
    private String name;
    private String genre;
    private int releaseYear;
    private List<Season> seasons;
}

Classe Season

@Setter
@Getter
@Builder
public class Season {
    private int seasonNumber;
    private int episodes;
}

Service

A pasta service é onde vamos reunir as classes responsáveis pela lógica de negócios da aplicação. O service é quem vai "fazer o trabalho pesado", ou seja, aplicar as regras de negócio, fazer cálculos, validar dados e interagir com o banco de dados. Normalmente, a interação com o banco de dados acontece por meio de uma interface chamada repository (vamos falar mais sobre isso em outro momento).

Como nossa aplicação não está conectada a um banco de dados, vamos simular as operações usando uma lista. Isso significa que no momento em que a aplicação for reiniciada, todos os dados serão perdidos, já que estamos armazenando tudo em memória.

Classe SeriesService

@Service
@RequiredArgsConstructor
public class SeriesService {

    private final List<Series> seriesDatabase = new ArrayList<>();
    private Long nextId = 1L;

    public Series createANewSeries(Series series) {
        series.setId(nextId++);
        seriesDatabase.add(series);
        return series;
    }

    public List<Series> listAllSeries() {
        return seriesDatabase;
    }

    public Optional<Series> findById(Long id) {
        return seriesDatabase.stream().filter(series -> series.getId().equals(id)).findFirst();
    }

    public Optional<Series> updateSeries(Long id, Series updatedSeries) {
        return findById(id).map(series -> {
            if (updatedSeries.getName() != null && !updatedSeries.getName().isEmpty()) {
                series.setName(updatedSeries.getName());
            }
            if (updatedSeries.getGenre() != null && !updatedSeries.getGenre().isEmpty()) {
                series.setGenre(updatedSeries.getGenre());
            }
            if (updatedSeries.getReleaseYear() > 0) {
                series.setReleaseYear(updatedSeries.getReleaseYear());
            }
            if (updatedSeries.getSeasons() != null && !updatedSeries.getSeasons().isEmpty()) {
                series.setSeasons(updatedSeries.getSeasons());
            }
            return series;
        });
    }


    public boolean deleteSeries(Long id) {
        return seriesDatabase.removeIf(series -> series.getId().equals(id));
    }
}

Parabéns por chegar até aqui! Agora nossa API possui as principais operações CRUD (Create, Read, Update e Delete). Para facilitar, o código completo está disponível no GitHub, junto com uma Collection do Postman para testes. Basta clicar aqui para acessá-los.

Caso tenha dúvida ou sugestão, fique à vontade para deixar nos comentários.

Nos vemos na próxima terça-feira! 😊