Pesquisar este blog

domingo, 21 de outubro de 2018

Microservices Arquitetura de Microserviços em Java na Prática

Microservices Arquitetura de Microserviços em Java na Prática

Anteriormente, aqui no blog, já houve uma explicação sobre a arquitetura de microserviços no post específico de arquitetura, agora vamos ver na prática como implementar um serviço, utilizando esta arquitetura.
Para este post vamos usar a biblioteca chamada DropWizard, que é responsável por trazer algumas bibliotecas usadas pelo java, como o Jetty, servidor light HTTP, o Jersey usado para chamadas REST, parsers de Json e algumas outras bibliotecas que serão bem úteis neste projeto.
Com isto conseguiremos gerar um arquivo jar, executável, que conterá um servidor web para prover o nosso microserviço para teste.

Iniciando o projeto.

O primeiro passo para este projeto é criar um projeto maven e adicionar a dependencia do dropwizard.
<dependencies>
    <dependency>
        <groupId>io.dropwizard</groupId>
        <artifactId>dropwizard-core</artifactId>
        <version>1.3.5</version>
    </dependency>
</dependencies>
 Assim o maven irá incluir a dependencia para esta versão do dropwizard.

Criando uma classe de configuração

Cada projeto do dropwizard possui uma classe de configuração, permitindo assim, uma flexibilidade bem grande na criação destes projetos
As configurações do projeto serão inseridas em um arquivo yaml. No exemplo postado aqui, será construído um pequeno serviço de exemplo de alta performance.
A seguir segue o exemplo da classe de configuração:
package br.unip.dsd_microservice.config;

import io.dropwizard.Configuration;
import com.fasterxml.jackson.annotation.JsonProperty;
import org.hibernate.validator.constraints.NotEmpty;

public class ConfiguracaoMicroservicos extends Configuration {
    @NotEmpty
    private String template;

    @NotEmpty
    private String nomePadrao = "Estranho";

    @JsonProperty
    public String getTemplate() {
        return template;
    }

    @JsonProperty
    public void setTemplate(String template) {
        this.template = template;
    }

    @JsonProperty
    public String getNomePadrao() {
        return nomePadrao;
    }

    @JsonProperty
    public void setNomePadrao(String name) {
        this.nomePadrao = name;
    }
}
Quando esta classe ler o nosso arquivo de configuração, ela irá pegar duas propriedades dela, template e nomePadrao, cujas propriedades estão anotadas com @NotEmpty, ou seja, se elas não forem encontradas no arquivo de configuração, uma exceção será disparada.
Cada uma destas propriedades estão anotadas com a @JsonProperty, o que faz com que seja possível desserializá-las do arquivo de configuração, assim como serializá-las.
O arquivo de configuração deverá conter estas duas propriedades, como segue abaixo:
template: Olá, %s!
nomePadrao: Estranho
Este arquivo deverá ser salvo no diretório, onde voce deseja rodar o jar com o microserviço, com qualquer_nome.yml e pronto.

package br.unip.dsd_microservice;


import io.dropwizard.Application;
import io.dropwizard.setup.Bootstrap;
import io.dropwizard.setup.Environment;

import br.unip.dsd_microservice.config.ConfiguracaoMicroservicos;
import br.unip.dsd_microservice.healthcheck.VerificadorMetodoExemplo;
import br.unip.dsd_microservice.resource.ResourceExemplo;

public class AplicacaoExemplo extends Application<ConfiguracaoMicroservicos> {
    public static void main(String[] args) throws Exception {
        new AplicacaoExemplo().run(args);
    }

    @Override
    public String getName() {
        return "exemplo-microservico";
    }

    @Override
    public void initialize(Bootstrap<ConfiguracaoMicroservicos> bootstrap) {
        
    }

    @Override
    public void run(ConfiguracaoMicroservicos configuration,
                    Environment environment) {
        //nada por enquanto
    }

}
Esta classe contem a parametrização da configuração do nosso serviço, que será feita na classe ConfiguracaoMicroservicos. O método initialize é utilizado para realizar configurações, antes que a plicação incialize.

Criando o modelo do serviço

Este serviço irá devolver um json, então ele necessita de um modelo para formatar este retorno. Este modelo nada mais é do que uma forma de parametrizar os dados, e a única diferença entre ele, e o que foi visto anteriormente neste blog, é que este será um modelo de JSON.
package br.unip.dsd_microservice.modelo;


import com.fasterxml.jackson.annotation.JsonProperty;
import org.hibernate.validator.constraints.Length;

public class ExemploJson {
    private long id;

    @Length(max = 3)
    private String content;

    public ExemploJson() {
        // Jackson deserialization
    }

    public ExemploJson(long id, String content) {
        this.id = id;
        this.content = content;
    }

    @JsonProperty
    public long getId() {
        return id;
    }

    @JsonProperty
    public String getContent() {
        return content;
    }
}
Repare que este é um modelo comum, porém ele é imutavel, uma vez que não contem nenhum método de set e suas variáveis são private. Isto o torna muito melhor para rodar em ambientes multithread, comuns em micro serviços, uma vez que não teremos comportamentos alterados, devido a alteração de valores do modelo durante a execução em várias threads diferentes.
Os métodos de get, permitem que a api de Json serialize este objeto e os transforme em um JSON.

Criação da classe de recurso

No dropwizard as classes de recurso são utilizadas para mapear uma url a uma classe, diferente do playframework que possui um arquivo de rota, aqui este mapeamento ocorre dentro da classe. Neste caso estamos mapeando o endereço dsd-webservice para esta classe de recurso, com isto, as chamadas a este caminho serão direcionadas a ela.
package br.unip.dsd_microservice.resource;


import com.codahale.metrics.annotation.Timed;

import br.unip.dsd_microservice.modelo.ExemploJson;

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.MediaType;
import java.util.concurrent.atomic.AtomicLong;
import java.util.Optional;

@Path("/dsd-microservice")
@Produces(MediaType.APPLICATION_JSON)
public class ResourceExemplo{
    private final String template;
    private final String nomePadrao;
    private final AtomicLong counter;

    public ResourceExemplo(String template, String nomePadrao) {
        this.template = template;
        this.nomePadrao = nomePadrao;
        this.counter = new AtomicLong();
    }

    @GET
    @Timed
    public ExemploJson digaOla(@QueryParam("name") Optional<String> name) {
        final String value = String.format(template, name.orElse(nomePadrao));
        return new ExemploJson(counter.incrementAndGet(), value);
    }
}
Repare que é possível definir qual metodo http será utilizado por aquele método da classe, com isso o @GET mapeia o método http pro método digaOla.
A anotação @Path é quem demonstra o mapeamento da url ao método, e a anotação @Produces é quem indica que este endereço retornaŕa um Json como resposta.
Repare que esta classe recebe dois parametros como argumento, o primeiro que é o Template, usado para produzir a resposta, e o nomePadrao, que será utilizado na resposta, quando um nome não for fornecido na chamada.

Criando um teste para aplicação

É altamente recomendável que se crie testes de saúde na sua aplicação, o intúito destes testes é chamar os métodos dela, que devem ser todos testados, para provar que eles estão em funcionamento.
Abaixo segue um exemplo de teste para o código do serviço que acabamos de gerar.

package br.unip.dsd_microservice.healthcheck;

import com.codahale.metrics.health.HealthCheck;

public class VerificadorMetodoExemplo extends HealthCheck {
    private final String template;

    public VerificadorMetodoExemplo(String template) {
        this.template = template;
    }

    @Override
    protected Result check() throws Exception {
        final String saying = String.format(template, "TEST");
        if (!saying.contains("TEST")) {
            return Result.unhealthy("não inclui um exemplo de json");
        }
        return Result.healthy();
    }
}
Este teste chega duas coisas, uma é se o retornor está sendo realmente a string esperada e outra é se ele inclui a string dada com entrada, no retorno do método.

Registrando o recurso e o teste na aplicação.

Como passo final, deveremos registrar, no método run da aplicação, tanto o Recurso, quanto o teste criado, isto será feito da seguinte maneira:
 @Override
    public void run(ConfiguracaoMicroservicos configuration,
                    Environment environment) {
        final ResourceExemplo resource = new ResourceExemplo(
            configuration.getTemplate(),
            configuration.getNomePadrao()
        );
        environment.jersey().register(resource);
        final VerificadorMetodoExemplo healthCheck =
                new VerificadorMetodoExemplo(configuration.getTemplate());
        environment.healthChecks().register("template", healthCheck);
    }

Criando o seu pacote

Ao adicionar todos estas classes no seu projeto, agora é hora de alterar o arquivo pom do projeto para que ele configure a classe da Aplicação, como mainclass do seu projeto, para que ela seja chamada quando o jar do projeto for executado.
<build>
 <plugins>
           <plugin>
      <groupId>org.apache.maven.plugins</groupId>
      <artifactId>maven-shade-plugin</artifactId>
      <version>2.3</version>
      <configuration>
          <createDependencyReducedPom>true</createDependencyReducedPom>
         <filters>
             <filter>
                 <artifact>*:*</artifact>
                 <excludes>
                     <exclude>META-INF/*.SF</exclude>
                     <exclude>META-INF/*.DSA</exclude>
                     <exclude>META-INF/*.RSA</exclude>
                 </excludes>
             </filter>
         </filters>
      </configuration>
       <executions>
           <execution>
               <phase>package</phase>
               <goals>
                   <goal>shade</goal>
               </goals>
               <configuration>
                   <transformers>
                       <transformer implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer"/>
                       <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
                           <mainClass>br.unip.dsd_microservice.AplicacaoExemplo</mainClass>
                       </transformer>
                   </transformers>
               </configuration>
           </execution>
       </executions>
   </plugin>
  </plugins>
 </build>
Com isso basta digitar mvn package que o maven irá criar um pacote jar dentro do diretório target no seu projeto.

java -jar target/dsd-microservice-0.0.1-SNAPSHOT.jar server hello-world.yml
Pronto, após seguir todos estes passos, você já é capaz de executar um microserviço com o Java, usando a api DropWizzard.
O código de exemplo deste post encontra-se neste repósitório.

Nenhum comentário:

Postar um comentário