Quarkus.io — Um novo framework Java

Stefan Horochovec
horochovec
Published in
9 min readMay 22, 2019

Olá pessoal,

2019 promete ser um ano promissor para o desenvolvedor Java. A algum tempo vivemos grandes mudanças na plataforma Java, e se pararmos para analisar, todas as mudanças que vivemos hoje se iniciaram a alguns anos.

Com a adoção em massa de nuvens públicas e privadas (on-primesse), a visão do recurso computacional foi revista. Felizmente não é mais aceitável pensarmos no modelo de grandes servidores abarrotados de memória e processadores em detrimento de servidores de aplicações extremamente pesados e executando uma ou mais aplicações monolíticas em ambiente único ou em cluster.

Como todo bônus possui o famoso ônus, coube ao desenvolvimento aplicar novas técnicas que se adequassem a esse modelo de entrega de software, e em conjunto a estratégia de utilizar mais servidores com menor recurso computacional do que um servidor monstruoso, “surge” a abordagem de uma arquitetura de software orientada a micro-serviços (microservices).

Micro-serviços a grosso modo, significa adotar uma estratégia para fatiar uma aplicação monolítica em diversos pedaços, que são executados de forma independente (infra-estrutura, base de dados, etc) e que se comunicam entre si utilizando requisições geralmente desenvolvidas em REST.

A proposta de arquitetura orientada a micro-serviços é tão flexível que permite que cada serviço possa ser desenvolvido utilizando a tecnologia que trará o melhor resultado possível ao problema que pretende ser resolvido.

Até esse momento, teoricamente está tudo certo, dividimos nossos mega servidores em instâncias menores para facilitar a gestão, otimizando o consumo de memória e processadores sem a existência de recursos ociosos, e fatiamos nossa aplicação utilizando uma ou mais tecnologias, tirando o melhor proveito das linguagens de programação.

Porém, nem tudo é tão simples quanto parece. Precisávamos de agilidade na entrega dos nossos aplicativos e também de uma certa padronização naquilo que entregávamos para ser executado na nuvem. Surge o conceito de containers com o Docker.

Basicamente, o Docker possibilita a entrega de software através de um container, que nada mais é do que uma nova instância do Linux, pré-configurada com sua aplicação pronta para ser executada, contendo todas as suas configurações e serviços a serem utilizados, sendo executada dentro de outro Linux. Isso é possível porque o Docker utiliza recursos do Linux como cgroups e namespaces para segregrar processos e assim, executá-los de forma independente.

De uma forma bastante simplificada, podemos imaginar que o desenvolvedor entrega em um repositório, público ou privado, um container para ser executado em um servidor Linux, com um servidor de aplicação configurado (ou pré-configurado), contendo a aplicação instalada e pronta para ser executada, e o time de infra-estrutura “apenas” executa este container em uma instância na nuvem, sem a necessidade de configurar um ou mais serviços, e pronto, um novo micro-serviço está pronto para ser consumido por algum client.

Porém, nem tudo é um mar de rosas no desenvolvimento de Software. A alguns anos o desenvolvedor se preocupava apenas em desenvolver uma aplicação em um único deploy e pronto, e agora ele precisa lidar com múltiplos servidores, multíplas tecnologias, a comunicação entre os serviços, etc.

Considerando que qualquer um destes servidores Wildfly, Glassfish, WebSphere ou WebLogic consomem um recurso computacional gigantesco apenas para ser inicializado, possuem um processo de inicialização muito demorado (falaremos sobre isso mais adiante), e ainda possuem inumeras configurações em diversos arquivos xmls que variam de acordo com inúmeros recursos que você venha a consumir na sua aplicação, realmente observávamos um cenário que não era muito favorável a adoção de micro-servicos, porém, como somos desenvolvedores, não podemos desistir nunca, e abraçamos essa proposta.

Com essa adoção, surge então a proposta da RedHat com o antigo Wildfly-Swarm, hoje carinhosamente chamado de Thorntail, que podemos resumidamente dizer que é do que a engine do Wildfly distribuída através de módulos, ou seja, você habilita em seu servidor de aplicação apenas os módulos EE que você consome, sem a necessidade de habilitar todos os módulos que um servidor Java EE precisa dispor para ser homologado que por padrão já vinham disponíveis no servidor de aplicação.

Outro ponto bastante importante referente ao Thorntail é a capacidade de entrega da aplicação através de um fatjar, que pode ser executado através de uma linha de comando, e nela o framework é responsável por instanciar a aplicação iniciando um servidor de aplicação pré-configurado por um arquivo dentro da própria aplicação.

Basicamente, o modelo de entrega de aplicações Java utilizando o conceito de fatjar, seja ele utilizando o Thorntail para Java EE ou o Sprint Boot para o framework Spring, permitiu um certo alívio aos desenvolvedores. Um container contendo uma JVM, pode simplesmente executar o fatjar de sua aplicação através da linha de comando e o deploy estará disponível.

Dessa forma, podemos ilustrar uma aplicação orientada a microserviços da seguinte maneira:

Mas lembra que um pouco acima falei sobre o tempo de inicialização de uma aplicação? Pois é, um desastre. Pois facilmente este tempo de inicialização ultrapassam 30 segundos, chegando a 60 segundos em alguns casos. E isso não é nada bom em um ambiente que necessita de alta-disponibilidade.

Vamos observar algumas das principais desvantagens de uma arquitetura orientada a microservicos:

Serviços distribuídos;
O aspecto técnico de desenvolver uma aplicação com serviços distribuídos é consideravelmente mais complexo do que uma aplicação monolítica;

Gerenciamento de infraestrutura;
Gerenciar um maior pool de servidores menores também possui suas desvantagens, principalmente pela necessidade constante de comunicação entre as instâncias, dependendo do número de servidores, isso pode se tornar um grande problema;

Containers também são “pesados”;
Containers não deixam as aplicações mais leves, apenas automatizam a forma de entrega. O que iria ser instalado no servidor, vai embarcado dentro do container.

Imprescindível a mudança cultural para um modelo de DevOps;
Trazer para o time de desenvolvimento aspectos de configuração de servidores, etc, é imprescindível mudar a cultura do time, e mudança cultural em muitos casos é BEM complicado;

Ok, e agora? Como resolvemos essas dores de cabeça?

Eis que surge a proposta Serverless, utilizando o conceito de FaaS (Function as a Service).

Serverless possui o conceito de que devemos terceirizar o provisionamento da sua infra-estrutura e servidores para a nuvem, e focar em nada mais nada menos do que códigos. Alguns dos principais benefícios da abordagem da Arquitetura Serverless:

  • Não há necessidade de provisionamento, manutenção e gerenciamento de servidores;
  • O custo é gerado pela execução das funções;
  • Escalabilidade automática pela plataforma;
  • Toda a disponibilidade do serviço é garantida pelo PaaS;
  • Em alguns PaaS você tem a possibilidade de uma redundância AZ;
  • Aumento da produtividade do desenvolvedor;
  • Redução de tempo considerável para publicação e iniciação das soluções na nuvem;

Com a proposta de adoção Serverless sugere então a idéia de nanoservices, que sugere fracionarmos ainda mais a visão de um serviço, conforme as ilustrações abaixo:

Ok, estamos saindo de um cenário monolítico aonde uma única aplicação poderia ter milhares de classes javas todas em um único projeto para o cenário que cada método HTTP a ser chamado pode ser um deploy isolado? Pois é, exatamente isso.

Para exemplificar isso com números, segue um comparativo de execução de uma função (que calcula 1000 vezes todos os números primos menores ou iguais a 1000), e analiremos sua disponibilidade de consumo de memória, tempo de execução e o custo final.

O que fica evidente nessa tabela apresentada, é que disponibilizar uma função no modelo Serverless é muito mais eficiente do ponto de vista de custo x tempo do que em um container tradicional sendo executado em uma instância na nuvem.

Em uma arquitetura Serverless utilizando FaaS, é fundamental observar que as instâncias são criadas e destruídas com muita freqüencia, e nesse ponto é fundamental que o “start” de uma nova instância seja muito eficiente. Até então, era impossível para um desenvolvedor Java entregar um deploy de algo que pudesse ser iniciado em menos de 1 segundo. Até agora…

Recentemente, RedHat lançou a versão pública do Quarkus. O objetivo é claro, permitir ao Java protagonismo na entrega de soluções utilizando Kubernets e Serverless.

Fazendo uso da GraalVM e OpenJDK HotSpot, o Quarkus foi concebido para trabalhar com os padrões mais utilizados no Java e também as novas especificações do mundo cloud, disponibilizadas hoje pelo MicroProfile, com isso, naturalmente, você tem a disposição (considerando especificações):

  • Beans Validations;
  • CDI;
  • Logs;
  • Microprofile Config;
  • Microprofile Fault Tolerance;
  • Microprofile Healt;
  • Microprofile JWT;
  • Microprofile Metrics;
  • Microprofile OpenAPI;
  • Microprofile OpenTracing;
  • Microprofile TypeSafe Rest Client;
  • JAX-RS;
  • JPA/JDBC;
  • Servlets;
  • Transactions;

E ainda acesso aos principais frameworks e ferramentas do mercado como:

  • Apache Camel;
  • Apache Kafka;
  • Hibernate;
  • Infinispan;
  • Jaeger;
  • Kubernetes;
  • Netty;
  • Prometheus;
  • RESTEasy;
  • Vert.x;

Caso queira se aventurar no Quarkus, você irá encontrar um framework que não difere muito do que você já vinha consumindo para desenvolver aplicações (principalmente com stacks orientadas a micro-serviços).

Você terá a disposição um starter via maven muito simples para criar um projeto inicial (veremos a seguir), você pode utilizar maven ou gradle para gestão de dependencias e build (neste post usaremos maven), possui como trabalhar com live-reload para facilitar o modo de desenvolvimento e pode criar seus códigos em Java ou Kotlin (iremos utilizar Java).

Criando um novo projeto:

Após a criação do projeto, efetuarmos a build padrão do projeto:

Podemos verificar que no diretório target do nosso projeto foram criados dois arquivos .jar. Para iniciarmos nossa aplicação via linha de comando, e para um deploy em um container sem a utilização da GraalVM, não podemos nos confundir com fatjars.

Todas as dependências estão dentro do diretório /lib, ou seja, é necessário copiá-las para seu container para poder executar o projeto, conforme abaixo:

E por fim, podemos então iniciar nosso projeto utilizando a versão padrão em Java, e teremos o seguinte resultado:

Porém, nesse estágio ainda não estamos utilizando a entrega nativa, através da GraalVM, para isso, precisamos efetuar o empacotamento do projeto utilizando o parametro adicional -Pnative.

E agora, poderemos executar nosso projeto usando a versão nativa compilada e distribuída para a GraalVM, conforme abaixo:

E consultando nosso navegador web, teremos o seguinte resultado:

É muito importante frizar o tempo que levou para eu iniciar minha aplicação e ela estar disponível para o usuário final. Apenas 0.006 segundos. Até então, essa métrica era inimaginável no mundo Java.

Ao considerar uma arquitetura Serverless, o tempo de inicialização de uma função é primordial para que a disponibilidade do projeto seja considerada eficiente do ponto de vista de tempo de execução.

Obviamente que devemos lembrar que o Java ficou um pouco para trás na largada para essa corrida na arquitetura Serveless, porém, sem dúvida o Java entra com o pé direito nessa nova proposta de entrega de software.

Seja bem vindo Quarkus!

--

--