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.
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.
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.