quarta-feira, 26 de novembro de 2014

Olha, Mamãe: Sem Container!


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 arquétipo do RestExpress, com o MongoDB incluído:

mvn archetype:generate -DarchetypeGroupId=com.strategicgains.archetype -DarchetypeArtifactId=restexpress-mongodb -DarchetypeVersion=1.12
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".