domingo, 3 de maio de 2015

Micro serviços imutáveis: A receita dos campeões!


E aí? Apanhando muito dos seus Projetos? O "Sprint" acaba mais rápido do que as tarefas que você tem a executar? Talvez, a culpa seja sua!

Vamos ver uma alternativa ágil, simples e dinâmica para você parar de apanhar pelas mesmas causas.




"If you find a good solution and become attached to it, the solution may become your next problem." 


Dr. Robert Anthony
Guru de auto-ajuda Americano

(Se você encontrar uma boa solução, e se apegar a ela, a solução se tornará seu próximo problema)

Na verdade, nós somos preguiçosos e desleixados! Só porque uma solução foi eficaz há alguns anos, não significa que continuará a ser eficaz. Por exemplo, vamos supor que você criou um projeto bem sucedido há uns 5 anos, usando Java EE e JSF. Isso não quer dizer que essa arquitetura seja uma boa solução para os dias de hoje.

É preciso renovar, estudar, melhorar, aprimorar, e, principalmente, estar antenado com as tendências dos negócios e das tecnologias atuais.

Nós criamos apps como nossos pais faziam...




Nossa tecnologia tem mais de 10 anos, nossas apps parecem "castelos" monolíticos, de altíssimo acoplamento. Para mudar uma pequena linha de código, precisamos construir todo o castelo novamente.

E, para piorar as coisas, tem um monte de gente "sentada" no caminho crítico da nossa aplicação, sobre os quais não temos a menor influência (Os Operadores e Preparadores de Produção),

Isso nos deixa sem margem para atuar! É como se estivéssemos lutando contra um adversário com o dobro do nosso tamanho, e, ainda por cima, com um dos nossos braços quebrados.

O que os campeões estão fazendo? E como estão fazendo?


"Junte-se aos bons, e serás um deles"

Os campeões de tecnologia atuais são: Netflix, Amazon, Google, Twitter, LinkedIn, Facebook, ThoughtWorks, IBM e muitos outros.

Eles descobriram alguns ingredientes, que, quando combinados em uma receita, os permitem desenvolver apps com uma agilidade que você jamais conseguirá imitar. É a Receita dos Campeões!


Micro serviços



Eles estão usando arquitetura de micro serviços:

  • Particionar sua aplicação em unidades independentes, autônomas, de alta coesão e baixo acoplamento;
  • Geralmente, separados em “conceitos de negócio”;
  • Micro serviços devem ser criados, mantidos, executados e distribuídos de forma totalmente independente;
  • E por equipes independentes!

Micro serviços isolados




Estes micro serviços são executados em hosts separados:

  • Baixo acoplamento;
  • Integração baseada em mecanismos padrões livres, como: HTTP e REST;
  • Balanceamento de carga entre várias instâncias.

Infrastructure as Code / Devops



A infraestrutura é tratada como parte da aplicação, e a equipe é responsável por todo o seu ciclo de vida. Não existem: "Ambientes de produção", "Operadores" e "Preparadores de dados". Os "operadores" são integrados à mesma equipe que desenvolve o sistema. 

IAC (Infrastructure As Code) usa tecnologias como Vagrant ou Docker, para transformar todos os servidores em programas! Você tem um código-fonte que instancia os seus servidores e também a sua estrutura de rede virtual. Várias empresas estão usando essa tecnologia hoje em dia. 

E o código-fonte de infraestrutura fica armazenado no mesmo controlador de versão que o código-fonte funcional, sendo sujeito ao mesmo processo de integração contínua. 


Containers ao invés de VMs


Fonte: ZDNet


Estes micro serviços rodam em Containers (LXC), geralmente usando Docker. Um Container é muito mais leve e performático do que uma máquina virtual, e pode ser gerado diretamente do seu servidor de Integração Contínua. Esses Containers são imutáveis, ou seja, ninguém "mexe" neles! Se for necessário alterar alguma coisa, é só alterar o código que gerou a infraestrutura.

Os micro serviços são compilados pela IC e seu produto é um novo ambiente completo, com um novo Servidor virtual, distribuído com um Container Linux.

Juntando tudo...



Nós temos uma equipe que controla todo o ciclo de vida da aplicação, cuja construção é baseada em Entrega Contínua, com toda a infraestrutura fazendo parte do código versionado. E, como usamos micro serviços, nossos componentes possuem baixíssimo acoplamento.

Mas isso não se aplica à nós, certo?


Já ouviu falar em "Eliminadores de Ideias"? Eles existem em qualquer empresa! São pessoas que não estão interessadas em alterar nada, pois estão se beneficiando do "status quo". Então, sempre que alguém surge com uma nova ideia, eles usam "golpes baixos" (sempre abaixo da cintura), com o intuito de afastar a proposta. Simples assim. Eis os golpes mais conhecidos:

  • E quanto à segurança disto? Todo IDIOTA sempre lança mão deste argumento, como se sua proposta fosse irresponsável e você fosse um imbecil. "Segurança" é algo que apavora a todos, então é a melhor forma de começar a golpear sua ideia;
  • Isso não funcionaria aqui! Claro! Nossa empresa é de Marte, e os processos normais de negócio não se aplicam nela. Essa é a segunda desculpa mais idiota usada pelos Eliminadores de Ideias;
  • A Chefia jamais aprovaria isso! Se tudo mais falhar, esse babacas acabam apelando para a Chefia ou Diretoria, a quem eles irão correndo "envenenar" contra a sua ideia, assim que você acabar de falar.
Não se deixe abater por este tipo de gente! Minha melhor resposta é deixar sem resposta...

Ok, vamos por partes...


Minha solução


Vou usar um repositório Git, onde armazenarei o código-fonte e o código de infraestrutura. Depois, um servidor Jenkins construirá o código, gerando, ao final, um Container Docker em execução. Ou seja, o resultado do processo de construção será uma nova "máquina" fechada, contendo nossa aplicação rodando.

Começando pelo Micro serviço


Para começar, eu criei um Micro Serviço bem simples, que valida a assinatura digital de um arquivo, usando um Certificado auto-assinado. E usei o Dropwizard para criá-lo.

Um serviço Dropwizard é como qualquer RESTful service em Java:

  • Usa JAX-RS, através do projeto Jersey;
  • Usa Jackson, para lidar com serialização JSON;
  • Usa uma combinação de "path" e método HTTP, para decidir qual método Java deve ser invocado. 
Só que, ao contrário da maioria dos projetos Java, ele usa um Container Jetty embutido, o que agrega agilidade ao seu Serviço. O Jetty é uma biblioteca que torna sua app capaz de lidar com HTTP, sem ser uma aplicação Java EE. Na verdade, é um projeto Java comum, que gera um arquivo JAR. Sem JBoss!!!

O projeto todo está no Github, e você pode usar à vontade!

O arquivo "SignatureResource.java" tem a implementação do método principal: 


@Path("/signature")
public class SignatureResource {
	private String keystorePath;
	private String alias;
	private String keystorePassword;
	
    @POST
    @Timed
    @Consumes(MediaType.APPLICATION_JSON)
    public Response verify(SampleDocument document) {
    	boolean resultado = false;
    	String mensagem = "";
    	String status = "";
    	int httpStatus = 200;
    	try {
			resultado = VerifySignature.verify(document.getHexSignature(), 
                    document.getTexto(),
					this.keystorePath, this.alias, 
                    this.keystorePassword);
	    	if (!resultado) {
	    		status = "fail";
	    		mensagem = "assinatura incorreta";
	    	}
	    	else {
	    		status = "success";
	    		mensagem = "assinatura ok!";    		
	    	}
		} catch (Exception e) {
			status = "error";
			mensagem = e.getClass().getName() + ": " 
                + e.getLocalizedMessage();
			httpStatus = 500;
		} 

    	String saidaJSON = "{ \"status\": \"" + status + "\","
    					 + "\"data\": {"
    					 + "\"mensagem\": \"" + mensagem + "\"}}"; 
        return Response.status(httpStatus).entity(saidaJSON).build();
    }


Ok. Para consumir este micro serviço, eu criei um código Javascript, que pode ser executado com Node.js. Assim, demonstro mais uma vantagem da arquitetura de Micro Serviços, que é a Interoperabilidade. O arquivo "src/test/resources/request.js" é o "Cliente" deste serviço:


var http = require('http');
var fs = require('fs');
var file = fs.readFileSync("C:/Users/55018335734/wsDropwizard01/signature/src/test/resources/arquivo.txt", "utf8");
var exemplo = {
  texto: file,
  hexSignature: '8ed7b4235f21db78c92e69082df3874c03d4135515cb04ff1592e66d70999d56c504dd8f6dd275f870873639ea8803ddae40272465101935a19a1877c0f07715f0cb65beb839dbf33d691acc30bd3a1af6bcc42a1b86215c6cc230e7f2ff2bcff0452df651c89659a2a6f4c8364f86ab2fccac5d7ca4d15654839aa9723e9c70f15f0699037e0745947f5253545f66b7cd3b549f9e94066c319c4e5945dddf6bafebf165c984cf60c2b4fb4ae8aade21f0a88a637161c9cb6314cf4fd42ad4c4a50337b911126f188e77dc83aeaed97338a5ee53ddc0c3575041413ab11655129f15418838a2a531516276cda5df1f814f3c3ae8986c6663533a3f31aba73e19'
};

var exemploString = JSON.stringify(exemplo)

var headers = {
  'Content-Type': 'application/json',
  'Content-Length': exemploString.length
};

var options = {
  host: 'localhost',
  port: 8080,
  path: '/signature',
  method: 'POST',
  headers: headers
};

var req = http.request(options, function(res) {
  res.setEncoding('utf-8');

  var responseString = '';

  res.on('data', function(data) {
    responseString += data;
  });

  res.on('end', function() {
    console.log(responseString)
    var resultObject = JSON.parse(responseString);
  });
});

req.write(exemploString);
req.end();

Para testar o serviço:

  1. Copie os arquivos de "src/test/resources" para uma outra pasta;
  2. Compile o projeto com: mvn clean package;
  3. Execute o Jar resultante com este comando: 
java -jar signature-0.0.1-SNAPSHOT.jar server signature.yml


O arquivo "signature.yml" fica em "src/main/resources".


Finalmente, execute o programa cliente com o comando: 

node request

É claro que você deve ter o Node.js instalado!

O resultado é esse: 


enterprise:dockertest cleutonsampaio$ node request
{ "status": "success","data": {"mensagem": "assinatura ok!"}}
enterprise:dockertest cleutonsampaio$ 

Agora, vamos ao Docker!

Eu queria que este serviço fosse distribuído em um Container, então, instalei o Docker em meu Macbook. O Docker roda nativamente em Linux, porém, em MS Windows e Apple Mac OSX, ele roda em uma VM, iniciada pelo "Boot2Docker". 

Instale o Docker e, se sua máquina for Mac OSX ou MS Windows, inicie o Boot2Docker. Depois, para usar o Docker, é só criar as 3 variáveis de ambiente que o "Boot2Docker" mostra para você, na console. No meu caso foram: 

export DOCKER_HOST=tcp://192.168.59.103:2376
export DOCKER_CERT_PATH=/Users/cleutonsampaio/.boot2docker/certs/boot2docker-vm
export DOCKER_TLS_VERIFY=1

A partir daí, você poderá rodar comandos Docker, que serão direcionados para a VM criada pelo Boot2Docker.

Para criar um Container Docker, precisamos de 3 coisas: 
  • O Jar;
  • O arquivo "yml", usado pelo Dropwizard;
  • Um arquivo "Dockerfile".
Vamos ver o arquivo "Dockerfile", que está dentro do projeto "signature": 

FROM dockerfile/java:openjdk-7-jdk
ADD signature-0.0.1-SNAPSHOT.jar /data/signature-0.0.1-SNAPSHOT.jar
ADD signature.yml /data/signature.yml
CMD java -jar /data/signature-0.0.1-SNAPSHOT.jar server /data/signature.yml
EXPOSE 3000

Este programa usa uma imagem de sistema operacional do DockerHub, já com Java Openjdk 7, para criar um Container Docker.  Note que ele adiciona dois arquivos: O Jar e o "signature.yml". O penúltimo passo é rodar o nosso serviço. 

Ele finaliza expondo a porta 3000. 

Se você copiar esse Dockerfile, o Jar e o "signature.yml" para a VM do "Boot2Docker", poderá criar o container com o comando: 

docker build -t "signatureimage" .

E poderá iniciar o container com o comando: 

docker run -d -p 3000:3000 --name signatureserver signatureimage

Ok... Falta uma coisa... O Jenkins!!!

Eu não poderia demonstrar um Micro serviço imutável, sem usar Entrega Contínua. E vou usar o Jenkins para isso. 

Depois de instalar o Jenkins, é necessário instalar um plugin: O "git plugin" ("Gerenciar Jenkins / Gerenciar Plugins", marcar "Git Plugin"). Assim, o Jenkins poderá fazer "checkout" do nosso projeto, diretamente do repositório.

Eu criei um "job" no Jenkins, e ele está dentro do repositório, no arquivo: "signature/SignatureJob.xml". Você pode importá-lo para o Jenkins usando o comando Jenkins CLI: 

java -jar jenkins-cli.jar -s http://localhost:8080/ create-job NAME


Ou pode criar manualmente. É só criar um build para pegar do repositório Git e rodar os comandos maven: "clean package". Depois, acrescente um passo no Build, para executar os seguintes comandos de Shell Script: 

export DOCKER_HOST=tcp://192.168.59.103:2376
export DOCKER_CERT_PATH=/Users/cleutonsampaio/.boot2docker/certs/boot2docker-vm
export DOCKER_TLS_VERIFY=1
cp $WORKSPACE/signature/target/signature-0.0.1-SNAPSHOT.jar .
cp $WORKSPACE/signature/src/main/resources/signature.yml .
cp $WORKSPACE/signature/Dockerfile .
/usr/local/bin/docker ps
set +e
/usr/local/bin/docker rm -f signatureserver
set -e
/usr/local/bin/docker build -t "signatureimage" .
/usr/local/bin/docker run -d -p 3000:3000 --name signatureserver signatureimage

Se estiver usando MS Windows, então deverá adaptar os comandos para ele. 

Estes comandos usam o Docker client para: 
  1. Parar o Container;
  2. Construir um novo Container com o novo Jar;
  3. Subir o novo Container.

Conclusão

A cada alteração no repositório, o Jenkins monitora e dispara o job. Ao final da construção, uma nova máquina é criada, totalmente imutável e fechada, com a nossa aplicação rodando.