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.
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.
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:
- Você vai precisar de uma IDE como por exemplo: IntelliJ IDEA, Eclipse, VSCode.
- Instale a JDK 8 or 11+
- Instale o Apache Maven 3.5.3+ ou o Gradle
- (Opcional) — Você pode baixar o GraalVM 19.2.1 para compilar nativamente sua aplicação.
- Panache Entity (Possível artigo futuro)
- Docker
- Escolha um cliente para conectar com o Banco de dados, exemplo: DBeaver, PGAdmin, Postico (Mac)
- Cliente para realizar requisições REST: Postman ou o Insomnia.
Instruções Adicionais:
- Instalação do Docker (Documentação oficial)
- Instalando Docker no windows: (Youtube, ESR)
- Instalando o Docker no Linux: (Youtube: LinuxTips)
- Instalando o Docker no Mac: (Youtube: Wellington Rogati)
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:
- Sugestão: verifique a última versão no site oficial do Quarkus.io.
mvn io.quarkus:quarkus-maven-plugin:1.11.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.11.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/
Criando o projeto — Executando o comando de criação
mvn io.quarkus:quarkus-maven-plugin:1.11.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.
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:
Testando a API /food, através do navegador
A URI da nossa API está definida inicialmente na classe FoodResource.java, com o caminho : /food
Abra o navegador e digite o endereço: http://localhost:8080/food, conforme a imagem a seguir:
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 Dockerdocker 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:
No cliente SQL, vamos abrir o schema do Banco de dados e percebemos que o mesmo está vazio, não existe nenhuma tabela criada.
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
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.
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:
Cadastrando outro alimento no Postman
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 — 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.
Verificando os registros no Banco de Dados
Removendo um alimento através do Postman
Listando todos os alimentos através do Postman
No último teste, será listado todos os alimentos que estão cadastrados no banco de dados.
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