Dando continuidade à nossa iniciativa "LightJava", vamos analisar uma alternativa interessante para continuar a usar a plataforma Java, sem o sobrepeso (gordura, nervo, sêbo) do Container Java EE, nem do "encosto" SOAP / XML.
Por que continuar com Java?
A maioria das empresas tem um grande investimento em Java. Várias aplicações e frameworks corporativos foram criados e não devem ser desperdiçados. Além disso, há um grande investimento em capacitação nessa plataforma. O problema não é (e nunca foi) o Java! O problema é a "selva" de frameworks criada em cima dele! Para começar, temos o Java EE e seus "comparsas", como: Javaserver Faces, Java Persistence API, Servlets, Enterprise Javabeans, JNDI etc. E temos os "agregados", como: Hibernate, Spring, XML e SOAP.
O objetivo da iniciativa "LightJava" é criar um Stack mais simplificado, que permita usar Java sem "sobrepeso", fácil de programar, usando padrões abertos e mais eficiente no consumo de recursos computacionais.
Pensando nisso, vamos apresentar a primeira alternativa: Netty com RestExpress!
Netty
Netty é um framework para criação de clientes e servidores de rede, baseado em NIO (I/O não bloqueante). Ele facilita muito a programação de aplicações de rede, e serve para criarmos servidores mais eficientes para as tarefas que desejamos executar.
Ele não é um Container Java EE, aliás, uma aplicação Netty é uma aplicação Java SE, que pode ser executada diretamente no "Terminal" (ou "Prompt de comandos"), sem Container nem qualquer outro acessório.
Sua arquitetura baseada em NIO, o suporte embudido a vários protocolos e o modelo de Eventos, o tornam um sério concorrente ao Node.js. Eis a sua arquitetura:
Porém, o Netty não é um produto pronto, e nem é tão simples de usar. Se quisermos criar RESTful services, teremos algum trabalho "boilerplate", o que diminui muito a produtividade. Aí é que entra o RestExpress!
RestExpress
RestExpress é um framework para criação de RESTful services, que funciona sobre o Netty, adicionando tudo o que falta para criarmos RESTful services de modo mais prático. Ele permite gerarmos respostas JSON ou XML, e cria um mapeamento de rotas de maneira semelhante ao Express.js.
O RestExpress tem vários arquétipos Maven prontos, o que nos poupa muito tempo para criarmos uma aplicação.
Brincando com o RestExpress
Antes de mais nada, quero deixar claro que o objetivo desse artigo NÃO É ENSINAR A USAR O RESTEXPRESS! Eu vou demonstrar seu uso como alternativa ao Stack Java EE.
Eu usei o mesmo banco MongoDB do meu curso de Stack MEAN, e criei um RESTful service simples, que lida com a entidade "Artista". Criei duas rotas:
- GET "/artista": Retorna todos os artistas
- GET "/artista/id": Retorna um artistas
Mas o RestExpress já providenciou o resto para nós: POST, PUT, DELETE etc. Veja o resultado de um GET de um determinado artista:
O projeto completo está no Dropbox, já em formato de projeto Maven / Eclipse.
Primeiramente, eu criei um projeto Maven usando o
Eu quebrei em várias linhas só para ficar mais fácil de ler. Digite tudo em uma linha só no seu terminal.
Você pode importar para o Eclipse, com o menu: FILE / IMPORT, selecionando "Maven" e "Existing maven projects". Selecione a pasta e pronto! Seu projeto está no Eclipse e pronto para rodar!
Se você baixou o meu projeto, não precisa usar o arquétipo para criar um projeto novamente!
Estrutura do projeto
Importe o meu projeto para o Eclipse e examine sua estrutura:
- com.obomprogramador.lightjava: Pasta com as classes principais da aplicação.
- com.obomprogramador.lightjava.config: Classe de configuração do serviço.
- com.obomprogramador.lightjava.controller: Controlador do serviço. Ele é um façade que executa as funções.
- com.obomprogramador.lightjava.domain: Entidades utilizadas.
- com.obomprogramador.lightjava.persistence: Repositórios que encapsulam o MongoDB.
- com.obomprogramador.lightjava.postprocessor: Processador pós-geração da resposta, para agregar informações adicionais.
- com.obomprogramador.lightjava.serialization: Classes que auxiliam na serialização e desserialização do Request.
- com.obomprogramador.lightjava.service: Classe de implementação do serviço.
Para subir o serviço pasta subir o MongoDB e executar o comando Maven:
mvn clean package exec:java
Não tem Container, não tem Hibernate, não tem Persistence.xml, não tem Faces config, não tem NADA! É uma aplicação Java comum!
Eis a classe "Main.java", que inicia tudo:
package com.obomprogramador.lightjava;
import static org.jboss.netty.handler.codec.http.HttpHeaders.Names.ACCEPT;
import static org.jboss.netty.handler.codec.http.HttpHeaders.Names.AUTHORIZATION;
import static org.jboss.netty.handler.codec.http.HttpHeaders.Names.CONTENT_TYPE;
import static org.jboss.netty.handler.codec.http.HttpHeaders.Names.LOCATION;
import static org.jboss.netty.handler.codec.http.HttpHeaders.Names.REFERER;
import static org.restexpress.Flags.Auth.PUBLIC_ROUTE;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.util.concurrent.TimeUnit;
import org.restexpress.Flags;
import org.restexpress.RestExpress;
import org.restexpress.exception.BadRequestException;
import org.restexpress.exception.ConflictException;
import org.restexpress.exception.NotFoundException;
import org.restexpress.pipeline.SimpleConsoleLogMessageObserver;
import org.restexpress.plugin.hyperexpress.HyperExpressPlugin;
import org.restexpress.plugin.hyperexpress.Linkable;
import com.obomprogramador.lightjava.config.Configuration;
import com.obomprogramador.lightjava.serialization.SerializationProvider;
import org.restexpress.util.Environment;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.codahale.metrics.MetricFilter;
import com.codahale.metrics.MetricRegistry;
import com.codahale.metrics.graphite.Graphite;
import com.codahale.metrics.graphite.GraphiteReporter;
import com.strategicgains.repoexpress.exception.DuplicateItemException;
import com.strategicgains.repoexpress.exception.InvalidObjectIdException;
import com.strategicgains.repoexpress.exception.ItemNotFoundException;
import com.strategicgains.restexpress.plugin.cache.CacheControlPlugin;
import com.strategicgains.restexpress.plugin.cors.CorsHeaderPlugin;
import com.strategicgains.restexpress.plugin.metrics.MetricsConfig;
import com.strategicgains.restexpress.plugin.metrics.MetricsPlugin;
import com.strategicgains.restexpress.plugin.swagger.SwaggerPlugin;
import com.strategicgains.syntaxe.ValidationException;
public class Main
{
private static final String SERVICE_NAME = "Banco de artistas";
private static final Logger LOG = LoggerFactory.getLogger(SERVICE_NAME);
public static void main(String[] args) throws Exception
{
RestExpress server = initializeServer(args);
server.awaitShutdown();
}
public static RestExpress initializeServer(String[] args) throws IOException
{
RestExpress.setSerializationProvider(new SerializationProvider());
Configuration config = loadEnvironment(args);
RestExpress server = new RestExpress()
.setName(SERVICE_NAME)
.setBaseUrl(config.getBaseUrl())
.setExecutorThreadCount(config.getExecutorThreadPoolSize())
.addMessageObserver(new SimpleConsoleLogMessageObserver());
Routes.define(config, server);
Relationships.define(server);
configurePlugins(config, server);
mapExceptions(server);
server.bind(config.getPort());
return server;
}
private static void configurePlugins(Configuration config,
RestExpress server)
{
new SwaggerPlugin()
.flag(Flags.Auth.PUBLIC_ROUTE)
.register(server);
new CacheControlPlugin() // Support caching headers.
.register(server);
new HyperExpressPlugin(Linkable.class)
.register(server);
new CorsHeaderPlugin("*")
.flag(PUBLIC_ROUTE)
.allowHeaders(CONTENT_TYPE, ACCEPT, AUTHORIZATION, REFERER, LOCATION)
.exposeHeaders(LOCATION)
.register(server);
}
private static void mapExceptions(RestExpress server)
{
server
.mapException(ItemNotFoundException.class, NotFoundException.class)
.mapException(DuplicateItemException.class, ConflictException.class)
.mapException(ValidationException.class, BadRequestException.class)
.mapException(InvalidObjectIdException.class, BadRequestException.class);
}
private static Configuration loadEnvironment(String[] args)
throws FileNotFoundException, IOException
{
if (args.length > 0)
{
return Environment.from(args[0], Configuration.class);
}
return Environment.fromDefault(Configuration.class);
}
}
A arquitetura do RestExpress funciona assim:
A classe "Routes.java" configura as rotas de acordo com o método HTTP e a URI, invocando nosso Controler para lidar com o Request:
package com.obomprogramador.lightjava;
import org.jboss.netty.handler.codec.http.HttpMethod;
import org.restexpress.RestExpress;
import com.obomprogramador.lightjava.config.Configuration;
public abstract class Routes
{
public static void define(Configuration config, RestExpress server)
{
/*
* Temos uma rota para lidar com um único Artista: /artista/{uuid} e
* outra para lidar com coleções de artistas: /artista
*/
server.uri("/artista/{uuid}", config.getArtistaController())
.method(HttpMethod.GET, HttpMethod.PUT, HttpMethod.DELETE)
.name(Constants.Routes.ARTISTA_SIMPLES);
server.uri("/artista", config.getArtistaController())
.action("readAll", HttpMethod.GET)
.method(HttpMethod.POST)
.name(Constants.Routes.ARTISTA_COLLECTION);
}
}
E o desempenho?
É absolutamente fantástico! Ele consegue atender a vários requests simultâneos, bem mais rapidamente que rodando sob um Container (como o JBoss, ou Tomcat). Ainda não fiz um benchmark próprio, mas existem vários por ai, que podem mostrar a comparação.
Esses frameworks servidores C10k, como o Node.js, o Netty, o NGIX e outros, apresentam melhor desempenho em ambientes de nuvem, com escalabilidade elástica, além de maior eficiência no consumo de recursos. Eles atendem a mais requests, consumindo menor CPU e memória.
Resumo da fritada
Ainda não é uma solução pronta, mas o Netty / RestExpress é uma solução a ser considerada para criar uma plataforma "LightJava".