Tuesday, February 26, 2019

SpringBoot + MySQL + Docker-Compose (parte 2)

Na parte 1 deste artigo criamos o front-end. Nesta, criamos o back-end e o serviço de banco de dados.

Back-end com Spring Boot

Acesse a página start.spring.io e preencha os campos de dependências conforme a imagem abaixo e clique em Generate project. Unzipe o arquivo baixado e importe o projeto para o eclipse.



O projeto back-end tem como base o pacote com.app.backend, a partir do qual criamos o pacote entidade, que contém a entidade Pessoa.java idêntica ao do projeto front-end. Temos também o pacote repository, que declara uma interface para persistência de dados utilizando o Spring data. E o pacote controller, que expõe um web service REST que recebe as requisições do front-end. A estrutura do projeto back-end é como da imagem abaixo:


Interface PessoaRepository.java cuja implementação fica a cargo do container do Spring:

package com.app.backend.repository;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

import com.app.backend.entidade.Pessoa;

@Repository
public interface PessoaRepository extends JpaRepository{

}

Web Service REST PessoaController.java:
package com.app.backend.controller;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import com.app.backend.entidade.Pessoa;
import com.app.backend.repository.PessoaRepository;

@RestController
@RequestMapping("/pessoa")
public class PessoaController {
 
 @Autowired
 PessoaRepository pessoaDao;
 
 @PostMapping(value = "/salvar")
 public ResponseEntity salvarPessoa(@RequestBody Pessoa pessoa) {
    
  try {
   pessoa = pessoaDao.save(pessoa);
   ResponseEntity response = new ResponseEntity<>(pessoa, HttpStatus.OK);   
   return response;
  }
  catch(Exception e) {
   e.printStackTrace();
   ResponseEntity response = new ResponseEntity<>(e.getMessage(), 
               HttpStatus.INTERNAL_SERVER_ERROR);   
   return response;
  }  
 }
 
 @GetMapping(value = "/todas")
 public ResponseEntity getTodas() {  
 
  List result = pessoaDao.findAll();  
  ResponseEntity> response = new ResponseEntity<>(result, HttpStatus.OK);
  return response;
 }   
}

A entidade Pessoa.java é idêntica àquela descrita na parte 1 deste artigo. O arquivo Dockerfile é auto explicativo e descreve passo a passo como construir a aplicação e executá-la dentro do Docker:
    FROM openjdk:8
    MAINTAINER "http://finalexception.blogspot.com"
    ADD target/back-end.jar .
    ENTRYPOINT ["java", "-jar", "back-end.jar"]


Na pasta main/resources temos o arquivo application.properties do Spring vazio. Deixe-o conforme abaixo:
# JPA PROPS
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.format_sql=true
spring.jpa.hibernate.ddl-auto=update
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5Dialect
spring.jpa.hibernate.naming-strategy=org.hibernate.cfg.ImprovedNamingStrategy

# APPLICATION CONTEXT PATH
server.servlet.context-path=/app-backend

# DATABASE
spring.datasource.url=jdbc:mysql://mysql-standalone:3306/bd_pessoa?useSSL=true&serverTimezone=UTC
spring.datasource.username=rafael
spring.datasource.password=12345

spring.database.driverClassName =com.mysql.cj.jdbc.Driver

Isso é tudo! Com o botão direito do mouse no projeto, selecione Run As e Run as Maven Install. Esse comando gera o arquivo back-end.jar na pasta target, conforme descrito no Dockerfile.

Subindo a aplicação com Docker-compose

Nossa aplicação tem 3 serviços, poderia ter dezenas. Subir e gerenciar uma grande quantidade de serviços pode ser trabalhos, é aí que entra o docker-compose.

O Compose é um arquivo binário escrito em Python que você deve instalar no host que roda o docker.

A pasta dos projetos back-end e front-end devem estar dentro do mesmo diretório, que também devre conter o arquivo docker-compose.yml:



Com o Docker-compose, definimos os serviços da aplicação em arquivo YML e o Compose se encarrega de publicar a aplicação na engine do Docker.

docker-compose.yml
:
version: '3.5'
services:
  mysql-standalone:
    image: mysql:5.6
    environment:
      - MYSQL_ROOT_PASSWORD=rootpass
      - MYSQL_DATABASE=bd_pessoa
      - MYSQL_USER=rafael
      - MYSQL_PASSWORD=12345
    expose:
      - 3306
  backend:
    build: back-end
    depends_on:
      - mysql-standalone
    ports:
      - 8989:8080
    environment:
      - DATABASE_HOST=mysql-standalone
      - DATABASE_USER=rafael
      - DATABASE_NAME=bd_pessoa
      - DATABASE_PASSWORD=12345
      - DATABASE_PORT=3306
  frontend:
    build: front-end
    depends_on:
      - backend
    ports:
      - 8180:8080


O arquivo docker-compose.yml acima também serve como uma valiosa documentação do projeto. Nele vemos que nossa aplicação possui 3 serviços, cada qual em um host virtual separado: mysql-standalone, backend e frontend. Execute o comando $ docker-compose up para subir aplicação:



Agora você pode acessar o front-end na porta 8180 e testar a aplicação:


O código fonte do projeto completo está no git-hub.

"Fiquem unidos a mim, e eu ficarei unidos a vocês. O ramo que não fica unido à videira não pode dar frutos. Vocês também não poderão dar frutos, se não ficarem unidos a mim. Eu sou a videira, e vocês são os ramos. Quem fica unido a mim, e eu a ele, dará muito fruto, porque sem mim vocês não podem fazer nada"


João 15:4-5

       

Monday, February 4, 2019

SpringBoot + MySQL + Docker-Compose (parte 1)


A maioria das aplicações modernas são constituidas de múltiplos pequenos serviços independentes que interagem e assim dão forma à própria aplicação. Chamamos isso de microserviços. Um simples exemplo pode ser uma aplicação com 3 serviços:
  1. web front-end
  2. web back-end
  3. banco de dados
Coloque os três serviços juntos e temos uma aplicação com alguma utilidade.

Subir e gerenciar vários serviços pode se tornar uma tarefa difícil. É aí que entra o Docker-Compose.

Ao invés de juntar tudo com scripts e comandos extreamente longos, o Docker-Compose permite você descrever toda aplicação em único arquivo de configuração declarativo. Depois então você sobe todos os serviços com um único comando:

$ docker-compose up

Vamos ilustrar o uso do docker-compose criando uma aplicação formada por 3 serviços: uma interface REST no backend (com Spring Boot), um front-end HTML (Java Server Faces) e um banco de dados MySQL. Cada serviço totalmente separado um do outro

Front-End com Java Server Faces

Nosso front-end é um formulário HTML com JSF para cadastrar uma entidade Pessoa, a qual possui dois campos: nome e profissão. O front também lista todas as pessoas registradas na base de dados em um elemento table. O container é o WildFly. Há uma camada de serviço para fazer a requisições ao backend, representada pela classe Service.java e retorná-las ao managed bean. A classe que recebe os eventos da página HTML é ManagedBean.java:

classe ManagedBean.java

package com.app;

import java.util.List;
import javax.annotation.PostConstruct;
import javax.enterprise.context.RequestScoped;
import javax.enterprise.inject.Model;
import javax.faces.application.FacesMessage;
import javax.faces.context.FacesContext;
import javax.inject.Inject;
import com.app.entidade.Pessoa;

@Model
public class ManagedBean {

 @Inject
 private Service service;
 private Pessoa pessoa = new Pessoa();
 private List pessoas;
 
 
 public String salvar() {
  
    try {
  pessoa = service.salvarPessoa(pessoa);
  pessoa = new Pessoa();
  FacesContext.getCurrentInstance()
    .addMessage(null, new FacesMessage(FacesMessage.SEVERITY_INFO, 
           "salvo com sucesso", "salvo com sucesso"));
   }  
   catch(Exception e) {
   FacesContext.getCurrentInstance()
    .addMessage(null, new FacesMessage(FacesMessage.SEVERITY_FATAL, 
            e.getMessage(), e.getMessage()));
  }
  return null;
 }
 
 public Pessoa getPessoa() {
  return pessoa;
 }
 public void setPessoa(Pessoa pessoa) {
  this.pessoa = pessoa;
 }
 public List getPessoas() {
  try {   
   pessoas = service.getPessoas();  
   return pessoas;
  }
  catch(RuntimeException e) {
   FacesContext.getCurrentInstance()
   .addMessage(null, new FacesMessage(FacesMessage.SEVERITY_FATAL, 
           e.getMessage(), e.getMessage()));   
  }
  return null;
 }
 public void setPessoas(List pessoas) {  
  this.pessoas = pessoas;
 } 
}

classe Service.java

package com.app;

import java.net.URI;
import java.util.List;
import javax.enterprise.inject.Model;
import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.client.Entity;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriBuilder;

import com.app.entidade.Pessoa;

@Model
public class Service {

 private final String URL_BACKEND = "http://backend:8080/app-backend/";
 
 public Pessoa salvarPessoa(Pessoa pessoa) {
  
  final String operacao = "salvar";  
  URI uri = UriBuilder.fromUri(URL_BACKEND).path(operacao).build();
  
  //usando JAX-RS Client API para enviar o objeto pessoa ao server back-end
  Entity data = Entity.entity(pessoa, MediaType.APPLICATION_XML_TYPE);
  Response response = ClientBuilder.newClient()
      .target(uri)
      .request(MediaType.APPLICATION_XML)
      .post(data, Response.class);
  
  if(response.getStatusInfo() == Response.Status.OK)
   return pessoa;
  else
   throw new RuntimeException("Ocorreu um erro ao salvar a Pessoa");    
 }
 
 public List getPessoas() {
  
  final String operacao = "all";
  URI uri = UriBuilder.fromUri(URL_BACKEND).path(operacao).build();
  
  Response response = ClientBuilder.newClient()
       .target(uri)
       .request(MediaType.APPLICATION_JSON_TYPE)
       .get(Response.class);
  
  if(response.getStatusInfo() == Response.Status.OK)
   return response.readEntity(List.class);
  else
   throw new RuntimeException("Não foi possível obter a Lista de Pessoas");  
 }
}

Entidade Pessoa.java
package com.app.entidade;

import java.io.Serializable;

import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlRootElement;

@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
public class Pessoa implements Serializable{

 private Long id;
 private String nome;
 private String profissao;

    //getters & setters omitidos
}

Página index.xhtml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
 xmlns:f="http://java.sun.com/jsf/core"
 xmlns:h="http://java.sun.com/jsf/html">

<h:head>
 <title>Cadastro Pessoa</title>
</h:head>
<h:body>
 <h:form>
  <h:messages />
  <h:panelGrid columns="2">
   <h:outputText value="Nome" />
   <h:inputText value="#{managedBean.pessoa.nome}" />
   <h:outputText value="Idade" />
   <h:inputText value="#{managedBean.pessoa.profissao}" />
  </h:panelGrid>
  <h:commandButton action="#{managedBean.salvar}" value="Salvar" />
  <p />
  Lista
  <h:dataTable value="#{managedBean.pessoas}" var="pessoa" border="1">
   <h:column>
    <h:outputText value="#{pessoa.nome}" />
   </h:column>
   <h:column>
    <h:outputText value="#{pessoa.profissao}" />
   </h:column>
  </h:dataTable>
 </h:form>
</h:body>
</html>

O arquivo Dockerfile é auto explicativo e descreve passo a passo como construir a aplicação e executá-la dentro do Docker: A aplicação roda dentro do Wildfly (FROM) e o arquivo front-end.war é adicionado na pasta /opt/jboss/wildfly/standalone/deployments/ do servidor:

   FROM jboss/wildfly
   MAINTAINER "http://finalexception.blogspot.com"
   ADD target/front-end.war /opt/jboss/wildfly/standalone/deployments/

Com o botão direito do mouse na pasta raiz do projeto, selecione Run As e Run as Maven Install. Esse comando gera o arquivo front-end.war na pasta target, conforme descrito no Dockerfile.

Neste ponto a aplicação pode rodar, porém ela ainda não estará funcional, já que depende de dois outros serviços: o backend propriamente dito, este por sua vez depende do banco de dados.

Na parte 2 deste artigo criamos e subimos esses dois serviços que faltam.



"Ouçam agora, vocês que dizem: 'Hoje ou amanhã iremos para esta ou aquela cidade, passaremos um ano ali, faremos negócios e ganharemos dinheiro'. Vocês nem sabem o que acontecerá amanhã! Que é a sua vida? Vocês são como a neblina que aparece por um pouco de tempo e depois se dissipa. Em vez disso, deveriam dizer: 'Se o Senhor quiser, viveremos e faremos isto ou aquilo'."

Tiago 4:13-15