Micro Profile JavaEE com Wildfly Swarm



Podemos criar aplicações Java EE separadas, vários arquivos war, jar ou ear. Podemos ter um único container. Porém pensando em micro serviços estaríamos assim gerando um único ponto de falha.
Uma alternativa seria cada serviço possuir seu próprio container. Com essa abordagem podemos fazer um sub uso deste. Uma vez que um container possui vários serviços embarcados, os quais podem não estar sendo usados por nossa aplicação.
É aí que uma ferramenta como o WildFly Swarm pode facilitar. Ele é uma completa desmontagem do Wildfly, em componentes reutilizáveis chamado frações. Assim podemos dizer quais módulos da API JEE queremos utilizar. O Swarm utiliza o conceito de UberJar, para gerar o arquivo final. Ou seja, um arquivo jar que contém o artefato da aplicação e todos as dependências para que o servidor consiga rodar.

Logo teremos um jar um pouco maior, porém poderemos rodar a aplicação com um único java -jar no console.




No Wildfly Swarm temos o conceito de fração, que nada mais é do que uma funcionalidade/configuração do servidor de aplicação. Na maioria das vezes uma fração pode ser comparado (mapeado) a um subsystem do servidor de aplicação (ex.: Datasource, Driver, Pool, socket-binding e etc…), temos outros casos em que uma fração é uma funcionalidade que antes não tínhamos (nativamente) no servidor de aplicação (Ex.: Jolokia, Spring, NetflixOSS ).
Para usar o Swarm 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.

 <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>2017.2.0</version.wildfly.swarm>  
      <version.h2>1.4.187</version.h2>  
 </properties>  

Agora vamos importar o arquivo pom.xml do Wildfly Swarm que tem o nome de BOM (Bill of Materials).  Se você quiser enumerar explicitamente as frações de WildFly Swarm que seu aplicativo usa, em vez de confiar na detecção automática, o projeto inclui um conjunto de listas de materiais (lista de materiais), que você pode usar para evitar ter que rastrear e atualizar versões de artefatos Maven em vários locais.

      <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>  

WildFly Swarm é descrito como "apenas o necessário app-server", o que significa que é composto de lotes de peças. Seu aplicativo inclui apenas as peças de que necessita.

Ao longo do tempo, algumas dessas peças atingiram status "estável", enquanto algumas são decididamente "instáveis" ou mesmo "experimentais". Para ajudar a separar o estável do não-como-estável, podem ser usadas diferentes listas técnicas do Maven.

bom-all  
Inclui tudo. Tudo o que é estável, instável, experimental, ou mesmo depreciado está incluído.

bom-deprecated
Inclui somente as frações que estão depreciadas.

bom-experimental
Inclui apenas as frações que são consideradas experimental. Eles podem desaparecer ou mudar radicalmente entre os lançamentos.

bom-unstable
Inclui frações que são melhores que as experimentais, mas ainda podem estar sujeitas a grandes mudanças no seu comportamento, ou podem conter bugs.

bom-stable ou bom
Inclui apenas frações estáveis e com uso recomendado.


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

 <dependency>  
      <groupId>javax</groupId>  
      <artifactId>javaee-api</artifactId>  
      <version>7.0</version>  
      <scope>provided</scope>  
 </dependency>  

Após isso precisamos adicionar as dependências que queremos no servidor de aplicação.

 <!--Swarm Dependencies -->  
           <dependency>  
                <groupId>org.wildfly.swarm</groupId>  
                <artifactId>jaxrs</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>  
           <dependency>  
                <groupId>com.h2database</groupId>  
                <artifactId>h2</artifactId>  
                <version>${version.h2}</version>  
           </dependency>  
           <dependency>  
                <groupId>org.wildfly.swarm</groupId>  
                <artifactId>cdi</artifactId>  
           </dependency>  
      </dependencies>              

Ao final teremos um pom.xml como o abaixo.

 <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>com.talkingaboutjava</groupId>  
      <artifactId>uf-exemplo</artifactId>  
      <version>1.0</version>  
      <packaging>war</packaging>  
      <name>uf</name>  
      <url>http://maven.apache.org</url>  
      <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>2017.2.0</version.wildfly.swarm>  
           <version.h2>1.4.187</version.h2>  
      </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>  
           <!--Swarm Dependencies -->  
           <dependency>  
                <groupId>org.wildfly.swarm</groupId>  
                <artifactId>jaxrs</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>  
           <dependency>  
                <groupId>com.h2database</groupId>  
                <artifactId>h2</artifactId>  
                <version>${version.h2}</version>  
           </dependency>  
           <dependency>  
                <groupId>org.wildfly.swarm</groupId>  
                <artifactId>cdi</artifactId>  
           </dependency>  
      </dependencies>  
      <build>  
           <resources>  
                <resource>  
                     <directory>src/main/resources</directory>  
                     <filtering>true</filtering>  
                </resource>  
           </resources>  
           <finalName>uf-wildfly-swarm</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>  
                     <configuration>  
                          <mainClass>com.talkingaboutjava.swarm.Boot</mainClass>  
                     </configuration>  
                </plugin>  
           </plugins>  
      </build>  
 </project>  

Com essa configuração inicial podemos iniciar o desenvolvimento do nosso pequeno sistema. Criaremos as respectivas classes abaixo.

 @Entity  
 @Table(name = "UF")  
 public class Uf implements Serializable {  
      /**  
       *   
       */  
      private static final long serialVersionUID = 232359273804681340L;  
      private String id;  
      private String nome;  
      private Long version;  
      /**  
       * Construtor padrão  
       */  
      public Uf() {  
           this(null, null);  
      }  
      /**  
       * Construtor  
       *   
       * @param id  
       *      {@link String}  
       * @param nome  
       *      {@link String}  
       */  
      public Uf(String id, String nome) {  
           setId(id);  
           setNome(nome);  
      }  
      @Id  
      @Column(name = "UF", columnDefinition = "char(2)")  
      public String getId() {  
           return id;  
      }  
      public void setId(String id) {  
           this.id = id;  
      }  
      @Column(name = "NOME")  
      public String getNome() {  
           return nome;  
      }  
      public void setNome(String nome) {  
           this.nome = nome;  
      }  
      @Version  
      @Column(name = "VERSION")  
      public Long getVersion() {  
           return version;  
      }  
      public void setVersion(final Long version) {  
           this.version = version;  
      }  
      @Override  
      public int hashCode() {  
           final int prime = 31;  
           int result = 1;  
           result = prime * result + ((id == null) ? 0 : id.hashCode());  
           return result;  
      }  
      @Override  
      public boolean equals(Object obj) {  
           if (this == obj)  
                return true;  
           if (obj == null)  
                return false;  
           if (getClass() != obj.getClass())  
                return false;  
           Uf other = (Uf) obj;  
           if (id == null) {  
                if (other.id != null)  
                     return false;  
           } else if (!id.equals(other.id))  
                return false;  
           return true;  
      }  
 public class UfDAO {  
      @PersistenceContext  
      private EntityManager entityManager;  
      public List<Uf> buscaTodos() {  
           return entityManager.createQuery("select e from Uf e order by e.id desc", Uf.class).getResultList();  
      }  
      public void create(String sigla, String nome) {  
           entityManager.persist(new Uf(sigla, nome));  
      }  
 }  
 @Stateless  
 public class UfService {  
      @Inject  
      private UfDAO ufDAO;  
      public List<Uf> buscaTodos() {  
           return ufDAO.buscaTodos();  
      }  
 }  
 @Path("uf/")  
 public class UfRest {  
      @Inject  
      private UfService ufService;  
      @GET  
      @Path("buscaTodos")  
      @Produces("application/json")  
      public List<Uf> buscaTodos() {  
           return ufService.buscaTodos();  
      }  
 }  

Iremos criar também uma classe para configurar o JAX-RS para receber as requisições no contexto a partir da raiz.


 import javax.ws.rs.ApplicationPath;  
 import javax.ws.rs.core.Application;  
 @ApplicationPath("/")  
 public class JaxRsConfiguration extends Application {  
 }  

Com isso teremos nosso recurso disponível na url /uf/buscaTodos.
Agora para conseguir rodar nossa aplicação iremos iniciar a construção da nossa classe de Boot. Nessa classe iniciaremos o Swarm, bem como as frações que serão necessárias para nossa aplicação.

                A primeira coisa que precisamos fazer é registrar nosso dataSource.

 private static DatasourcesFraction datasourceWithH2() {  
           return new DatasourcesFraction().jdbcDriver("h2", (d) -> {  
                d.driverClassName("org.h2.Driver");  
                d.xaDatasourceClass("org.h2.jdbcx.JdbcDataSource");  
                d.driverModuleName("com.h2database.h2");  
           }).dataSource("ExampleDS", (ds) -> {  
                ds.driverName("h2");  
                ds.connectionUrl("jdbc:h2:mem:test;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE");  
                ds.userName("sa");  
                ds.password("sa");  
           });  
      }  

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 seguranç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 static void main(String[] args) throws Exception {  
           Swarm swarm = new Swarm(args);  
           swarm.fraction(datasourceWithH2());  
           swarm.start();  
           JAXRSArchive deployment = ShrinkWrap.create(JAXRSArchive.class);  
           ClassLoader classLoader = Boot.class.getClassLoader();  
           deployment.addModule("com.h2database.h2");  
           deployment.addAsWebInfResource(classLoader.getResource("beans.xml"), "beans.xml");  
           deployment.addAsWebInfResource(classLoader.getResource("persistence.xml"), "classes/META-INF/persistence.xml");  
           deployment.addAsWebInfResource(classLoader.getResource("load.sql"), "classes/META-INF/load.sql");  
           deployment.addPackages(true, "com.talkingaboutjava");  
           deployment.addAllDependencies();  
           swarm.deploy(deployment);  
      }  

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 uf-exemplo.war e um uf-exemplo-swarm.jar (entre outros arquivos).
O arquivo uf-exemplo-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 uf-exemplo-swarm.jar.

O código fonte completo pode ser baixado aqui.

Referencias


Comentários

Postagens mais visitadas deste blog

Engenharia de Agentes de IA com LangChain4j, Spring Boot e Gemini

Mapeando Enuns com JPA.