Tuesday, September 18, 2018

Microserviços com o PayaraMicro: parte 1

Microserviços constituem uma abordagem arquitetural para sistemas distribuíudos. O objetivo dessa arquitetura é promover o maior desacoplamento possível, de modo que cada serviço seja um sistema menor com seu próprio ciclo de vida, evitando assim armadilhas comuns em sistemas monolíticos maiores, tais como o aumento exponencial e descontrolado da complexidade e a consequente dificuldade de manutenção a medida em que novas funcionalidades vão sendo incorporadas. Empresas como Amazon e Netflix têm se destacado por promover e usar a arquitetura de microserviços em suas próprias plataformas. A comunicação entre os diferentes serviços geralmente é feita via web-services REST.

Microserviços no JavaEE

A iniciativa Eclipse MicroProfile  surgiu em 2016 e reúne grupos de usuários e as empresas que distribuem os principais containers JavaEE como WildFly (Red Hat), WebSphere (IBM), TomEE (TomiTribe) e Payara (antigo GlassFish). O objetivo da iniciativa é otimizar a plataforma JavaEE para o uso da arquitetura de microserviços, criando novos recursos e funcionalidades para isso.

A especificação Eclipse MicroProfile (atualmente na versão 2.0) define uma plataforma de tamanho reduzido para desenvolver microserviços utilizando as principais APIs JavaEE. Portabilidade, menos configuração e simplificação do processo de deploy também são um requisito. Todos os containers microprofile devem conter as seguintes especificações JavaEE:
  • JAX-RS 2.0
  • CDI 1.2
  • JSON-P 1.0 

Payara Micro


Neste artigo exploramos o Payara MicroProfile, o qual, como vimos, é compatível com a especificação Eclipse MicroProfile. Não confunda o Payara Micro com o Payara Server Full, que é o container tradiconal e maior, contendo toda API JavaEE bem como o console de administração.

O Payara Micro compõe um arquivo .jar de 70MB, não é necessário instalação, configuração, nem nenhuma escrita de código. Para subir o Payra Micro apenas execute o arquivo .jar passando como parâmetro o arquivo .war do serviço. Você pode baixá-lo aqui.

Como exemplo, vamos criar um serviço chamado BookService. Esse serviço acessa a base de dados e fornece uma lista de livros disponíveis para outra aplicação que acessa o serviço. Você pode escolher o banco de dados que mais lhe convier, neste exemplo utilizamos o MySQL. A IDE utilizada é o Eclipse JavaEE Photon.

O código fonte do projeto completo está disponível no github.

Crie um banco de dados chamado Livros.

Abra o Eclipse e crie um novo projeto MAVEN com o nome de BookService. No Arquivo pom.xml adicionamos as dependencias do mysql-connector e javaEE 8 API como segue:

<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>BookService</groupId>
 <artifactId>BookService</artifactId>
 <version>1.0</version>
 <packaging>war</packaging>
 <properties>
  <maven.compiler.source>1.8</maven.compiler.source>
  <maven.compiler.target>1.8</maven.compiler.target>
  <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
 </properties>
 <dependencies>
  <dependency>
   <groupId>javax</groupId>
   <artifactId>javaee-web-api</artifactId>
   <version>8.0</version>
   <scope>provided</scope>
  </dependency>
  <!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
  <dependency>
   <groupId>mysql</groupId>
   <artifactId>mysql-connector-java</artifactId>
   <version>8.0.12</version>
  </dependency>
 </dependencies>
</project>

Agora clique com o botão direito na pasta raiz do projeto. Navegue por Java EE Tools e depois selecione Generate Web Deployment Descriptor:


A importância do arquivo web.xml neste projeto será crucial. Tradicionalmente, aplicações Java EE definem o datasource no próprio servidor de aplicação seja via console admin ou por algum cliente em linha de comando. Com microserviços, convém utilizar uma outra variante. Vamos definir o datasource na própria aplicação e, mais ainda, podemos disponibilizar esse datasource para outros serviços. Isso é possível desde que usemos um JNDI válido para o datasource contendo o namespace java:global. Portanto nosso web.xml fica como segue:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" 
         version="3.1" xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
    <session-config>
        <session-timeout>30</session-timeout>
    </session-config>
    <data-source>
        <name>java:global/livrosDataSource</name>
  <class-name>com.mysql.cj.jdbc.MysqlXADataSource</class-name>
        <server-name>localhost</server-name>
        <port-number>3306</port-number>
        <database-name>Livros</database-name>
        <user>root</user>        
        <password>km02h5</password>
        <property>
         <name>serverTimezone</name>
         <value>UTC</value>
        </property>
        <property>
         <name>useTimezone</name>
         <value>true</value>
        </property>
        <property>
         <name>useSSL</name>
         <value>true</value>
        </property>
        <property>
            <name>fish.payara.slow-query-threshold-in-seconds</name>
            <value>5</value>
        </property>
        <property>
            <name>fish.payara.log-jdbc-calls</name>
            <value>true</value>
        </property>
        <property>
            <name>fish.payara.is-connection-validation-required</name>
            <value>true</value>
        </property>
        <property>
            <name>fish.payara.connection-validation-method</name>
            <value>custom-validation</value>
        </property>
        <property>
            <name>fish.payara.validation-classname</name>
            <value>org.glassfish.api.jdbc.validation.MySQLConnectionValidation</value>
        </property>
    </data-source>
</web-app>

Como vamos utilizar as APIs Java Persistence (JPA) e Java Transaction (JTA), precisamos criar o arquivo persistence.xml e informar o nome do datasource criado no web.xml:

<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.1"
 xmlns="http://xmlns.jcp.org/xml/ns/persistence"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_1.xsd">

 <persistence-unit name="livrosDataSourcePU" transaction-type="JTA">
  <jta-data-source>java:global/livrosDataSource</jta-data-source>
  <exclude-unlisted-classes>false</exclude-unlisted-classes>
  <properties>
   <property
    name="javax.persistence.schema-generation.database.action"
    value="create" />
  </properties>
 </persistence-unit>
</persistence>

Crie agora 2 pacotes chamados com.app e outro com.entidades. No pacote entidades crie a classe Livro

//imports omitidos...

@Entity
@Table(name = "LIVROS")
@XmlRootElement
@NamedQuery(name = "Livro.buscarTodos", query = "SELECT l FROM Livro l")
public class Livro implements Serializable{
 
 private static final long serialVersionUID = 1L;

 @Id @Basic @NotNull
 private Long id;
 
 private String nome;
 
 private int numeroPaginas;
 
 private double preco;
 
 @Temporal(TemporalType.DATE)
 private Date publicacao;

    //getters, setters, equals e hashCode omitidos...
}

No pacote app configuramos o web service REST. Primeiro criamos a classe AplicationConfig. Nela definimos que os serviços poderão ser invocados quando houver a palavra rest na URL do  browser:

package com.app;

import java.util.Set;
import javax.ws.rs.core.Application;

@javax.ws.rs.ApplicationPath("rest")
public class ApplicationConfig extends Application{
 
 @Override
 public Set> getClasses() {
  Set> resources = new java.util.HashSet<>();
  resources.add(LivrosREST.class);
  return resources;
 }
}

E agora crie a classe LivrosREST, a qual será o web service propriamente dito, que retorna ao cliente os livros disponíveis:

//imports e package omitidos...

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

 @PersistenceContext(unitName = "livrosDataSourcePU")
 private EntityManager em;

 @GET
 @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
 public List findAll() {
  
  List livros = null;
  try {
   TypedQuery query = em.createNamedQuery("Livro.buscarTodos", Livro.class);
   livros = query.getResultList();
  }
  catch (NoResultException e) {
  }
  return livros;
 }
}

Nosso projeto já está pronto. Clique com o botão direito do mouse na pasta raiz do projeto, navegue pelas opções run/run as/maven install:


Após executar esse comando, repare que no log do eclipse aparece o caminho completo para onde foi gerado o arquivo .war. Selecione e copie esse caminho:


Agora vamos publicar o serviço. Vá para a pasta onde você salvou o arquivo jar do Payara Micro. Como foi dito no começo deste artigo, execute como um arquivo jar qualquer, e utilizamos o comando --deploy passando como parâmetro o arquivo .war que acabamos de gerar. Abra o prompt de comando e execute o seguinte comando:

java -jar  payara-micro-5.182.jar --deploy /home/rafael/eclipse-workspace/BookService/target/BookService-1.0.war

Ao final do log do servidor, aparece a URL do serviço publicado. A porta default é 8080:


Como a tabela do banco está vazia, ao acessar essa URL não teremos nenhum retorno. Como teste execute o seguinte script sql no seu banco de dados:

insert into LIVROS values (1, 'Pro Spring 5', 600, 25.99, '2010-05-25');
insert into LIVROS values (2, 'Java How to Program', 1200, 10.50, '1999-06-30');
insert into LIVROS values (3, 'JSP Design Patterns', 450, 1.99, '2001-11-28');
insert into LIVROS values (4, 'OCP Certification Guide', 900, 25.99, '2012-05-05');
insert into LIVROS values (5, 'Java EE 7', 600, 5.00, '2016-01-02');

Agora sim. Acesse novamente a URL http://localhost:8080/BookService-1.0/rest/livros e veremos a resposta sob a forma de um arquivo xml:


Na segunda parte deste post criamos uma aplicação cliente que acessa esse serviço.