Tutorial: Criando um CRUD utilizando Quarkus Java + REST + CDI + Panache, Hibernate com Postgres (Docker) + Postman
Quarkus IO

Tutorial: Criando um CRUD utilizando Quarkus Java + REST + CDI + Panache, Hibernate com Postgres (Docker) + Postman

Introdução

Os programadores de Java estão "cansados" de ouvir as seguintes expressões: "Java é lento!", "O pacote do java é grande", "O tempo de inicialização é lento!". Como uma forma de resolver isso, a Red Hat lançou o Quarkus, um framework Java nativo do Kubernetes feito sob medida para o GraalVM e OpenJDK HotSpot. O Quarkus visa tornar o java uma plataforma líder em ambientes serverless e Kubernetes, oferecendo aos desenvolvedores um modelo unificado de programação reativa e imperativa.

Imagem: Java é uma esquema do capitalismo para vender RAM

O QuarkusIO, promete entregar pacotes menores, com um tempo de inicialização extremanente rápido, menor consumo de memória, desde que combinado com a GraalVM, O Quarkus vai realizar a compilação Ahead-of-time(AOT). Baseada nos melhores padrões, plataforma integrada, baixo consumo de processamento e tempo de inicialização é incrivelmente rápido, em milisegundos, Quarkus permite o uso do Java em ambientes serverless, suportando um ambiente responsivo e escalável.

Na imagem abaixo, podemos ver a comparação do consumo e memória e o tempo de inicialização + tempo da resposta da primeira requisição, podemos perceber que o Quarkus + OpenJDK + GraalVM, consomem menos memória e iniciam mais rapidamente, do que a o Quarkus + OpenJDK e um framework equivalente de mercado.

Quadro comparativo entre o Quarkus e um framework de mercado

Um grande diferencial do Quarkus é que ele suporta as principais especificações, ou seja, você, não precisa aprender nada de novo. Por exemplo, você pode utilizar o CDI e JAX-RS, também é possível suportar as extensões como: Hibernate, Kafka, OpenShift, Kubernetes. Na era do cloud, no qual containers, Kubernetes, microservices, functions-as-a-service (Faas), e aplicações nativas para o cloud estão apresentando altos níveis de produtividade e eficiência, o Quarkus surge com uma alternativa muito interessante.

Antes de continuar o post, gostaria de compartilhar a apresentação que fiz nesse ano.

Apresentação no Evento Brasília Dev Festival 2019

Em Setembro de 2019, palestrei no evento Brasília Dev Festival, realizado em Brasília, foi um evento recheado de muito conhecimento e network, a apresentação está disponível abaixo:

Parte prática - Hands-on (Tutorial passo a passo)

Antes de iniciar o desenvolvimento da aplicação, é necessário atender os requisitos mínimos abaixo:

Requisitos mínimos:

Instruções Adicionais:

Escopo da aplicação

A partir de agora, vamos criar uma aplicação que será desenvolvida com o Quarkus, utilizando o Panache + Hibernate para persistência, vamos utilizar o CDI para Injeção de Dependência e JAX-RS para a API REST.

Vamos criar uma API para Listar, Cadastrar, Editar e Excluir alimentos.

O Quarkus possui um Archetype para criação da aplicação de forma fácil, basta executar o comando abaixo:

mvn io.quarkus:quarkus-maven-plugin:1.0.1.Final:create \
     -DprojectGroupId=br.com.food \
     -DprojectArtifactId=quarkus-food \
     -DclassName="br.com.food.resource.FoodResource" \
     -Dpath="/food"

$ cd quarkus-food

Vamos entender o que cada trecho significa:

# Plugin do Maven para criação do Projeto no Quarkus
$ mvn io.quarkus:quarkus-maven-plugin:1.0.1.Final:create

# Definição do pacote do projeto
-DprojectGroupId=br.com.food

# Definição do Nome do Projeto
-DprojectArtifactId=quarkus-food

# Definindo o caminho da classe Rest (inicial)
-DclassName="br.com.food.resource.FoodResource"

# Definição da URI, final do endereço para acessar no navegador
-Dpath="/food

O Quarkus disponibiliza um site chamado Quarkus.code.io, onde é posísvel configurar o projeto de uma forma mais visual, vale a pena conferir, segue o link: https://code.quarkus.io/

Site do Quarkus.code.io, para criação de um projeto de forma visual

Criando o projeto - Executando o comando de criação

mvn io.quarkus:quarkus-maven-plugin:1.0.1.Final:create \
>     -DprojectGroupId=br.com.mp \
>     -DprojectArtifactId=food \
>     -DclassName="br.com.mp.FoodResource" \
>     -Dpath="/food"


// Log omitido 
[INFO] Your application will be accessible on http://localhost:8080
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  42.305 s
[INFO] Finished at: 2019-12-12T09:36:11-02:00
[INFO] ------------------------------------------------------------------------

Log completo você encontra aqui: https://pastebin.com/UfczCUDL

Observação: A criação do projeto pode levar um tempo, já que o Maven, irá realizar o download de todas as dependências


Abra a aplicação em sua IDE preferida

Para esse exemplo, estou utilizando o InteliJ, porém, gosto bastante do Eclipse e Visual code, sinta-se a vontade para escolher sua IDE favorita.

Na imagem abaixo, podemos ver a estrutura do projeto (Maven), ressaltando a criação dos arquivos dentro da pasta Docker, Dockerfile.jvm e Dockerfile.native, que pretendo falar em um outro artigo. Logo abaixo, veremos o arquivo FoodResource, essa classe é a responsável por "expor" a API.

IDE IntelliJ visão geral do projeto.

Executando o projeto inicial

No terminal, execute o comando abaixo:

mvn compile quarkus:dev

Resultado da execução:

Possivelmente, na primeira execução o compilador irá verificar se existe alguma dependência para baixar, como no exemplo abaixo.
[INFO] Scanning for projects...
[INFO] 
[INFO] ---------------------------< br.com.mp:food >---------------------------
[INFO] Building food 1.0-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO] 
[INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ food ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] Copying 2 resources
// LOG OMITIDO 
[INFO] ---------------------------< br.com.mp:food >---------------------------
[INFO] Building food 1.0-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO] 
[INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ food ---
2019-12-12 15:12:19,730 INFO  [io.quarkus] (main) Quarkus 1.0.1.Final started in 0.735s. Listening on: http://0.0.0.0:8080
2019-12-12 15:12:19,740 INFO  [io.quarkus] (main) Profile dev activated. Live Coding activated.
2019-12-12 15:12:19,741 INFO  [io.quarkus] (main) Installed features: [cdi, resteasy]

Log completo da execução da aplicação: https://pastebin.com/pnLdVYsc

No final Log, aparece a URL para acessar com o navegador.

[io.quarkus] (main) Quarkus 1.0.1.Final started in 0.735s. Listening on: http://0.0.0.0:8080

Você pode acessar a API através dos endereços: http://localhost:8080, http://0.0.0.0:8080 ou http://127.0.0.1:8080.

Para acessar a página, basta clicar no endereço exposto no terminal ou simplesmente copiar e colar no seu navegador, como mostrado no exemplo abaixo:

Foto da tela inicial de um projeto criado com o Quarkus

Testando a API /food, através do navegador

A URI da nossa API está definida inicialmente na classe FoodResource.java, com o caminho : /food

Código-fonte da classe FoodResource.java

Abra o navegador e digite o endereço: http://localhost:8080/food, conforme a imagem a seguir:

Não foi fornecido texto alternativo para esta imagem

Executando uma instância do Postgresql no Docker

Para armazenar as informações, vamos subir uma instância do Postgresql, utilizando o Docker, para isso, é necessário ter o Docker instalado e executar o comando listado abaixo:

# Criando uma instância do Postgressql através do Docker

docker run --name postgres-food -e "POSTGRES_PASSWORD=postgres" -p 5433:5432 -v ~/developer/PostgreSQL:/var/lib/postgresql/data -d postgres
Observação: Precisei mudar a porta padrão de 5432 para 5433, já que tinha uma outra instância rodando aqui.

Vamos testar a conexão com o Banco de dados, no meu caso, eu estou utilizando o DBeaver 6.1.0, porém, existem diversas outras aplicações para conectar com o Banco de Dados PostgreSQL, abaixo, segue o teste da conexão do banco de dados:

Cliente SQL para testar a conexão com o banco de dados.

No cliente SQL, vamos abrir o schema do Banco de dados e percebemos que o mesmo está vazio, não existe nenhuma tabela criada.

Não foi fornecido texto alternativo para esta imagem

Voltando para a nossa aplicação

Retorne para sua IDE e adicione as seguintes dependências para o PanacheEntity e a dependência do Postgresql, as informações devem ser adicionadas no arquivo pom.xml (Na raiz do projeto).

<!-- Hibernate ORM specific dependencies -->
<dependency>
  <groupId>io.quarkus</groupId>
  <artifactId>quarkus-hibernate-orm-panache</artifactId>
</dependency>

<!-- JDBC driver dependencies -->
<dependency>
  <groupId>io.quarkus</groupId>
  <artifactId>quarkus-jdbc-postgresql</artifactId>
</dependency>

<!-- JSONB serialize JSON -->
<dependency>
  <groupId>io.quarkus</groupId>
  <artifactId>quarkus-resteasy-jsonb</artifactId>

</dependency>
O Panache Entity visa facilitar a implementação da camada de persistência da aplicação. Além disso, o framework trás diversos métodos como Count, ListAll(), findById, persist, delete já implementados, conforme exibido código abaixo

Exemplo do PanacheEntity (Retirado do site: https://quarkus.io/guides/hibernate-orm-panache)

// creating a person
Person person = new Person();
person.name = "Stef";
person.birth = LocalDate.of(1910, Month.FEBRUARY, 1);
person.status = Status.Alive;

// persist it
person.persist();

// note that once persisted, you don't need to explicitly save your entity: all
// modifications are automatically persisted on transaction commit.

// check if it's persistent
if(person.isPersistent()){
    // delete it
    person.delete();
}

// getting a list of all Person entities
List<Person> allPersons = Person.listAll();

// finding a specific person by ID
person = Person.findById(personId);

// finding all living persons
List<Person> livingPersons = Person.list("status", Status.Alive);

// counting all persons
long countAll = Person.count();

// counting all living persons
long countAlive = Person.count("status", Status.Alive);

// delete all living persons
Person.delete("status", Status.Alive);

// delete all persons
Person.deleteAll();
Observação: O PanacheEntity é muito interessante, porém, vou explicar melhor em um artigo futuro.

Voltando para a implementação da classe de modelo (Entity)

Na IDE, crie a classe (Entidade): Food.java, dentro da pasta br.com.food.entity.

Classe: Food.java

package br.com.food.entity;

import io.quarkus.hibernate.orm.panache.PanacheEntityBase;

import javax.persistence.*;
import java.util.Objects;

@Entity
public class Food extends PanacheEntityBase {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @Column
    private String name;
    
    @Column
    private Double calories;

// Omitido
}

Configurando a conexão de Banco de dados no projeto

Procure o arquivo application.properties, dentro da pasta resources e adicione as seguintes configurações:

# configure your datasource
quarkus.datasource.url = jdbc:postgresql://localhost:5433/food
quarkus.datasource.driver = org.postgresql.Driver
quarkus.datasource.username = postgres
quarkus.datasource.password = postgres

# drop and create the database at startup (use `update` to only update the schema)
quarkus.hibernate-orm.database.generation = drop-and-create
Ao executar nossa aplicação, nos deparamos com o seguinte problema: Caused by: org.postgresql.util.PSQLException: FATAL: database "food" does not exist
Caused by: org.postgresql.util.PSQLException: FATAL: database "food" does not exist
        at org.postgresql.core.v3.QueryExecutorImpl.receiveErrorResponse(QueryExecutorImpl.java:2468)
        at org.postgresql.core.v3.QueryExecutorImpl.readStartupMessages(QueryExecutorImpl.java:2587)
        at org.postgresql.core.v3.QueryExecutorImpl.<init>(QueryExecutorImpl.java:134)
        at org.postgresql.core.v3.ConnectionFactoryImpl.openConnectionImpl(ConnectionFactoryImpl.java:250)
        at org.postgresql.core.ConnectionFactory.openConnection(ConnectionFactory.java:49)
        at org.postgresql.jdbc.PgConnection.<init>(PgConnection.java:195)
        at org.postgresql.Driver.makeConnection(Driver.java:458)
        at org.postgresql.Driver.connect(Driver.java:260)

Solução: Criar um Database no Postgresql, através do ClienteSQL
No cliente SQL criar uma nova Tabela (Database) chamada Food


Não foi fornecido texto alternativo para esta imagem

Executando a aplicação com o Banco de Dados configurado

Retornando para a nossa IDE, vamos executar o comando que irá iniciar nossa aplicação:

$ mvn compile quarkus:dev


[INFO] Scanning for projects...
[INFO] 
[INFO] --------------------------< br.com.food:food >--------------------------
[INFO] Building food 1.0-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO] 
[INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ food ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] Copying 2 resources
[INFO] 
[INFO] --- maven-compiler-plugin:3.8.1:compile (default-compile) @ food ---
[INFO] Changes detected - recompiling the module!
[INFO] Compiling 2 source files to /home/marcus/Estudo/Quarkus/food/target/classes
[INFO] 
[INFO] --- quarkus-maven-plugin:1.0.1.Final:dev (default-cli) @ food ---
Listening for transport dt_socket at address: 5005
2019-12-12 16:44:24,885 WARN  [org.hib.eng.jdb.spi.SqlExceptionHelper] (main) SQL Warning Code: 0, SQLState: 00000
2019-12-12 16:44:24,891 WARN  [org.hib.eng.jdb.spi.SqlExceptionHelper] (main) table "food" does not exist, skipping
2019-12-12 16:44:25,135 INFO  [io.quarkus] (main) Quarkus 1.0.1.Final started in 1.715s. Listening on: http://0.0.0.0:8080
2019-12-12 16:44:25,136 INFO  [io.quarkus] (main) Profile dev activated. Live Coding activated.
2019-12-12 16:44:25,136 INFO  [io.quarkus] (main) Installed features: [agroal, cdi, hibernate-orm, hibernate-orm-panache, jdbc-postgresql, narayana-jta, resteasy]
Obversação 1: Assim que finalizar a execução, percebemos que a tabela Food foi criada no banco de dados.
Obvervação 2: Caso não queira o comportamento de criação automatica do banco de dados, basta comentar ou remover a linha: "quarkus.hibernate-orm.database.generation = drop-and-create", de dentro do arquivo do application.properties.

Verificando se a tabela foi criada no Banco de Dados.

Não foi fornecido texto alternativo para esta imagem
Espero que esteja gostando desse artigo.

Implementando uma simples classe de negócio

Crie uma classe chamada de FoodController.java, dentro da pasta br.com.food.controller. Essa classe será responsável por conter uma regra de negócio simples.

@ApplicationScoped
public class FoodController {

    public Food update(Long id, Food food) {
        Food foodEntity = Food.findById(id);

        if (foodEntity == null) {
            throw new WebApplicationException("Food with id of " + id + " does not exist.", Response.Status.NOT_FOUND);
        }

        foodEntity.setName(food.getName());
        foodEntity.setCalories(food.getCalories());

        return foodEntity;
    }

    /**
     * This method is main purpose to show simple "Business" example
     * @param food
     * @return
     */
    public boolean isFoodNameIsNotEmpty(Food food) {
        return food.getName().isEmpty();
 
   }
}

Implementação do FoodResouce.java

A classe FoodResource, é responsável por expor a API com os métodos para cadastro, edição, remoção e lista de todos os alimentos cadastrados.

# FoodResource.java
package br.com.food.resource;
// OMITIDO

@Path("/food")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public class FoodResource {

    @Inject
    private FoodController foodController;

    @GET
    public List<Food> findAll() {
        return Food.listAll();
    }

    @POST
    @Transactional
    public Response create(Food food) {
        Food.persist(food);
        return Response.ok(food).status(201).build();
    }

    @PUT
    @Path("{id}")
    @Transactional
    public Response update(@PathParam("id") Long id, Food food) {

        if (foodController.isFoodNameIsNotEmpty(food)) {
            return Response.ok("Food was not found").type(MediaType.APPLICATION_JSON_TYPE).build();
        }

        Food foodEntity = foodController.update(id, food);

        return Response.ok(foodEntity).build();
    }

    @DELETE
    @Path("{id}")
    @Transactional
    public Response delete(@PathParam("id") Long id) {
        Food foodEntity = Food.findById(id);

        if (foodEntity == null) {
            throw new WebApplicationException("Food with id " + id + " does not exist.", Response.Status.NOT_FOUND);
        }

        foodEntity.delete();
        return Response.status(204).build();
    }
}

Observação: Reparem que é possível efetuar uma operação de persistência tanto na camada de Resource(API), quanto na classe de negócio ou até mesmo em uma classe Data Access Object - DAO.

Testando a API através do Postman

Abra o Postman, selecione o método POST e digite a URL: http://localhost:8080/food. Adicionalmente, adicione um Body com os dados conforme a imagem abaixo:

Postman - Criação de um alimento

Cadastrando outro alimento no Postman

Não foi fornecido texto alternativo para esta imagem

Listando todas os alimentos no Postman

No Postman, crie uma outra requisição do tipo GET e digite a url: http://localhost:8080/food, o resultado será a lista de todos os alimentos cadastrados no banco de dados, conforme a imagem abaixo:

Postman - listando todos os alimentos cadastrados no banco de dados.

Postman - Atualizando um objeto

Para atualizar a descrição e/ou a caloria de um alimento, basta criar uma nova requisição do tipo PUT, adicionar no final da URL o código (ID) do alimento que modificado. Não esqueça de preencher o body, que contém o JSON do objeto alterado.

Não foi fornecido texto alternativo para esta imagem

Verificando os registros no Banco de Dados

Não foi fornecido texto alternativo para esta imagem

Removendo um alimento através do Postman

Não foi fornecido texto alternativo para esta imagem

Listando todos os alimentos através do Postman

No último teste, será listado todos os alimentos que estão cadastrados no banco de dados.

Não foi fornecido texto alternativo para esta imagem

Conclusão

Esse é o meu primeiro artigo e espero que vocês tenham gostado, sinta-se à vontade para sugerir, criticar ou elogiar. Um grande abraço e até a próxima.

Código-Fonte

O código-fonte está disponível no endereço: https://github.com/marcuspaulo/quarkus-food

Tony F.

Angular Developer and Java Junior.

3y

Parabéns

Like
Reply
Marcelo Leonardo Silva

Analista de Desenvolvimento | Cibersegurança

3y

Obrigado por compartilhar. Estou trabalhando em um novo projeto e escolhi o Quarkus como framwork. Estou animado!! Vida que segue.

Luiz Leão

Company Owner na Quimera Tecnologia

4y

Parabéns, Mr Marcus!! Feliz pelo sr. 

Felipe Henrique Gross Windmoller

Staff Software Engineer at Banco do Brasil

4y

Parabéns, muito bem detalhado!

Fábio Bezerra

Associate Professor at UFRA - Universidade Federal Rural da Amazônia

4y

Parabéns!

To view or add a comment, sign in

Others also viewed

Explore topics