Skip to content

vinicius-martinez/quarkus-workshop

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

26 Commits
 
 
 
 
 
 

Repository files navigation

Quarkus Workshop

Descrição

Esse workshop tem o objetivo de ilustrar algumas capacidades do Quarkus. A stack para desenvolvimento Java para aplicações Cloud Native e Serverless.

Para maiores informações, por favor consulte a seção Referências Adicionais.

Ambiente

Demonstrações - Walkthrough

  1. Criação de um Projeto Quarkus
  2. Execução do Projeto
  3. Geração do Pacote
  4. Compilações Quarkus
  5. Restfull WebServices
  6. Adicionar suporte a JSON-B
  7. Criação da Camada de Service - Dependency Injection
  8. Adicionar suporte a OpenAPI/Swagger
  9. Inclusão Persistência - Hibernate Panache
  10. Inclusão BuscaCEP - MicroProfile Rest Client
  11. Inclusão Monitoramento - MicroProfile Metrics
  12. Implementar Tolerância Falha - MicroProfile Fault Tolerance
  13. Implementar APM - OpenTracing
  14. Segurança - Keycloak/OAuth/OIDC/JWT

Demonstrações - How To

  1. Quarkus para Desenvolvedores Spring Boot
  2. Integração com Kafka
  3. Quarkus & Camel

0 - Criação de um Projeto Quarkus

  • Criação de um projeto básico com o VSCode e seu respectivo plugin para Quarkus

    Cmd/Ctrl + Shift + P
    Generate a Quarkus Project
    Selecione a ferramenta de build de sua escolha: Maven ou Gradle
    Informe o GAV (Group, Artifact, Version)
    Escolha o nome do recurso (ex. GreetingResource)
    Não adicione nenhuma extensão adicional, portanto, aperte <ENTER>
    Selecione diretório destino do projeto gerado
    
  • Criação possível através do Quarkus Initializer

  • Criação possível através do Maven de maneira tradicional:

    mvn io.quarkus:quarkus-maven-plugin:1.5.2.Final:create \
      -DprojectGroupId=my-groupId \
      -DprojectArtifactId=my-artifactId \
      -DprojectVersion=my-version \
      -DclassName="org.my.group.MyResource"
    

1 - Inicialização de um Projeto Quarkus

  • Navegue até o diretório do projeto e execute o comando:

    /mvnw compile quarkus:dev -DskipTests
    
  • Outra alternativa seria através do VSCode:

    Cmd/Ctrl + Shift + P
    Quarkus: Debug current Quarkus Project
    
  • Invoque o Endpoint criado:

    http :8080/hello
    
  • O seguinte output é esperado:

    http :8080/hello
    HTTP/1.1 200 OK
    Content-Length: 5
    Content-Type: text/plain;charset=UTF-8
    
    hello
    
    • por padrão o projeto é criado utilizando JDK 11, portanto, caso sua JVM seja de uma versão inferior, por exemplo 1.8, é necessário alteração arquivo pom.xml:

      <maven.compiler.source>1.8</maven.compiler.source>
      <maven.compiler.target>1.8</maven.compiler.target>
      

2 - Geração do Pacote

  • Navegue até o diretório do projeto e execute o comando:

    ./mvnw package -DskipTests
    
  • Certifique-se que a aplicação foi compilada com sucesso, e após esse processo, inicialize a mesma:

    java -jar target/quarkus-getting-started-1.0.0-SNAPSHOT-runner.jar
    
  • Verifique o tamanho do arquivo jar gerado, quantidade de memória utilizada e tempo de bootstrap:

    du -sh ./target/*runner.jar  ./target/lib
    ps -o pid,rss,command -p <pid>
    
    • arquivo "-runner.jar" consiste no jar executável. Necessária a presença do diretório /lib. Maiores detalhes nesse LINK
    • caso necessite criar um uber-jar, basta adicionar as seguintes properties no arquivo application.properties. Maiores detalhes nesses links: MAVEN GRADLE

3 - Compilações Quarkus

  • Maiores detalhes sobre esses processos estão disponíveis nesses links: Native Image Container Image

  • Geração de uma imagem Docker com binário tradicional:

    docker build -f src/main/docker/Dockerfile.jvm -t getting-started-jvm .
    docker images | grep 'quarkus-quickstart'
    
  • Geração de uma imagem Docker com binário compilado nativamente (macOS):

    ./mvnw package -Pnative -DskipTests
    file <file>
    ./target/<file>
    docker build -f src/main/docker/Dockerfile.native -t getting-started-native .
    docker images | grep 'getting-started-native'
    
  • Geração de uma imagem Docker com binário compilado nativamente (Linux):

    ./mvnw package -Pnative -Dquarkus.native.container-build=true -DskipTests
    docker build -f src/main/docker/Dockerfile.native -t getting-started-native-linux .
    docker images | grep 'getting-started-native-linux'
    
  • Execução das imagens:

    docker run -it -p 8080:8080 getting-started-jvm
    docker run -it -p 8180:8080 getting-started-native
    docker run -it -p 8280:8080 getting-started-native-linux
    
  • Monitoramento dos Containers:

    docker stats
    
  • Execução Benchmarks de Performance:

    ab -n 50000 -c 10 http://localhost:8080/hello
    ab -n 50000 -c 10 http://localhost:8280/hello
    

4 - Restfull WebServices

  • Criação de um projeto básico com o VSCode e seu respectivo plugin para Quarkus:

    Cmd/Ctrl + Shift + P
    Generate a Quarkus Project
    Selecione a ferramenta de build de sua escolha: Maven ou Gradle
    Informe o GAV (Group, Artifact, Version)
    Escolha o nome do recurso (ex. CustomerResource)
    Não adicione nenhuma extensão adicional, portanto, aperte <ENTER>
    Selecione diretório destino do projeto gerado
    
  • Criação possível através do Quarkus Initializer

  • Criação possível através do Maven de maneira tradicional:

    mvn io.quarkus:quarkus-maven-plugin:1.5.2.Final:create \
      -DprojectGroupId=br.com.redhat.quarkus \
      -DprojectArtifactId=customer \
      -DprojectVersion=1.0.0-SNAPSHOT \
      -DclassName="br.com.redhat.quarkus.CustomerResource"
    
    • por padrão o projeto é criado utilizando JDK 11, portanto, caso sua JVM seja de uma versão inferior, por exemplo 1.8, é necessário alteração arquivo pom.xml:

      <maven.compiler.source>1.8</maven.compiler.source>
      <maven.compiler.target>1.8</maven.compiler.target>
      
  • Inicialização da aplicação em dev mode:

    ./mvnw compile quarkus:dev
    
  • Mudança do Produces para MediaType.APPLICATION_JSON:

    @GET
    @Produces(MediaType.APPLICATION_JSON)
    public String hello() {
        return "hello";
    }
    
  • Criação do Domínio Customer:

    package br.com.redhat.quarkus;
    
    public class Customer {
    
      public String primeiroNome;
      public Integer rg;
      public String sobreNome;
    
    
      public String getPrimeiroNome() {
      	return primeiroNome;
      }
      public void setPrimeiroNome(String primeiroNome) {
      	this.primeiroNome = primeiroNome;
      }
      public Integer getRg() {
      	return rg;
      }
      public void setRg(Integer rg) {
      	this.rg = rg;
      }
      public String getSobreNome() {
      	return sobreNome;
      }
      public void setSobreNome(String sobreNome) {
      	this.sobreNome = sobreNome;
      }
    
      @Override
      public int hashCode() {
      	final int prime = 31;
      	int result = 1;
      	result = prime * result + ((rg == null) ? 0 : rg.hashCode());
      	return result;
      }
      @Override
      public boolean equals(Object obj) {
      	if (this == obj)
      		return true;
      	if (obj == null)
      		return false;
      	if (getClass() != obj.getClass())
      		return false;
      	Customer other = (Customer) obj;
      	if (rg == null) {
      		if (other.rg != null)
      			return false;
      	} else if (!rg.equals(other.rg))
      		return false;
      	return true;
      }
    
    }
    

5 - Adicionar suporte a JSON-B

  • Maiores detalhes em na documentação JSON-B

  • Modificar o Restfull WebService para retornar a entidade Customer:

    public Customer hello() {
        Customer customer = new Customer();
        customer.setPrimeiroNome("nome1");
        customer.setSobreNome("sobreNome1");
        customer.setRg(101010);
        return customer;
    }
    
  • Ao tentar invocar o WebService o seguinte erro é esperado:

    http :8080/hello    
    HTTP/1.1 500 Internal Server Error
    Content-Length: 124
    Content-Type: text/html;charset=UTF-8
    
    Could not find MessageBodyWriter for response object of type: br.com.redhat.quarkus.Customer of media type: application/json
    
  • Incluir extension RestEasy JSON-B:

    Quarkus: Add extensions to current project
    RESTEasy JSON-B
    
  • Modificar path do WebService para Customers:

    @Path("/customers")
    public class CustomerResource {
    
  • Ao tentar invocar o WebService o seguinte retorno é esperado:

    HTTP/1.1 200 OK
    Content-Length: 62
    Content-Type: application/json
    
    {
      "primeiroNome": "nome1",
      "rg": 101010,
      "sobreNome": "sobreNome1"
    }
    

6 - Criação da Camada de Service - Dependency Injection

  • Referência do suporte a CDI pelo Quarkus

  • Criar a classe CustomerService:

    @ApplicationScoped
    public class CustomerService {
    
        private Set<Customer> customerSet = new HashSet<Customer>(0);
    
        public Customer addCustomer(Customer customer){
            if (customerSet.contains(customer)){
                for (Customer customerEntity : customerSet) {
                    if (customerEntity.equals(customer)){
                        return customerEntity;
                    }
                }
            }
            customerSet.add(customer);
            return customer;
        }
    
        public Customer getCustomer(Customer customer){
            if (customerSet.contains(customer)){
                for (Customer customerEntity : customerSet) {
                    if (customerEntity.equals(customer)){
                        return customerEntity;
                    }
                }
            }
    		return null;
        }
    
        public Set<Customer> listCustomer(){
            return customerSet;
        }
    
        public Customer deleteCustomer(Customer customer){
            if (customerSet.contains(customer)){
                customerSet.remove(customer);
                return customer;
            }
            return null;
        }
    
        public Customer updateCustomer(Customer customer){
            if (customerSet.contains(customer)){
                for (Customer customerEntity : customerSet) {
                    if (customerEntity.equals(customer)){
                        customerEntity.setRg(customerEntity.getRg());
                        customerEntity.setPrimeiroNome(customer.getPrimeiroNome());
                        customerEntity.setSobreNome(customer.getSobreNome());
                        return customerEntity;
                    }
                }
            }
            return null;
        }
    
    }
    
  • Customizar a classe CustomerResource:

    @Path("/customers")
    public class CustomerResource {
    
        @Inject
        CustomerService customerService;
    
        @GET
        @Produces(MediaType.APPLICATION_JSON)
        public Set<Customer> listCustomer(){
            return customerService.listCustomer();
        }
    
        @GET
        @Consumes(MediaType.APPLICATION_JSON)
        @Produces(MediaType.APPLICATION_JSON)
        @Path("/rg/{rg}")
        public Customer getCustomer(@PathParam("rg") Integer rg){
            Customer customerEntity = new Customer();
            customerEntity.setRg(rg);
            customerEntity = customerService.getCustomer(customerEntity);
            return customerEntity;
        }
    
        @POST
        @Consumes(MediaType.APPLICATION_JSON)
        @Produces(MediaType.APPLICATION_JSON)
        public Customer addCustomer(Customer customer){
            Customer customerEntity = customerService.addCustomer(customer);
            return customerEntity;
        }
    
        @PUT
        @Consumes(MediaType.APPLICATION_JSON)
        @Produces(MediaType.APPLICATION_JSON)
        public Customer updatCustomer(Customer customer){
            Customer customerEntity = customerService.updateCustomer(customer);
            return customerEntity;
        }
    
        @DELETE
        @Consumes(MediaType.APPLICATION_JSON)
        @Produces(MediaType.APPLICATION_JSON)
        @Path("/rg/{rg}")
        public Customer deleteCustomer(@PathParam("rg") Integer rg){
            Customer customerEntity = new Customer();
            customerEntity.setRg(rg);
            customerEntity = customerService.deleteCustomer(customerEntity);
            return customerEntity;
        }
    
    }
    
  • Execução de Testes da RestFull API CustomerResource:

    http :8080/customers
    http POST :8080/customers rg=11111 primeiroNome=nome1 sobreNome=sobrenome1
    http POST :8080/customers rg=22222 primeiroNome=nome2 sobreNome=sobrenome2
    http :8080/customers/rg/11111
    http :8080/customers/rg/22222
    http PUT :8080/customers rg=11111 primeiroNome=nome1Editado sobreNome=sobrenome1Editado
    http :8080/customers/rg/11111
    http :8080/customers
    http DELETE :8080/customers/rg/11111
    http :8080/customers
    

7 - Adicionar suporte a OpenAPI/Swagger

  • Maiores detalhes em na documentação OpenAPI

  • Incluir extension Quarkus SmallRye OpenApi:

    Quarkus: Add extensions to current project
    SmallRye OpenAPI
    
  • Habilitar Swagger UI em ambiente produtivo:

    application.properties
    quarkus.swagger-ui.always-include = true
    
  • Acessar Swagger UI ou OpenAPI Endpoint:

    http://localhost:8080/swagger-ui/
    http :8080/openapi
    
  • Criação da classe CustomerAPIApplication

    import org.eclipse.microprofile.openapi.annotations.OpenAPIDefinition;
    import org.eclipse.microprofile.openapi.annotations.info.Contact;
    import org.eclipse.microprofile.openapi.annotations.info.Info;
    import org.eclipse.microprofile.openapi.annotations.info.License;
    import org.eclipse.microprofile.openapi.annotations.tags.Tag;
    
    @OpenAPIDefinition(
        tags = {
            @Tag(name="customers", description="API de Gerenciamento de Customers"),
        },
        info = @Info(
            title="Customers API",
            version = "1.0.0",
            contact = @Contact(
                name = "Customers API Support",
                url = "http://customersapi.com/contact",
                email = "customersapi@customersapi.com"),
            license = @License(
                name = "Apache 2.0",
                url = "http://www.apache.org/licenses/LICENSE-2.0.html"))
    )
    public class CustomerAPIApplication extends Application{
    
    }
    
  • Acessar Swagger UI ou OpenAPI Endpoint:

    http://localhost:8080/swagger-ui/
    http :8080/openapi
    

8 - Inclusão Persistência - Hibernate Panache

  • Maiores detalhes em na documentação Hibernate Panache

  • Incluir extensions Hibernate ORM with Panache e JDBC Driver PostgreSQL:

    Quarkus: Add extensions to current project
    Hibernate ORM with Panache
    JDBC Driver - PostgreSQL
    
  • Habilitar Swagger UI em ambiente produtivo:

    application.properties
    quarkus.swagger-ui.always-include = true
    
  • Acessar Swagger UI:

    http://localhost:8080/swagger-ui/
    
  • Maiores detalhes em na documentação Hibernate Panache

  • Incluir extension Hibernate ORM with Panache e PostgreSQL JDBC Driver:

    Quarkus: Add extensions to current project
    Hibernate ORM with Panache
    JDBC Driver - PostgreSQL
    
  • Inicializar Banco de Dados:

    docker run -e "POSTGRES_PASSWORD=postgres" -p 5432:5432 -d postgres:9.6.18-alpine
    
    • posterior a essa etapa crie o banco de dados "customers"
  • Modificar classe Customer adicionando referências ao Hibernate Panache:

    @Entity
    public class Customer extends PanacheEntity {
    
      public String primeiroNome;
      public Integer rg;
      public String sobreNome;
    
      public static List<Customer> findByPrimeiroNome(Customer customer){
      	List<Customer> customerList = list("primeiroNome",  customer.getPrimeiroNome());
      	return customerList;
      }
    
      public static List<Customer> findByPrimeiroOrSobreNome(Customer customer){
      	List<Customer> customerList = list("primeiroNome = :firstName or sobreNome = :lastName",
      									Parameters.with("firstName", customer.getPrimeiroNome())
      										.and("lastName", customer.getSobreNome()));
      	return customerList;
      }
    
      public static Customer findByRg(Customer customer){
      	customer = Customer.find("rg", customer.getRg()).firstResult();
      	return customer;
      }
    
    	public String getPrimeiroNome() {
    		return primeiroNome;
    	}
    
    	public void setPrimeiroNome(String primeiroNome) {
    		this.primeiroNome = primeiroNome.toUpperCase();
    	}
    
    	public Integer getRg() {
    		return rg;
    	}
    
    	public void setRg(Integer rg) {
    		this.rg = rg;
    	}
    
    	public String getSobreNome() {
    		return sobreNome;
    	}
    
    	public void setSobreNome(String sobreNome) {
    		this.sobreNome = sobreNome.toUpperCase();
    	}
    
    }
    
    
  • Modificar classe CustomerService adicionando referências aos métodos recém criados:

    @ApplicationScoped
    public class CustomerService {
    
      @Transactional
      public Customer addCustomer(Customer customer){
        Customer.persist(customer);
        return customer;
      }
    
      @Transactional
      public Customer getCustomerById(Customer customer){
        customer = Customer.findById(customer.id);    
      	  return customer;
      }
    
      @Transactional
      public List<Customer> getByPrimeiroNome(Customer customer){
        List<Customer> cList = Customer.findByPrimeiroNome(customer);
        return cList;
      }
    
      @Transactional
      public List<Customer> getByPrimeiroNomeOrSobreNome(Customer customer){
        List<Customer> cList = Customer.findByPrimeiroOrSobreNome(customer);
        return cList;
      }
    
      @Transactional
      public Customer getCustomerByRg(Customer customer){
        customer = Customer.findByRg(customer);    
      	  return customer;
      }
    
      @Transactional
      public List<Customer> listCustomer(){
        List<Customer> cList = Customer.listAll();
        return cList;
      }
    
      @Transactional
      public Customer deleteCustomer(Customer customer){
        customer = Customer.findById(customer.id);
        Customer.deleteById(customer.id);
        return customer;
      }
    
      @Transactional
      public Customer updateCustomer(Customer customer){
        Customer customerEntity = Customer.findById(customer.id);
        if (customerEntity != null){
            customerEntity.setPrimeiroNome(customer.getPrimeiroNome());
            customerEntity.setSobreNome(customer.getSobreNome());
            customerEntity.setRg(customer.getRg());
        }
        return customerEntity;
      }
    
    }
    
    
  • Modificar classe CustomerResource adicionando referências aos métodos recém criados:

    @Path("/customers")
    public class CustomerResource {
    
      @Inject
      CustomerService customerService;
    
      @GET
      @Produces(MediaType.APPLICATION_JSON)
      public List<Customer> listCustomer(){
          return customerService.listCustomer();
      }
    
      @GET
      @Consumes(MediaType.APPLICATION_JSON)
      @Produces(MediaType.APPLICATION_JSON)
      @Path("/{id}")
      public Customer getCustomerById(@PathParam("id") Long id){
          Customer customerEntity = new Customer();
          customerEntity.id = id;
          customerEntity = customerService.getCustomerById(customerEntity);
          return customerEntity;
      }
    
      @GET
      @Consumes(MediaType.APPLICATION_JSON)
      @Produces(MediaType.APPLICATION_JSON)
      @Path("/rg/{rg}")
      public Customer getCustomer(@PathParam("rg") Integer rg){
          Customer customerEntity = new Customer();
          customerEntity.setRg(rg);
          customerEntity = customerService.getCustomerByRg(customerEntity);
          return customerEntity;
      }
    
      @GET
      @Consumes(MediaType.APPLICATION_JSON)
      @Produces(MediaType.APPLICATION_JSON)
      @Path("/primeiroNome/{primeiroNome}")
      public List<Customer> getCustomerByName(@PathParam("primeiroNome") String name){
          Customer customerEntity = new Customer();
          customerEntity.setPrimeiroNome(name);
          List<Customer> customers = customerService.getByPrimeiroNome(customerEntity);
          return customers;
      }
    
      @GET
      @Consumes(MediaType.APPLICATION_JSON)
      @Produces(MediaType.APPLICATION_JSON)
      @Path("/primeiroNome/{primeiroNome}/sobreNome/{sobreNome}")
      public List<Customer> getCustomerByNameOrLastName(@PathParam("primeiroNome") String primeiroNome,
                                                          @PathParam("sobreNome") String sobreNome){
          Customer customerEntity = new Customer();
          customerEntity.setPrimeiroNome(primeiroNome);
          customerEntity.setSobreNome(sobreNome);
          List<Customer> customers = customerService.getByPrimeiroNomeOrSobreNome(customerEntity);
          return customers;
      }
    
      @POST
      @Consumes(MediaType.APPLICATION_JSON)
      @Produces(MediaType.APPLICATION_JSON)
      public Customer addCustomer(Customer customer){
          Customer customerEntity = customerService.addCustomer(customer);
          return customerEntity;
      }
    
      @PUT
      @Consumes(MediaType.APPLICATION_JSON)
      @Produces(MediaType.APPLICATION_JSON)
      public Customer updatCustomer(Customer customer){
          Customer customerEntity = customerService.updateCustomer(customer);
          return customerEntity;
      }
    
      @DELETE
      @Consumes(MediaType.APPLICATION_JSON)
      @Produces(MediaType.APPLICATION_JSON)
      @Path("/rg/{rg}")
      public Customer deleteCustomerByRg(@PathParam("rg") Integer rg){
          Customer customerEntity = new Customer();
          customerEntity.setRg(rg);
          customerEntity = customerService.getCustomerByRg(customerEntity);
          customerEntity = customerService.deleteCustomer(customerEntity);
          return customerEntity;
      }
    
    }
    
    
  • Ajustar application.properties:

    quarkus.datasource.db-kind = postgresql
    
    %dev.quarkus.datasource.username = postgresql
    %dev.quarkus.datasource.password = postgresql
    %dev.quarkus.datasource.jdbc.url = jdbc:postgresql://localhost:5432/customers
    %dev.quarkus.hibernate-orm.database.generation = drop-and-create
    
  • Iniciar aplicação e executar testes:

    http :8080/customers
    http POST :8080/customers rg=11111 primeiroNome=nome1 sobreNome=sobrenome1
    http POST :8080/customers rg=22222 primeiroNome=nome2 sobreNome=sobrenome2
    http :8080/customers/rg/11111
    http :8080/customers/rg/22222
    http PUT :8080/customers id=1 rg=11111 primeiroNome=nome1Editado sobreNome=sobrenome1Editado
    http :8080/customers/rg/11111
    http :8080/customers
    http DELETE :8080/customers/rg/11111
    http :8080/customers
    
    ** Testes com os novos métodos
    http POST :8080/customers rg=33333 primeiroNome=nome3 sobreNome=sobrenome3
    http POST :8080/customers rg=44444 primeiroNome=nome4 sobreNome=sobrenome4
    http :8080/customers/3
    http :8080/customers/4
    http POST :8080/customers rg=55555 primeiroNome=nomeigual sobreNome=sobrenome5
    http POST :8080/customers rg=66666 primeiroNome=nomeigual sobreNome=sobrenome6
    http :8080/customers/primeiroNome/NOMEIGUAL
    
    http POST :8080/customers rg=77777 primeiroNome=nomeigualA sobreNome=sobrenomeA
    http POST :8080/customers rg=88888 primeiroNome=nomeigualA sobreNome=sobrenomeB
    http POST :8080/customers rg=99999 primeiroNome=nome99999 sobreNome=sobrenomeB
    http :8080/customers/primeiroNome/nomeigualA/sobreNome/ahushuhahus
    http :8080/customers/primeiroNome/ahuahuhs/sobreNome/sobrenomeB
    
    

9 - Inclusão BuscaCEP - MicroProfile Rest Client

  • Maiores detalhes em na documentação Rest Client

  • Criação de um projeto básico com o VSCode e seu respectivo plugin para Quarkus:

    Cmd/Ctrl + Shift + P
    Generate a Quarkus Project
    Selecione a ferramenta de build de sua escolha: Maven ou Gradle
    Informe o GAV (Group, Artifact, Version)
    Escolha o nome do recurso (ex. buscacep)
    Inclua a extensão: RESTEasy JSON-B
    Selecione diretório destino do projeto gerado
    
  • Modifique a classe CEPResource:

    @Path("/cep")
    public class CEPResource {
    
      @GET
      @Produces(MediaType.APPLICATION_JSON)
      @Consumes(MediaType.APPLICATION_JSON)
      public Response getNumeroCEP() {
          CEP  cep = new CEP();
          cep.setNumeroCep(new Random().ints(1, 99999).findFirst().getAsInt());
          return Response.ok(cep).build();
      }
    
    }
    
  • Crie a classe CEP:

    public class CEP {
    
      private Integer numeroCep;
    
    	public Integer getNumeroCep() {
    		return numeroCep;
    	}
    
    	public void setNumeroCep(Integer numeroCep) {
    		this.numeroCep = numeroCep;
    	}
    
    }
    
  • Modifique a porta padrão do Quarkus no arquivo application.properties para evitar conflito:

    %dev.quarkus.http.port = 8180
    
    • por padrão o projeto é criado utilizando JDK 11, portanto, caso sua JVM seja de uma versão inferior, por exemplo 1.8, é necessário alteração arquivo pom.xml:

      <maven.compiler.source>1.8</maven.compiler.source>
      <maven.compiler.target>1.8</maven.compiler.target>
      
  • Teste o serviço Restfull recém criado:

    http :8180/cep
    
  • Copie/Cole a classe CEP no projeto Customer;

  • Adicione a extension Quarkus Rest Client no projeto Customer;

  • Crie uma interface BuscaCEPRestClient no projeto Customer com o seguinte conteúdo:

    @ApplicationScoped
    @RegisterRestClient
    public interface BuscaCEPRestClient {
    
      @GET
      @Produces(MediaType.APPLICATION_JSON)
      @Consumes(MediaType.APPLICATION_JSON)
      public CEP getNumeroCEP();
    
    }
    
  • Adicione um campo do tipo Integer na classe de domínio Customer e gere seus respectivos get/set:

    public Integer numeroCep;
    
  • Injete a referência da interface BuscaCEPRestClient na classe CustomerResource:

    @Inject
    @RestClient
    BuscaCEPRestClient buscaCepRestClient;
    
  • Modifique o método addCustomer da classe CustomerResource atribuindo ao atributo numeroCEP um valor gerado através de uma chamada remota ao webservice restfull BuscaCEPRestClient:

    @POST
    @Consumes(MediaType.APPLICATION_JSON)
    @Produces(MediaType.APPLICATION_JSON)
    public Customer addCustomer(Customer customer){
      customerEntity.setNumeroCep(buscaCepRestClient.getNumeroCEP().getNumeroCep());
      Customer customerEntity = customerService.addCustomer(customer);
      return customerEntity;
    }
    
  • Modifique o arquivo application.properties do projeto Customer:

    br.com.redhat.quarkus.BuscaCEPRestClient/mp-rest/url=http://localhost:8180/cep
    br.com.redhat.quarkus.BuscaCEPRestClient/mp-rest/scope=javax.inject.Singleton
    
  • Adicione um novo Customer através de sua API:

    http POST :8080/customers rg=12345 primeiroNome=nome12345 sobreNome=sobrenome12345
    http :8080/customers/rg/12345
    

10 - Inclusão Monitoramento - MicroProfile Metrics

  • Maiores detalhes em na documentação Quarkus MicroProfile Metrics

  • Incluir extension SmallRye MicroProfile Metrics:

    Quarkus: Add extensions to current project
    MicroProfile Metrics
    
  • Configurar e iniciar o serviço do Prometheus:

    prometheus.yml
    global:
    scrape_interval: 10s
    evaluation_interval: 10s
    
    rule_files:
    
    scrape_configs:
      - job_name: 'prometheus'
        static_configs:
          - targets: ['localhost:9090']
    
      - job_name: 'quarkus'
        static_configs:
          - targets: ['localhost:8080']
    
      - job_name: 'quarkus2'
        static_configs:
          - targets: ['localhost:8180']
    
  • Acessar Endpoints de Monitoramento:

    http://localhost:8080/metrics
    http://localhost:9090/graph
    
  • Modificar a classe CustomerResource adicionando os seguintes métodos:

    @Gauge(name = "QUARKUS_QUANTIDADE_CLIENTES", unit = MetricUnits.NONE, description = "QUANTIDADE DE CLIENTES")
    public long checkCustomerAmmout(){
        return customerService.listCustomer().size();
    }
    
  • Adicionar alguns customers:

    http POST :8080/customers rg=11111 primeiroNome=nome1 sobreNome=sobrenome1;
    http POST :8080/customers rg=22222 primeiroNome=nome2 sobreNome=sobrenome2;
    http POST :8080/customers rg=33333 primeiroNome=nome3 sobreNome=sobrenome3;
    http POST :8080/customers rg=44444 primeiroNome=nome4 sobreNome=sobrenome4
    
  • Verificar nos Endpoints de monitoramento a inclusão da métrica QUARKUS_QUANTIDADE_CLIENTES

    application_br_com_redhat_quarkus_CustomerResource_QUARKUS_QUANTIDADE_CLIENTES
    http://localhost:8080/metrics
    http://localhost:9090/graph?g0.range_input=1h&g0.expr=application_br_com_redhat_quarkus_CustomerResource_QUARKUS_QUANTIDADE_CLIENTES&g0.tab=0
    

11 - Implementar Tolerância Falha - MicroProfile Fault Tolerance

  • Criação possível através do Quarkus Fault Tolerance

  • Incluir extension SmallRye Fault Tolerance:

    Quarkus: Add extensions to current project
    SmallRye Fault Tolerance
    
  • Incluir as seguintes propriedades na classe CustomerResource:

    private static final Logger LOGGER = Logger.getLogger(CustomerResource.class);
    
    @ConfigProperty(name = "isTestingFault")
    boolean isTestingFault;
    
    @ConfigProperty(name = "isRetry")
    boolean isRetry;
    final int quantidadeRetry = 5;
    int exceptionCount = quantidadeRetry - 1;
    int count = 0;
    
    @ConfigProperty(name = "isFallBack")
    boolean isFallBack;
    
  • Modificar a classse CustomerResource adicionando os seguintes métodos:

    @GET
    @Produces(MediaType.APPLICATION_JSON)
    //@Retry(maxRetries = quantidadeRetry, delay = 1, delayUnit = ChronoUnit.SECONDS)
    //@Fallback(fallbackMethod = "fallbackCustomers")
    public List<Customer> listCustomer(){
        if (isTestingFault){
            executeFault();
        }
        return customerService.listCustomer();
    }
    
    private void executeFault(){
      if (isRetry){
          while ( count < exceptionCount){
              count++;
              LOGGER.error("Simulando Erro: " + count);
              throw new RuntimeException("Simulando Erro: " + count);
          }
          count = 0;
      }
      if (isFallBack){
          throw new RuntimeException("Simulando Erro: ");  
      }   
    }
    
    private List<Customer> fallbackCustomers(){
        return new ArrayList<Customer>(0);
    }
    
  • Teste de Retry:

    • Inclua as seguintes propriedades no arquivo application.properties:

      # Teste Fault Tolerance
      %dev.isTestingFault = true
      # Retry
      %dev.isRetry = true
      # Timeout
      %dev.isFallBack = false
      
    • Descomente a Annotation @Retry do método listCustomer

    • Inclua alguns Customers:

      http POST :8080/customers rg=11111 primeiroNome=nome1 sobreNome=sobrenome1;
      http POST :8080/customers rg=22222 primeiroNome=nome2 sobreNome=sobrenome2;
      http POST :8080/customers rg=33333 primeiroNome=nome3 sobreNome=sobrenome3;
      http POST :8080/customers rg=44444 primeiroNome=nome4 sobreNome=sobrenome4
      
    • Faça uma chamada ao método listCustomer()

      http :8080/customers  
      
    • Verifique que algumas exceções são lançadas porém é obtido um retorno após execução bem sucedida:

      2020-06-21 17:14:45,970 ERROR [br.com.red.qua.CustomerResource] (executor-thread-198) Simulando Erro: 1
      2020-06-21 17:14:47,108 ERROR [br.com.red.qua.CustomerResource] (executor-thread-198) Simulando Erro: 2
      2020-06-21 17:14:48,105 ERROR [br.com.red.qua.CustomerResource] (executor-thread-198) Simulando Erro: 3
      2020-06-21 17:14:49,288 ERROR [br.com.red.qua.CustomerResource] (executor-thread-198) Simulando Erro: 4
      
      HTTP/1.1 200 OK
      Content-Length: 345
      Content-Type: application/json
      
      [
          {
              "id": 1,
              "numeroCep": 21624,
              "primeiroNome": "NOME1",
              "rg": 11111,
              "sobreNome": "SOBRENOME1"
          },
          {
              "id": 2,
              "numeroCep": 44109,
              "primeiroNome": "NOME2",
              "rg": 22222,
              "sobreNome": "SOBRENOME2"
          },
          {
              "id": 3,
              "numeroCep": 46397,
              "primeiroNome": "NOME3",
              "rg": 33333,
              "sobreNome": "SOBRENOME3"
          },
          {
              "id": 4,
              "numeroCep": 50003,
              "primeiroNome": "NOME4",
              "rg": 44444,
              "sobreNome": "SOBRENOME4"
          }
      ]
      
      • Teste de Fallback:

        • Inclua as seguintes propriedades no arquivo application.properties:

          # Teste Fault Tolerance
          %dev.isTestingFault = true
          # Retry
          %dev.isRetry = false
          # Timeout
          %dev.isFallBack = true
          
        • Descomente a Annotation @Fallback e comente @Retry do método listCustomer

        • Inclua alguns Customers:

          http POST :8080/customers rg=11111 primeiroNome=nome1 sobreNome=sobrenome1;
          http POST :8080/customers rg=22222 primeiroNome=nome2 sobreNome=sobrenome2;
          http POST :8080/customers rg=33333 primeiroNome=nome3 sobreNome=sobrenome3;
          http POST :8080/customers rg=44444 primeiroNome=nome4 sobreNome=sobrenome4
          
        • Faça uma chamada ao método listCustomer()

          http :8080/customers  
          
        • Verifique o retorno da invocação do Endpoint RestFull não apresenta erro porém sem dados:

          HTTP/1.1 200 OK
          Content-Length: 2
          Content-Type: application/json
          
          []
          

12 - Inclusão Monitoramento - MicroProfile Metrics

  • Maiores detalhes em na documentação OpenTracing

  • Inicializar serviço do Jaeger:

    docker run -p 5775:5775/udp -p 6831:6831/udp -p 6832:6832/udp -p 5778:5778 -p 16686:16686 -p 14268:14268 jaegertracing/all-in-one:latest
    
  • Incluir extension SmallRye OpenTracing:

    Quarkus: Add extensions to current project
    SmallRye OpenTracing
    
  • Inclua alguns Customers:

    http POST :8080/customers rg=11111 primeiroNome=nome1 sobreNome=sobrenome1;
    http POST :8080/customers rg=22222 primeiroNome=nome2 sobreNome=sobrenome2;
    http POST :8080/customers rg=33333 primeiroNome=nome3 sobreNome=sobrenome3;
    http POST :8080/customers rg=44444 primeiroNome=nome4 sobreNome=sobrenome4
    
  • Acesse a interface do Jaeger e verifique as rotas:

    http://localhost:16686/
    

13 - Segurança - Keycloak/OAuth/OIDC/JWT

  • Maiores detalhes em na documentação Quarkus OpenID Connect

  • Inicie o serviço do Keycloak

    docker run -p 10080:8080 viniciusmartinez/quarkus-rhsso:1.0
    
  • Incluir extension OpenID Connect:

    Quarkus: Add extensions to current project
    OpenID Connect
    
  • Adicione no arquivo application.properties os seguintes parâmetros:

    %dev.quarkus.oidc.auth-server-url = http://localhost:10080/auth/realms/Quarkus
    %dev.quarkus.oidc.client-id = customer-app
    %dev.quarkus.oidc.credentials.secret =5ffb3490-4d7b-42ed-8cac-e6774550bc92
    %dev.quarkus.http.auth.policy.role-policy1.roles-allowed = user,admin                      
    %dev.quarkus.http.auth.permission.roles1.paths = /*
    %dev.quarkus.http.auth.permission.roles1.policy = role-policy1
    
  • Tente executar qualquer endpoint e um HTTP 403 é esperado:

    http :8080/customers
    
    HTTP/1.1 401 Unauthorized
    content-length: 0
    
  • Obtenha um Token com as credenciais do usuário user1:

    export access_token=$(\
      curl -X POST http://localhost:10080/auth/realms/Quarkus/protocol/openid-connect/token \
      --user customer-app:5ffb3490-4d7b-42ed-8cac-e6774550bc92 \
      -H 'content-type: application/x-www-form-urlencoded' \
      -d 'username=user1&password=user1&grant_type=password' | jq --raw-output '.access_token' \
    )
    
    echo $access_token
    
  • Modifique o método de DELETE adicionando a necessidade de uma role admin:

    @DELETE
    @Consumes(MediaType.APPLICATION_JSON)
    @Produces(MediaType.APPLICATION_JSON)
    @Path("/rg/{rg}")
    @RolesAllowed("admin")
    public Customer deleteCustomerByRg(@PathParam("rg") Integer rg){
      Customer customerEntity = new Customer();
      customerEntity.setRg(rg);
      customerEntity = customerService.getCustomerByRg(customerEntity);
      customerEntity = customerService.deleteCustomer(customerEntity);
      return customerEntity;
    }
    
  • Realize uma chamada informando o Bearer Token

    http :8080/customers "Authorization: Bearer "$access_token
    
  • Adicione alguns Customers e verifique seu estado:

    http POST :8080/customers "Authorization: Bearer "$access_token rg=11111 primeiroNome=nome1 sobreNome=sobrenome1;
    http POST :8080/customers "Authorization: Bearer "$access_token rg=22222 primeiroNome=nome2 sobreNome=sobrenome2;
    http POST :8080/customers "Authorization: Bearer "$access_token rg=33333 primeiroNome=nome3 sobreNome=sobrenome3;
    http POST :8080/customers "Authorization: Bearer "$access_token rg=44444 primeiroNome=nome4 sobreNome=sobrenome4
    
    http :8080/customers "Authorization: Bearer "$access_token
    
  • Tente remover qualquer customer recém criado. Um erro 403 é esperado:

    http DELETE :8080/customers/rg/33333 "Authorization: Bearer "$access_token
    
    HTTP/1.1 403 Forbidden
    Content-Length: 9
    Content-Type: application/json
    
    Forbidden
    
  • Obtenha um Token com as credenciais do usuário admin:

    export access_token=$(\
      curl -X POST http://localhost:10080/auth/realms/Quarkus/protocol/openid-connect/token \
      --user customer-app:5ffb3490-4d7b-42ed-8cac-e6774550bc92 \
      -H 'content-type: application/x-www-form-urlencoded' \
      -d 'username=admin&password=admin&grant_type=password' | jq --raw-output '.access_token' \
    )
    
    echo $access_token
    
  • Tente remover qualquer customer recém criado:

    http DELETE :8080/customers/rg/33333 "Authorization: Bearer "$access_token
    

Referências Adicionais

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published