Nesse artigo vamos examinar um framework bem interessante, simples e de alta performance para a construção de serviços. Estamos falando do Apache Thrift, que é amplamente utilizado hoje no mundo apesar de muitos nem saberem que ele existe. Também faremos um casamento do Apache Thrift com o Apache Camel.

Em 2007, o Facebook publicou um trabalho descrevendo em detalhes o funcionamento de um mecanismo de troca de mensagens e construção de serviços ágil. Esse documento dissertava sobre uma espécie de RPC que tempos depois foi doado para a fundação Apache e hoje é conhecido por Apache Thrift.

O Apache Thrift é extremamente simples, mas sua arquitetura interna é bem organizada e ao mesmo tempo facilmente adaptável e isso tem chamado a atenção das grandes corporações tais como Twitter, Linkedin e Facebook já há alguns anos. Se você fizer uma pesquisa rápida no Google associando Thrift ao Twitter, por exemplo, verá que eles já investiram muito tempo e conhecimento na construção de muitas ferramentas extremamente interessantes.


Camadas do Thrift

Figura 1: Camadas do Thrift

Antes de começarmos, provavelmente você não tem as ferramentas necessárias para trabalhar com esse framework. Vá até o site http://thrift.apache.org e faça o download de acordo com a sua plataforma. Detalhes completos de como fazer a instalação está disponível em http://thrift.apache.org/docs/install/.

Antes de continuarmos é preciso entender que ele não é de forma alguma um padrão internacional mantido por alguma entidade regulamentadora tal qual Web Services, mas é bem útil e aplicável dentro de alguns contextos mais específicos. Tanto cliente quanto servidor trocam mensagens bem definidas através de um contrato razoavelmente rígido (Figura 1). Esse contrato é descrito em uma forma de IDL (Interface Definition Language) que é utilizado para gerar tanto código do cliente quanto do servidor.

O Thrift oferece um compilador/gerador para essa IDL e suporta as seguintes linguagens de programação: C++, Java, Python, PHP, Ruby, Erlang, Perl, Haskell, C#, Cocoa, JavaScript, Node.js, Smalltalk, OCaml and Delphi e muito mais. Ora, isso quer dizer que podemos ter facilmente uma aplicação IPHONE (Cocoa/ Objective C) “conversando” com um servidor em Erlang ou Java, utilizando exatamente o mesmo contrato.

Veremos na Listagem 1 um trecho de exemplo dessa IDL e como ela se parece ao mesmo tempo com Java, C++ ou C.

Listagem 1: Exemplo de descritor Thrift


struct Work {
  1: i32 num1 = 0,
  2: i32 num2,
  3: Operation op,
  4: optional string comment,
}
exception InvalidOperation {
  1: i32 what,
  2: string why
}
service Calculator extends shared.SharedService {

   void ping(),

   i32 add(1:i32 num1, 2:i32 num2),

   i32 calculate(1:i32 logid, 2:Work w) throws (1:InvalidOperation ouch),

   oneway void zip()

}

Na Listagem 1 temos estruturas que são agrupamentos de informação (struct), exceções (exception) representando erros, declaração de serviços (service) que inclusive podem estender operações de outros serviços. O Thrift suporta vários tipos pré-definidos como inteiros, string, longs, booleans etc. Mas o que fazer com esse arquivo? É preciso que ele seja interpretado, como vemos na Listagem 2.

Listagem 2: Linha de comando para chamada do Thrift


thrift –gen cocoa meuexemplo.thrift

Se a definição estiver correta, uma série de arquivos serão gerados. No nosso artigo, como trabalharemos com cliente e serviço em Java, utilizaremos o maven para gerenciar isso para nós (Listagem 3).

Listagem 3: trecho de projeto Maven.


<plugin>
	<groupId>org.apache.thrift.tools</groupId>
	<artifactId>maven-thrift-plugin</artifactId>
	<version>0.1.11</version>
	<configuration>
		<thriftExecutable>/usr/local/bin/thrift</thriftExecutable>
	</configuration>
	<executions>
		<execution>
			<id>thrift-sources</id>
			<phase>generate-sources</phase>
			<goals>
				<goal>compile</goal>
			</goals>
		</execution>
		<execution>
			<id>thrift-test-sources</id>
			<phase>generate-test-sources</phase>
			<goals>
				<goal>testCompile</goal>
			</goals>
		</execution>
	</executions>
</plugin>

Reparem que estamos referenciando o caminho onde o executável do compilador do Thrift está instalado no ambiente, nesse caso “/usr/local/bin/thrift”. Isso pode variar de acordo com a sua plataforma. O nosso projeto maven se encarregará de gerar o código e adicioná-lo ao caminho de build do nosso projeto. Mas o que foi gerado? Vejamos a figura 2.

Camadas da arquitetura do Thrift

Figura 1: Camadas da arquitetura do Thrift

Para cada struct, service, exception e enum que declaramos na IDL é gerada uma classe Java correspondente. A mais importante é o Processor criado para cada serviço. Ele faz a ligação entre o protocolo e a nossa implementação de negócio que fica no “Server”. É o processor que sabe identificar, por exemplo, que método está sendo chamado e direcioná-lo corretamente para a implementação que está no Handler.

Implementar um serviço no Thrift significa implementar um Handler que é a realização da interface que está descrita por um service. Não entendeu? Vamos ver a listagem 4 com um exemplo banal de “start” do servidor e chamada do cliente.

Listagem 4: Exemplo de chamada do thrift

Fácil não é? Reparem que para criar um Processor, precisamos de um Handler e para iniciarmos um servidor, precisamos de um processor. O cliente faz uso dos componentes da API do thrift (transport, protocol) e do código gerado (Calculator.Cllient). E a implementação do Handler? Vejamos um trecho na Listagem 5.

Listagem 5: Handler para serviço


public class CalculatorHandler implements Calculator.Iface {
	...
	public void ping() {
		System.out.println("ping()");
	}

	public int add(int n1, int n2) {
		System.out.println("add(" + n1 + "," + n2 + ")");
		return n1 + n2;
	}
	...
}

A essa altura você deve estar pensando se é possível utilizar o Thrift dentro de um servidor HTTP, por exemplo, como um um Tomcat e Jboss. A resposta é CLARO!. Como foi dito no início, o Thrift é facilmente adaptável a qualquer ambiente ou framework, graças à sua arquitetura. Nesse artigo, vamos explorar como seria utilizá-lo dentro do Apache Camel.

Já sabemos que o Camel funciona com um Contexto que controla uma ou mais rotas. Essas rotas são configuradas por uma instância de RouteBuilder e podem direcionar requisições para instâncias de Processor (se você viajou agora, pare de ler e veja mais sobre o Camel no site da DevMedia ou em http://camel.apache.org antes de continuar).

Para acoplarmos o Thrift ao Camel. Utilizamos uma rota que inicia um servidor HTTP em uma porta específica e para cada requisição recebida, direciona o exchange a um processor que utiliza a API do Thrift. Vamos ver a rota na Listagem 6.

Listagem 6: Rota do Camel para o Thrift


public class ThriftRouteBuilder extends RouteBuilder {
	@Autowired
	ThriftProcessor processor;
	
	@Override
	public void configure() throws Exception {
		from("jetty://http://localhost:8182/thrift").process(processor).end();
	}
}

Como sempre o Apache Camel nos surpreende por ser extremamente enxuto e direto ao ponto. O Processador do thrift está na Listagem 7.

Listagem 7: Trecho do Processador Camel para Thrift


	private tutorial.Calculator.Processor<CalculatorHandler> processor;

	@Override
	public void process(Exchange exchange) throws Exception {
		HttpMessage httpMessage = exchange.getIn(HttpMessage.class);
		InputStream inputBody = (InputStream) exchange.getIn().getBody();
		HttpServletRequest request = httpMessage.getRequest();
		HttpServletResponse response = httpMessage.getResponse();
		doPost(request, response, inputBody);
	}

	protected void doPost(HttpServletRequest request,
			HttpServletResponse response, InputStream is)
			throws ServletException, IOException {

		TTransport inTransport = null;
		TTransport outTransport = null;

		try {
			response.setContentType("application/x-thrift");
			OutputStream out = response.getOutputStream();

			TTransport transport = new TIOStreamTransport(is, out);
			inTransport = transport;
			outTransport = transport;

			TProtocol inProtocol = inProtocolFactory.getProtocol(inTransport);
			TProtocol outProtocol = outProtocolFactory
					.getProtocol(outTransport);

			processor.process(inProtocol, outProtocol);
			out.flush();
		} catch (TException te) {
			throw new ServletException(te);
		}
	}

O que esse processador do Camel faz é simplesmente receber o Stream de bytes e repassá-lo utilizando através de um transporte e protocolo ao nosso processor Calculator.Processor. A resposta é simplesmente adicionada ao ServletResponse que é então controlado pelo Apache Camel. Esse código não está pronto para produção, mas demonstra facilmente como é fácil adicionar funcionalidades baseadas no Thrift.

Bom, é isso pessoal! Conseguimos passar rapidamente pelo Apache Thrift. Não se esqueçam de visitar o código fonte do projeto disponível em https://github.com/leogsilva/CamelThrift.git. Boa programação a todos e até a próxima.