Desenvolvimento

Criando uma API Rest com Quarkus: Parte 2

Neste artigo vamos dar continuidade a criação do nossa API em quarkus para criar uma lista de tarefas, se você ainda não leu o primeiro artigo recomendamos que volte nele primeiro: Criando uma API Rest com Quarkus: Parte 1

Como Criar uma entidade usando o Panache

Para que o quarkus entenda que está trabalhando com uma entidade de banco de dados precisamos adicionar a seguinte anotação na nossa classe: @Entity. Na sequência devemos adicionar todos os nossos campos ficando da seguinte forma:

package br.com.codeinloop.model;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import jakarta.ws.rs.core.UriBuilder;

import java.net.MalformedURLException;
import java.net.URISyntaxException;
import java.net.URL;

@Entity
public class Todo {
    @Id
    @GeneratedValue
    private Long id;
    private String title;
    private boolean completed;
    @Column(name = "\"order\"")
    private Integer order;
    private URL url;


    public URL getUrl() throws URISyntaxException, MalformedURLException {
        if (this.id != null) {
            return UriBuilder.fromUri(url.toURI()).scheme(url.getProtocol()).path(this.id.toString()).build().toURL();
        }
        return this.url;
    }

}
Java

Nas linhas 15,16 e 17 estamos dizendo que queremos que o campo seja considerado um ID na tabela com o tipo Long e que ele tenha valores gerados dinamicamente (1,2,3…). Algo parecido fazemos com os demais campos com exceção do nome “order”, que por ser uma palavra reservada nos bancos de dados temos que dizer ao banco usando a anotação “@Column” que o nome seria outro, no nosso caso apenas escapamos.

no final deste arquivo temos da linha 25 em diante uma regra negocial em nosso get, a primeira vista vamos manter ele ali porém em evoluções futura vamos trazer ele para outras camadas. não incluimos neste arquivos todos os sets e gets fique a vontade para fazer isso ou usar bibliotecas como lombok

Como configurar a camada de comunicação com banco de dados via Repository

Esta é uma classe que na maioria das vezes vai conter todas as suas consultas personalizadas para aquela entidade, porém em nosso caso, por se tratar de operações simples não vamos precisar criar nada novo ou sobrescrever os existentes. Então para criação deste arquivo vamos apenas definir o escopo, existem vários tipos de escopos que você pode usar para gerenciar o ciclo de vida dos seus beans, neste vamos nos limitar ao @RequestScoped, em artigos futuros vamos abordar melhor cada um deles.

Para nosso arquivo ficaria assim:

package br.com.codeinloop.repository;

import br.com.codeinloop.model.Todo;
import io.quarkus.hibernate.orm.panache.PanacheRepository;
import jakarta.enterprise.context.RequestScoped;

@RequestScoped
public class TodoRepository implements PanacheRepository<Todo> {
}
Java

Como parte da nossa implementação devemos fazer uma implementação para classe PanacheRepository que usa uma generalização da sua entidade.

Criando nossa camada de negócios

Para simplificar nossa explicação vamos separar o arquivo em várias partes, a primeira com o modelo “puro” e na sequência vamos explicar nosso negocial. O arquivo puro de um serviço também precisa de um escopo e vamos usar o mesmo do repository, e como precisamos nos comunicar com a repository vamos injetar ele no nosso serviço da seguinte forma:

package br.com.codeinloop.service;

import br.com.codeinloop.model.Todo;
import br.com.codeinloop.repository.TodoRepository;
import io.quarkus.panache.common.Sort;
import jakarta.enterprise.context.RequestScoped;
import jakarta.inject.Inject;
import jakarta.ws.rs.NotFoundException;
import jakarta.ws.rs.core.UriInfo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.net.MalformedURLException;
import java.net.URL;
import java.util.List;
import java.util.Optional;

@RequestScoped
public class TodoService {

    private static final Logger LOGGER = LoggerFactory.getLogger(TodoService.class);
    @Inject
    TodoRepository repository;
    @Inject
    UriInfo uriInfo;


}
Java

Para fins de Logger de aplicação vamos inserir a linha 21 e para manipulação de URI a linha 25.

Criar

    public Todo create(Todo todo) throws MalformedURLException {
        LOGGER.info("Creating new Todo item: {}", todo);
        String scheme = uriInfo.getRequestUri().getScheme();
        URL uri = this.uriInfo.getAbsolutePathBuilder().scheme(scheme).build().toURL();
        todo.setUrl(uri);
        repository.persist(todo);
        LOGGER.info("Todo item created successfully: {}", todo);
        return todo;
    }
Java

Negocialmente não temos nenhuma regra explícita para a criação do nosso “todo” então apenas vamos pegar o objeto passado e passar para o banco de dados, porém caso fosse necessário algum tipo de validação complexa poderíamos fazer aqui. Entenda validação complexa como por exemplo algo que precisa de consulta no banco de dados, já os simples poderiamos usar o hibernate validator para validar campos nulos e afins.

Listar

    public List<Todo> readAll() {
        LOGGER.info("Fetching all Todo items");
        List<Todo> todoList = repository.listAll(Sort.by("order"));
        LOGGER.info("Found {} Todo items", todoList.size());
        return todoList;
    }
    public Todo readOne(Long id) {
        try {
            LOGGER.info("Fetching Todo item with id: {}", id);
            return this.validateIfExistsAtDatabase(id);
        } catch (NotFoundException e) {
            LOGGER.error("Todo item not found for id: {}", id, e);
            throw e;
        }
    }
    private Todo validateIfExistsAtDatabase(Long id){
        Optional<Todo> todoExist = repository.findByIdOptional(id);
        return todoExist.orElseThrow(() -> new NotFoundException("Item not found"));
    }
Java

Como parte do negocial definido pelo todobackend, devemos prover 2 endpoints, um que lista tudo e outro que pega apenas um com base no ID, e para garantir que o ID vai existir criamos um método comum, pois vamos usar várias vezes, ele que vai validar se o ID existe senão, ele retorna um erro 404 com a mensagem “Item not found”.

Atualizar

    public Todo Update(Long id, Todo todoForUpdate) {
        Todo todoStored = this.validateIfExistsAtDatabase(id);
        return update(todoStored, todoForUpdate);
    }
    private Todo update( Todo todoStored, Todo todoForUpdate) {
        merge(todoStored, todoForUpdate);
        repository.persistAndFlush(todoStored);
        return todoStored;
    }
    private void merge(Todo current, Todo todoItem) {
        if (todoItem.getTitle() != null) {
            current.setTitle(todoItem.getTitle());
        }
        if (todoItem.getOrder() != null) {
            current.setOrder(todoItem.getOrder());
        }
        current.setCompleted(todoItem.getCompleted());
    }
Java

Para nosso fluxo de atualização criamos dois métodos update apenas para demonstrar um exemplo de sobrecarga porém o negocial mesmo está no metodo “merge”. Ele basicamente verifica se o valor vindo da tela é diferente de nulo, se sim ele configura o valor a ser salvo, se não mantém o valor do banco de dados.

Deletar

    public void deleteOne(Long id) {
       this.validateIfExistsAtDatabase(id);
       repository.deleteById(id);
    }
    public void deleteAll() {
            repository.deleteAll();
    }
Java

Semelhante ao listar, aqui também precisamos de um para deletar tudo e outro para deletar um por ID e vamos novamente usar o método validateIfExistsAtDatabase.

Conclusão

E com isso finalizamos a parte 2 do nosso crud para o todobackend, caso não tenha visto a primeira parte não deixe de conferir aqui: Criando uma API Rest com Quarkus: Parte 1. Para a próxima parte vamos falar sobre nossa estrutura REST. O que achou deste artigo? Ficou com dúvida? Se quiser ver o código completo, estamos publicando ele no github.

Deixe um comentário

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *

Esse site utiliza o Akismet para reduzir spam. Aprenda como seus dados de comentários são processados.