Quarkus: A nova revolução do universo Java.
A área da tecnologia sempre foi bastante marcada pela busca por eficiência, agilidade e redução de custos. Com isso, sempre surgem novas tecnologias, conceitos e frameworks que acabam alterando as formas como as aplicações são feitas.
E uma dessas tecnologias que vem se destacando bastante, e aos poucos ganhando mercado é o Quarkus, um novo concorrente do já consolidado Spring Boot. O Quarkus é um framework do Java nativo para microsserviços e nuvem.
O Quarkus se posiciona como uma tecnlogia bastante poderosa e inovadora para os grandes desafios que é o desenvolvimento de aplicações modernas com Java. Ele combina um desempenho muito bom, baixo consumo de memória e o tempo de inicialização super rápido. O Quarkus promete revolucionar e elevar o universo do Java.
Neste artigo, iremos explorar mais sobre o Quarkus, o que ele tem a oferecer, recursos e benefícios. Também será discutido como que esta tecnologia aborda os principais desafios do desenvolvimento Java, qual problema ele resolve e como. Mas, antes precisamos primeiro entender sobre uma nova abordagem de nuvem chamada contêiner e também sobre Microsserviços.
Contêiner: A "mini" máquina virtual.
Em computação em nuvem, contêineres é toda uma unidade de software que empacota toda sua aplicação com tudo que a mesma necessita para executar. Você pode pensar em um contêiner como uma caixa que contém todos os componentes necessários para executar um aplicativo, como bibliotecas, frameworks e configurações. Essa caixa é isolada do ambiente em que é executada, o que significa que o software dentro do contêiner não interfere com outros softwares no sistema.
Você pode pensar um contêiner como se fosse uma caixa, que contém todas as coisas que é necessário para executar uma aplicação, como bibliotecas, configurações e outras dependências. Essa caixa é isolada do ambiente (sistema operacional) na qual é executado, o que significa que a aplicação dentro do contêiner não interfere com outras no sistema.
O contêiner é um pouco semelhante com uma máquina virtual. Ele também recebe recursos para ser executado (memória RAM, processamento, entre outros). Porém, falando de forma bem simples, a principal diferença entre um contêiner e uma Máquina Virtual é que o contêiner ele serve apenas para rodar a aplicação dentro dele, portanto fazendo dele normalmente bem leve.
Algumas das principais vantagens em utilizar contêiner são:
- Portabilidade: A aplicação dentro do contêiner irá executar em qualquer ambiente indepente do sistema operacional na qual está, já que ele isola a aplicação do restante do ambiente.
- Isolamento de falha: Cada aplicação dentro de um contêiner executa independemente da outra. Portanto, se um falhar, não irá afetar o outro.
- Eficiência de recursos: Por ter um proposito específico, o contêiner é muito mais leve que uma Máquina Virtual.
- Economia: Como as aplicações dentro do contêiner executa em qualquer Sistema Operacional, não há mais a necessidade da instalação e configuração do mesmo.
Microsserviços: Dividir pra conquistar.
Microsserviços, basicamente é uma abordagem na qual você divide uma aplicação em várias outras aplicações menores, que compõe ela. A idéia é que essas aplicações menores se comuniquem e que sejam independentes entre si. Ou seja, se algum microsserviço de um determinado projeto vir a falhar ou cair, isso não afetará os demais. Além disso, os microsserviços independem de tecnologia. É possível que em um mesmo projeto se tenha um microsserviço em Java, outro em Node, e por ai vai.
É importante notar que seu funcionamento é totalmente contrário da arquitetura Monolítica, que é básicamente um sistema único, uma aplicação onde seus componentes estão todos dentro de uma única aplicação.
As vantagens de se usar este tipo de arquitetura é:
- Por serem independentes de tecnologias, você pode ter um microsserviço em Node (Javascript) e outro com Java, por exemplo.
- Quando um microsserviço cair ou falhar, isso não irá atrapalhar nos demais.
- São bem mais escaláveis
Entre outras vantagens...
Outro conceito importante dentro desta abordagem de microsserviços é o Microsserviço de Gateway, que em termos simples, ele recebe as requisições da aplicação na qual está consumindo o mesmo e encaminha para o microsserviços correspondente.
Um exemplo de Microsserviço de Gateway é o seguinte: suponha que tenhamos uma API composta por microsserviços, onde um deles é responsável pelos produtos, outro pelas compras e um terceiro pelos distribuidores. Agora, imagine que o front-end deseje cadastrar um novo produto, mas sem precisar conhecer diretamente o microsserviço responsável por essa funcionalidade. Nesse caso, entra em ação o Microsserviço de Gateway, que recebe a requisição do front-end e a encaminha para o microsserviço responsável pelo cadastro de produtos. O Gateway atua como uma ponte entre o front-end e os microsserviços específicos, facilitando a comunicação e permitindo que o front-end interaja com a API de forma simplificada e sem necessitar conhecer os demais microsserviços.
Além disso, os microsserviços normalmente são projetados para terem uma inicialização rápida por diversos motivos:
- Resiliência: A falha de um microsserviço individual não deve afetar toda a aplicação. Portanto, quando o mesmo falha, o comum é implantar uma nova instância (servidor na nuvem) ou contêiner para substituí-lo rapidamente. Logo, tendo um tempo rápido de inicialização irá ajudar a minimizar o tempo de inatividade deste microsserviço em específico.
- Elasticidade: neste tipo de arquitetura, é comum que os microsserviços sejam escalados horizontalmente, ou seja, várias instâncias dos mesmos serviços são implantadas para lidar com a demanda. Quando há aumento da demanda, é necessário iniciar rapidamente novas instâncias com o microsserviço para lidar com a carga adicional. Se o tempo de inicialização do microsserviço for lento, a capacidade de resposta da aplicação pode ser comprometida.
- Atualizações e implantações contínuas: Quando há alterações dos códigos ou novas features em um determinado microsserviço, a idéia é derruba-lo e iniciar o mesmo para que assim as alterações sejam carregadas. Caso a inicialização do mesmo for lenta, isso irá prejudicar a disponibilidade do sistema.
Acrescentando a isso, os microsserviços precisam ser leves e consumir pouca memória para executarem em ambientes de contêineres, uma vez que um contêiner costuma ter poucos recursos alocados para si.
Breve história do Java: Do passado ao presente
Antes de enterdemos o Quarkus, qual problema que ele resolve e o porquê de ser revolucionário dentro do universo Java, é preciso primeiramente dá uma breve passada na história do Java, para que possamos compreender o contexto da época e qual problema que o Java resolvia.
O Java foi criado na década de 1990 por uma equipe de desenvolvedores liderada por James Gosling na empresa Sun Microsystems, que mais tarde foi adquirida pela Oracle Corporation. A linguagem Java foi projetada com o objetivo de resolver os desafios enfrentados no desenvolvimento de software na época. Além disso, a internet estava começando a se popularizar, e era necessário ter uma maneira eficiente de desenvolver aplicativos para a web.
Naquela tempo, a indústria de software estava enfrentando problemas de incompatibilidade entre aplicações e arquiteturas de hardware. Básicamente, o problema era que para cada ambiente ou sistema operacional em que um determinado software fosse executado, era necessário um código diferente em cada.
O Java foi projetado para ser uma linguagem de programação versátil, portátil e segura, com o famoso lema "write once, run anywhere", que traduzido para o português significa "escreva uma vez, execute em qualquer lugar". Com isso, qualquer sistema operacional que tivesse a Java Virtual Machine (a famosa JVM) poderia executar um código em Java, sem necessidade de nenhuma adaptação para o mesmo.
Com a combinação de portabilidade, segurança e outros recursos poderosos, o Java se tornou amplamente adotado em várias áreas, incluindo desenvolvimento de aplicativos empresariais, desenvolvimento web, dispositivos móveis e até mesmo em aplicações embarcadas na época.
MAAAAAAAAS, nem tudo eram flores, tudo na vida tem seu preço! Devido ao funcionamento da JVM, onde muitas coisas eram carregadas ao executar a aplicação, deixando a mesma mais pesada e consumindo muito mais recursos. Entretanto, até uma determinada época isso não era problema, pois as correções de bugs e novas funcionalidades eram feitos da seguinte forma: o servidor que suportava a aplicação era desligado em horário fora de pico (quase sempre de madrugada), e ligado novamente para carregar a nova versão com as melhorias. O tempo de carregamento variava entre 5 a 30 minutos, devido a demora de inicialização das aplicações naquele tempo.
Java e o Universo Cloud: Uma relação conturbada
O tempo foi passando, e com isso veio o surgimento de contêineres e a abordagem de Microsserviços já explicado anteriormente. Com essa nova onda, o Java acabou ficando para trás, principalmente em aplicações modernas.
No início dessa tendência, o Java enfrentava sérios problemas ao executar aplicativos em ambientes de contêineres. A JVM não era capaz de reconhecer os recursos específicos do contêiner em que estava sendo executada, apenas os recursos do servidor. Isso significava que, mesmo se a aplicação estivesse alocada em um contêiner de apenas 100 MB, a JVM só enxergaria os 100 GB do servidor, o que resultaria no erro "java.lang.OutOfMemoryError". Essa exceção é lançada quando a JVM tenta alocar mais recursos do que estão disponíveis. Como resultado, a execução de aplicativos Java em contêineres era quase inviável.
No entanto, esse problema foi solucionado na versão 11 do Java, lançada no segundo semestre de 2018. Com essa atualização, finalmente foi possível executar aplicativos Java em contêineres. Apesar disso, os aplicativos ainda eram considerados pesados e tinham um tempo de inicialização significativamente lento. Isso dificultava a prática de atualizações e implantações contínuas, conforme explicado anteriormente (derrubar o microsserviço no contêiner para reiniciá-lo com as atualizações). Além disso, os aplicativos Java consumiam muitos recursos, tornando sua execução lenta em um ambiente de contêiner, onde os recursos são limitados, já que a proposta dos contêineres é serem leves.
GraalVM: A evolução da JVM
Antes de falarmos do Quarkus, e como ele resolve este problema, precisamos falar um pouco sobre a GraalVM, que também é parte da solução deste problema.
A GraalVM, como mencionada no título deste tópico, é uma evolução da JVM. Ela tem um característica chamada "compilação nativa", que permite transformar o código-fonte em um executável independente (ao invés de um JAR), eliminando a necessidade de ter a máquina virtual instalada no ambiente de execução. Isso torna a mesma adequada para ambientes de contêineres, microsserviços e execução de aplicações em nuvem. Ou seja, a GraalVM é uma ferramenta poderosa que melhora o desempenho e a eficiência da execução da aplicação.
E uma curiosidade, é que além das linguagens do "universo Java", como o próprio Java, Kotlin, Scala, entre outras, a GraalVM também executa outras linguagens, como Python, Javascript, etc.
Quarkus: O Java supersônico e subatômico
Para resolver a ineficiência do Java, foi criado o framework Quarkus, este que tem como lema "Java supersônico e subatômico". Finalmente agora temos Java rodando em contêineres com eficiência!
Quanto ao seu lema, é chamado "supersônico" por sua inicialização super-rápida. De acordo com a imagem acima, tirado do site oficial do Quarkus:
- Aplicação de API Rest, que contenha um CRUD, desenvolvida em outras tecnologias e rodando na tradicional JVM podem demorar até 9,5 segundos para ser inicializada.
- Enquanto isso, uma API Rest feita em Quarkus e sendo executada também na JVM, aproximadamente 2 segundos.
- Essa mesma aplicação Quarkus, rodando via GraalVM, pode demorar apenas incríveis 0,042 segundos!
E é chamado de "subatômico" devido aos poucos recursos que uma aplicação Quarkus consome. Tirando por esta outra imagem que está acima (que também foi retirada do site oficial do Quarkus):
- Aplicação de API Rest, que contenha um CRUD, desenvolvida em outras tecnologias e rodando na tradicional JVM pode consumir até 209mb de memória RAM.
- API Rest feita em Quarkus e sendo executada também na JVM, consumindo 145mb.
- A mesma aplicação também em Quarkus, mas rodando pela GraalVM, consumindo apenas 28mb de memória RAM!
E como o Quarkus realiza essa "mágica"? De forma simplificada, vários processamentos que seria feito em tempo de execução é colocado pra ser feito em tempo de compilação. Isso significa que é feito uma troca, onde o tempo de execução que é mais importante é priorizado. Com isso, temos menos coisas carregas no momento de executar a aplicação, e por consequência, a inicialização é muito mais rápida (fazendo ela ser "supersônica"), e bem mais leve, consumindo poucos recursos (fazendo ela ser "subatômica").
Diante de tudo isso, pode se concluir que o Quarkus realmente é uma tecnologia revolucionária dentro do universo do Java.
Com a chegada da nuvem, contêineres e microsserviços, o Java acabou ficando pra trás diante dessas novidades. Entretanto, o Quarkus veio para mudar isso e promete bastante para um futuro próximo!