Micro Profile JavaEE com Wildfly Swarm

Micro Profile JavaEE com Wildfly Swarm
fernando.furtado
fernando.furtado

Compartilhe

Foi-se o tempo em que desenvolver uma aplicação JavaEE era algo extremamente burocrático e que tínhamos um servidor de aplicação inchado e com um consumo excessivo de recursos.

Hoje temos servidores que foram pensado na reutilização dos recursos e ainda temos a possibilidade de escolher quais módulos vamos inicializar junto com o servidor de aplicação, os chamados profiles. Mas e quando pensamos em micro-serviço? Como podemos trabalhar com micro-serviços no JavaEE?

A ideia do micro-serviço é de ter serviços isolados e independentes. Até aí tudo bem, podemos criar aplicações JavaEE separadas(isto é cada uma com seu .war). Mas como podemos fazer o deploy dessas aplicações?

Banner promocional da Alura, com um design futurista em tons de azul, apresentando dois blocos de texto, no qual o bloco esquerdo tem os dizeres:

Podemos ter um único contêiner (servidor de aplicação) para onde efetuamos o deploy de todas as aplicações. Mas com isso estamos indo de frente com a ideia de micro-serviço. Pois estamos gerando um ponto único de falha (SPOF).

Em outras palavras se precisarmos por alguma razão interromper o contêiner, todas as aplicações penduradas nele cairão também.

Outra alternativa seria cada serviço ter seu próprio contêiner, mas dessa forma estaríamos subutilizando o próprio contêiner. Pois nele temos diversos serviços rodando e que nossas aplicações não iriam precisar.

![Fonte: http://wildfly-swarm.io](assets/micro-profile-javaee-com-wildfly-swarm/monolithic-as-300x73.png) Fonte: http://wildfly-swarm.io 

Se não tivessemos usando servidor de aplicação poderiamos usar um contêiner embutido na aplicação (Tomcat, Jetty, Undertown e etc..). Porém dessa forma perderiamos as facilidades que o servidor de aplicação nos proporcionam.

E aí que entra uma ferramenta nova para nos ajudar, Wildfly-Swarm.

Com ele podemos declarar quais "módulos" da plataforma JavaEE queremos utilizar.

A partir daí é desenvolver sua aplicação JavaEE normalmente e ao empacotar nossa aplicação, o wildfly-swarm pegará nosso pacote .war e irá embrulhar em um pacote dele com um contêiner micro para rodar nossa aplicação. Daí o nome de Micro-Profile.

Esse artefato final (o pacote que foi gerado a partir do nosso .war) é um arquivo .jar e podemos executar com um simples comando java -jar no nosso terminal.

Wildfly-Swarm usa o conceito de UberJar para gerar o artefato final. Que nada mais é do que um arquivo .jar que contém além do seu .war todas as dependências necessárias para que o wildfly-swarm consiga rodar.

Logo um UberJar é um arquivo .jar um pouco mais inchado mas somente com o necessário para rodar a aplicação.

![Fonte: http://wildfly-swarm.io](assets/micro-profile-javaee-com-wildfly-swarm/swarm-uberjar-300x300.png) Fonte: http://wildfly-swarm.io

O mínimo necessário para rodar o wildfly-swarm é JDK8 e Maven ou Gradle.

No wildfly-swarm temos o conceito de fraction, que nada mais é do que uma funcionalidade/configuração do servidor de aplicação. Na maioria das vezes um fraction pode ser comparado (mapeado) com um subsystem do servidor de aplicação (ex.: Datasource, Driver, Pool, socket-binding e etc...), temos outros casos em que um fraction é uma funcionalidade que antes não tínhamos (nativamente) no servidor de aplicação (Ex.: Jolokia, Spring, NetflixOSS ).

Para usar o wildfly precisamos importar um pom.xml do wildfly-swarm no nosso projeto, e adicionar um plugin que fará a geração do UberJar.

Nesse post vamos construir uma simples aplicação rest usando o wildfly-swarm. Vamos lá!

Com o projeto (web) maven criado precisamos importar o pom.xml do wildfly-swarm ao nosso projeto. Esse pom.xml é importado declarando uma denpendencia gerenciada e declarando que o escopo da mesma será import.

Para não ficar espalhando a versão do wildfly-swarm estamos utilizando por todo nosso pom.xml vamos declarar uma propriedade com ela. Além disso já vamos definir a versão do Java que iremos utilizar, e como se trata de um projeto web teoricamente precisariamos de um arquivo web.xml porém não é necessário para nosso caso. Então vamos definir que não deve ser gerado um erro caso não exista o arquivo web.xml.

 <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <maven.compiler.source>1.8</maven.compiler.source> <maven.compiler.target>1.8</maven.compiler.target> <failOnMissingWebXml>false</failOnMissingWebXml> <version.wildfly.swarm>2016.9</version.wildfly.swarm> </properties> 

Agora vamos importar o arquivo pom.xml do wildfly-swarm que tem o nome de BOM (Bill of Materials).

 <dependencyManagement> <dependencies> <dependency> <groupId>org.wildfly.swarm</groupId> <artifactId>bom</artifactId> <version>${version.wildfly.swarm}</version> <scope>import</scope> <type>pom</type> </dependency> </dependencies> </dependencyManagement> 

Além disso precisamos adicionar o plugin que irá gerar o UberJar baseado no nosso war.

 <plugin> <groupId>org.wildfly.swarm</groupId> <artifactId>wildfly-swarm-plugin</artifactId> <version>${version.wildfly.swarm}</version> <executions> <execution> <goals> <goal>package</goal> </goals> </execution> </executions> </plugin> 

Precisamos também adicionar a dependência referente à api do JavaEE e esta será provida pelo wildfly-swarm.

 <!--Java EE Api --> <dependency> <groupId>javax</groupId> <artifactId>javaee-api</artifactId> <version>7.0</version> <scope>provided</scope> </dependency> 

E esse é o setup para utilizar o wildfly-swarm, ao final teriamos o nosso arquivo pom.xml mais ou menos da seguinte maneira:

 <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>br.com.caelum</groupId> <artifactId>livraria</artifactId> <version>1.0</version> <packaging>war</packaging>

<properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <maven.compiler.source>1.8</maven.compiler.source> <maven.compiler.target>1.8</maven.compiler.target> <failOnMissingWebXml>false</failOnMissingWebXml> <version.wildfly.swarm>2016.9</version.wildfly.swarm> </properties>

<dependencyManagement> <dependencies> <dependency> <groupId>org.wildfly.swarm</groupId> <artifactId>bom</artifactId> <version>${version.wildfly.swarm}</version> <scope>import</scope> <type>pom</type> </dependency> </dependencies> </dependencyManagement>

<dependencies>

<!--Java EE Api --> <dependency> <groupId>javax</groupId> <artifactId>javaee-api</artifactId> <version>7.0</version> <scope>provided</scope> </dependency>

</dependencies>

<build> <finalName>livraria</finalName> <plugins> <plugin> <groupId>org.wildfly.swarm</groupId> <artifactId>wildfly-swarm-plugin</artifactId> <version>${version.wildfly.swarm}</version> <executions> <execution> <goals> <goal>package</goal> </goals> </execution> </executions> </plugin> </plugins> </build>

</project> 

Agora vamos adicionar as dependências que queremos do servidor de aplicação, para o nosso caso vamos adicionar, jax-rs, cdi, ejb, jpa, datasources.


<!--Swarm Dependencies -->

<dependency> <groupId>org.wildfly.swarm</groupId> <artifactId>jaxrs-cdi</artifactId> </dependency>

<dependency> <groupId>org.wildfly.swarm</groupId> <artifactId>ejb</artifactId> </dependency>

<dependency> <groupId>org.wildfly.swarm</groupId> <artifactId>jpa</artifactId> </dependency>

<dependency> <groupId>org.wildfly.swarm</groupId> <artifactId>datasources</artifactId> </dependency>

Vamos começar a "codar" nosso projeto de livraria.

Vamos iniciar criando nossas classes de domínio Livro e Autor e seus respectivos DAOs

 @Entity public class Livro { @Id @GeneratedValue(strategy=GenerationType.IDENTITY) private Long id; private String titulo; private String descricao; @ManyToMany private List<Autor> autores; @Deprecated private Livro(){} public Livro(Long id, String titulo, String descricao, List<Autor> autores) { this.id = id; this.titulo = titulo; this.descricao = descricao; this.autores = autores; } public Long getId() { return id; } public String getTitulo() { return titulo; } public String getDescricao() { return descricao; }

public List<Autor> getAutores() { return autores; }

public void adicionaAutor(Autor autor) { this.autores.add(autor); } } 
 @Entity public class Autor { @Id @GeneratedValue(strategy=GenerationType.IDENTITY) private Long id; private String nome; @Deprecated private Autor(){} public Autor(Long id, String nome) { this.id = id; this.nome = nome; }

public Long getId() { return id; }

public String getNome() { return nome; } } 
 public class LivroDAO {

@PersistenceContext private EntityManager manager;

public Livro buscaLivroComId(Long id) {

Livro livro = manager.createQuery("select l from Livro l left join fetch l.autores a where l.id = :id", Livro.class) .setParameter("id", id).getSingleResult();

return livro; }

public List<Livro> listaTodos() { return manager.createQuery("select l from Livro l left join fetch l.autores a", Livro.class).getResultList(); }

public void adicionar(Livro livro) { manager.persist(livro); }

public void atualiza(Livro livro) { manager.merge(livro); }

} 
 public class AutorDAO {

@PersistenceContext private EntityManager manager;

public Autor buscaAutorComId(Long id) { return manager.find(Autor.class, id); }

public void adiciona(Autor autor) { manager.persist(autor); } } 

Vamos criar uma classe para configurar o JAX-RS para receber requisições a partir da raiz:

 import javax.ws.rs.ApplicationPath; import javax.ws.rs.core.Application;

@ApplicationPath("/") public class JaxRsConfiguration extends Application{ } 

Agora que já temos como receber requisições vamos criar nossos recursos que serão expostos pela nossa api LivroResource e AutorResource além disso já vamos marcar-los como EJB Stateless para que já tenhamos transação entre outras coisas disponíveis.

 @Path("livros") @Stateless public class LivroResource {

@Inject private LivroDAO livroDao;

@Path("{id:\\\\d+}") @GET @Produces(MediaType.APPLICATION\_JSON) public Livro buscaPorId(@PathParam("id") Long id){ return livroDao.buscaLivroComId(id); } @GET @Produces(MediaType.APPLICATION\_JSON) public List<Livro> listaDeLivros(){ return livroDao.listaTodos(); } @POST @Consumes(MediaType.APPLICATION\_JSON) public Response adicionaLivro(Livro livro){ livroDao.adicionar(livro); return Response .created(URI.create("/livros/" + livro.getId())) .entity(livro) .type(MediaType.APPLICATION\_JSON) .build(); } } 

Nesse recurso temos as seguintes URIs

  • /livros - GET (Retorna uma lista com todos os livros)
  • /livros/id - GET (Retorna o livro com ID especificado)
  • /livros - POST (Cria um novo livro)
 @Path("livros/{livroId:\\\\d+}/autores") @Stateless public class AutorResource { @Inject private LivroDAO livroDao; @Inject private AutorDAO autorDao; @GET @Produces(MediaType.APPLICATION\_JSON) public List<Autor> listaDeAutoresDoLivro(@PathParam("livroId") Long livroId){ return livroDao.buscaLivroComId(livroId).getAutores(); } @GET @Path("{autorId:\\\\d+}") @Produces(MediaType.APPLICATION\_JSON) public Autor autorDoLivro(@PathParam("autorId") Long autorId){ return autorDao.buscaAutorComId(autorId); } @POST @Consumes(MediaType.APPLICATION\_JSON) public Response adicionaAutor(@PathParam("livroId") Long livroId, Autor autor){ Livro livro = livroDao.buscaLivroComId(livroId); autorDao.adiciona(autor); livro.adicionaAutor(autor); livroDao.atualiza(livro); return Response .created(URI.create("/livros/" + livroId + "/autores/" + autor.getId()) ) .entity(autor) .type(MediaType.APPLICATION\_JSON) .build(); }

} 

Nesse recurso temos as seguintes URIs

  • /livros/idDoLivro/autores - GET (retorna todos os autores de um livro especifico)
  • /livros/idDoLivro/autores/idDoAutor - GET (retorna um autor especifico de um livro especifico)
  • /livros/idDoLivro/autores - POST (cria um novo autor associado ao livro)

Agora que temos nosso projeto pronto temos que registrar um datasource e associa-lo no nosso arquivo persistence.xml . Para o nosso caso, esta configuração será feita em uma classe com um método main.

Porém para configurar o datasource precisamos dizer qual o driver, e no nosso caso será um driver para mysql. para não precisarmos registrar um driver, podemos adicionar a dependência para esse driver diretamente no pom.xml

 <dependency> <groupId>org.wildfly.swarm</groupId> <artifactId>mysql</artifactId> <version>${version.wildfly.swarm}</version> </dependency> 

Para que seja adicionado corretamente o driver para mysql precisamos excluir o driver default que vem quando adicionamos a dependência para JPA que é o H2. Para isso vamos alterar a dependência de JPA e remover o H2.

 <dependency> <groupId>org.wildfly.swarm</groupId> <artifactId>jpa</artifactId> <exclusions> <exclusion> <groupId>org.wildfly.swarm</groupId> <artifactId>h2</artifactId> </exclusion> </exclusions> </dependency> 

(Se não fizermos essa configuração ao subirmos o wildfly-swarm iremos receber um erro, informando que esta rolando um conflito entre essas dependências H2, MYSQL).

Pronto agora vamos registrar nosso datasource dentro de um método main:

 public class Boot { public static void main(String\[\] args) throws Exception { Swarm swarm = new Swarm(args); swarm.fraction( new DatasourcesFraction() .dataSource("swarmDs", (ds) -> { ds.driverName("mysql"); ds.connectionUrl("jdbc:mysql://localhost/livraria\_swarm?createDatabaseIfNotExist=true&&useSSL=false"); ds.userName("root"); })); swarm.start(); } } 

Como estamos sobrescrevendo o comportamento default do wildfly-swarm precisamos ensinar ele como ele deve fazer o deploy da nossa aplicação (ou seja dentro do nosso .war quais classes devem estar disponíveis, quais arquivos e etc...).

Para fazer isso iremos usar uma biblioteca chamada ShrinkWrap da própria JBoss que serve para criar um pacote programaticamente, ela é muito utilizada quando estamos usando o Arquillian para testes de aceitação/integração.

Com ele podemos criar JARArchive (.jar) e WARArchive (.war). Além desses temos outros tipos mais especificos por exemplo RibbonArchive um archive especifico que pode ser registrar em aplicações baseadas em Ribbon, Secured que já injeta o arquivo keycloak.json e já configura a parte de seguraça.

Temos também um outro tipo especifico que é JAXRSArchive que é um archive que já configura o jax-rs e faz o binding para classe de configuração Application/@ApplicationPath. Vamos utilizar esse archive.

Além disso precisamos dizer que ao fazer o deploy da aplicação seja levado os arquivos persistence.xml e beans.xml necessários para o funcionamento da JPA e CDI.

O arquivo beans.xml deve estar dentro do diretório WEB-INF a partir do classpath e o arquivo persistence.xml, deve estar em META-INF a partir do classpath também. E precisaremos configurar isso também.

 public class Boot { public static void main(String\[\] args) throws Exception { Swarm swarm = new Swarm(args); swarm.fraction( new DatasourcesFraction() .dataSource("swarmDs", (ds) -> { ds.driverName("mysql"); ds.connectionUrl("jdbc:mysql://localhost/livraria\_swarm?createDatabaseIfNotExist=true&&useSSL=false"); ds.userName("root"); })); swarm.start(); JAXRSArchive deployment = ShrinkWrap.create(JAXRSArchive.class); ClassLoader classLoader = Boot.class.getClassLoader(); deployment.addAsWebInfResource(classLoader.getResource("beans.xml"),"beans.xml"); deployment.addAsWebInfResource(classLoader.getResource("persistence.xml"),"classes/META-INF/persistence.xml"); deployment.addPackages(true, Package.getPackage("br.com.caelum.livraria")); deployment.addAllDependencies(); swarm.deploy(deployment); } } 

Como ultima configuração precisamos indicar ao plugin do wildfly-swarm que ele não deve usar sua classe padrão para iniciar. Ele deve usar nossa classe Boot e com isso alterar o arquivo de manifesto para usar essa classe como ponto inical da nossa aplicação. Vamos alterar a declaração do plugin no arquivo pom.xml e adicionar a configuração ``.

 <plugin> <groupId>org.wildfly.swarm</groupId> <artifactId>wildfly-swarm-plugin</artifactId> <version>${version.wildfly.swarm}</version> <executions> <execution> <goals> <goal>package</goal> </goals> </execution> </executions> <configuration> <mainClass>br.com.caelum.livraria.Boot</mainClass> </configuration> </plugin> 

Para que seja gerado o UberJar devemos executar o goal package do maven (Ex.: mvn package). Ao termino dessa execução no diretório target teremos um arquivo livraria.war e um livraria-swarm.jar (entre outros arquivos).

O arquivo livraria-swarm.jar é o nosso UberJar.

Para executarmos podemos faze-lo pelo plugin do maven através de mvn wildfly-swarm:run ou executando o comando java -jar livraria-swarm.jar.

Dessa forma temos uma aplicação JavaEE somente com o mínimo necessário e um contêiner bem mais leve para rodar a mesma.

Aprendendo mais

Quer aprender mais sobre como utilizar a especificação JavaEE?

Não deixe de conferir nosso curso Plataforma JavaEE, nele abordamos jax-rs, jpa, cdi, ejb entre outros recursos da especificação JavaEE.

E aí o que você achou do wildfly-swarm?

Veja outros artigos sobre Programação