Bitbot: um robô que converte valores em Bitcoins via Twitter

O Desafio

Quando você está aprendendo alguma linguagem nova ou utilizando um novo serviço, nada melhor do que criar um projeto para ver como as coisas se comportam em situações reais, não é mesmo?

Comigo não é diferente e por isso mesmo resolvi criar um pequeno projeto para conhecer o Jelastic mais a fundo e ver quais dúvidas surgiam durante o processo de desenvolvimento e deploy em produção.

De quebra eu queria que ele tivesse alguma relação com Bitcoin. Por quê? Simplesmente porque sou entusiasta da moeda digital e ainda não havia tido a oportunidade fazer alguma coisa que envolvesse o tema. Além disso, não sei como são vocês, mas eu me desmotivo facilmente quando descubro que alguém já fez algo que estava pensando em fazer. Quando tive a ideia e percebi que ninguém havia tentado isso antes, fiquei animado, pois poderia atingir todos os objetivos com os quais eu me propunha:

  • conhecer melhor o Jelastic;
  • envolver Bitcoin;
  • treinar meu Ruby.

Foi assim que surgiu o Bitbot, um robo de Twitter para obter cotações de Bitcoin em tempo real. Pelo fato de todas as contas de Twitter com nomes legais já estarem registradas, acabamos usando o nome (não tão bacana...) de Bitconversions. Pelo menos é curto e passa diretamente a ideia... Você pode acessar a conta de Twitter do robô em https://twitter.com/bitconversions

Funcionamento e uso do robô

A ideia era simples: para obter o valor de uma determinada quantia de bitcoins em uma determinada moeda, bastaria enviar um tuite mensionando @bitconversions com a quantia de bitcoins e a hashtag com o código internacional da moeda (ISO 4217) em que se deseja a cotação conforme o exemplo abaixo:

@bitconversions 1.533 #BRL

Neste exemplo, o bitbot nos responderá com o resultado da conversão de 1.533 bitcoins em reais (BRL é o código ISO 4217 para o Real). Na verdade, trata-se de um exemplo do mínimo necessário para que o robô responda. Através do uso de expressões regulares, reconhecemos se o twitter possui esses três elementos necessários. Contudo, é possível enviar uma pergunta em qualquer idioma:

@bitconversions quanto é 1.533 bitcoins em #BRL?

A resposta, no entanto, virá sempre em inglês:

É possível enviar os valores usando tanto vírgula ou ponto como separador decimal. A hashtag com o código também pode estar em mínúsculo (#brl) apesar do padrão ISO exigir letras maiúsculas.

Os Componentes

API do Twitter

O robô utiliza a API de streaming do Twitter, para obter as menções enviadas para ele em tempo real. Ao contrário da API REST, esta parece permitir que você abra uma conexão websockets com o servidor do Twitter, de forma que eles possam redirecionar a timeline do usuário para o programa que você está desenvolvendo. Trata-se de uma abordagem inteligente que facilita o desenvolvimento, pois desta forma é possível consumir a API como um cliente. Outras abordagens, como a do Instagram, forçam você a praticamente contruir uma outra API para poder consumir dados em tempo real.

API do BitcoinAverage

Para que a ideia funcionasse, era preciso obter as cotações de Bitcoin de algum lugar. A solução foi a utilizar a API do BitcoinAverage. O propósito do BitcoinAverage é muito simples: ele consolida dados de diversas exchanges pelo mundo, exibindo um valor médio que leva em conta a ponderação do volume negociado por cada um deles. Como uma moeda internacional, nada melhor do que uma cotação internacional. Isso dificulta muito a formação de cartéis e distorções artificiais de preço. Apesar disso, é possível obter uma cotação que leva em conta apenas o preço negociado em um determinado país. No meu caso, optei não utilizar essa opção, já que havia apenas um exchange brasileiro sendo computado.

A API do BitcoinAverage é outro exemplo de uma API bem feita: simples, intuitiva e funcional. Além do enorme trabalho que ela realiza no backend, consolidando cotações do mundo inteiro, ela fornece um recurso importante que é a conversão de valores para diversas moedas. De início, pensei que precisaria utilizar uma terceira API para converter as cotações em uma determinada moeda (talvez dólar) para todas as outras. Contudo, isso não foi necessário e o resultado saiu "melhor do que a encomenda".

Ruby

Como foi dito anteriormente, o programa foi todo escrito em Ruby. Felizmente, já existem bibliotecas para facilitar a autenticação com o OAUTH e o uso da API de streaming do Twitter, o que facilita bastante o trabalho.

Pelo fato de se tratar de um streaming de dados, que chegam um após o outro, não era possível programar de forma síncrona, esperando que cada pedido fosse resolvido para somente em seguida, desbloquear o próximo caso. Desta forma, corríamos o risco de perder algum dado no meio do caminho. Por conta disso, foi utilizada a EventMachine para programar de forma assíncrona com Ruby.

Por último, utilizamos o Rack apenas para criar uma interface web simples que nos permitisse visualizar de maneira fácil, o número de conversões efetuadas.

Redis

No começo de tudo, pensei em não utilizar base de dados alguma e armazenar tudo em memória. Como desejava que as cotações fossem atualizadas, não haveria a necessidade de guardar nada em banco de dados, sendo que a API do BitcoinAverage atualiza os dados a cada 10 segundos. Mas a necessidade de trabalhar com múltiplas threads fez com que alguns valores não estivessem acessíveis por elas, especialmente o número de conversões que era atualizado dentro de cada thread que era iniciada pelo callback da EventMachine.

O Redis foi a solução encontrada para armazenar os valores das cotações em memória de forma que todas as threads acessassem os mesmos valores: cotações, número de conversões e timestamp.

O Resultado

A ideia inicial era fazer algo simples, mas durante o caminho surgiram alguns desafios. O primeiro deles foi justamente programar assincronamente. Quando palestro, sempre digo que apesar de "performático" esse paradigma de programação representa uma dificuldade a mais no desenvolvimento, uma vez que "nosso cérebro pensa sincronamente" em instruções que são executadas uma após a outra.

Além disso, como a EventMachine abre uma nova thread a cada callback, além daquelas que já trabalhamos no código, me deparei com resultados que não acessavam a mesma variável, exibindo valores diferentes daqueles esperados e com o encerramento súbito do programa, que morria sem emitir erro algum. Descobrir o que causava isso foi talvez tenha tomado mais tempo.

Lições aprendidas

Foi muito bom colocar esse projeto em prática, pois desta forma pude conhecer o Jelastic mais a fundo. Ok, eu trabalho na Locaweb, mas posso dizer que se trata realmente de uma plataforma bastante flexível e poderosa.

Hoje, o programa roda gastando aproximadamente 29 reais por mês, utilizando apenas 2 cloudlets: 1 para a aplicação e outro para o Redis. Ou seja, menos de 200Mhz de processamento e 128Mb de memória, podendo escalar esses recursos sempre que necessário.

Outro aprendizado interessante é que o Jelastic redireciona a saída padrão (STDOUT) para o arquivo errors.log do Nginx, de forma que você possa ver qualquer print que tenha feito em seu programa com, por exemplo, o puts foo, no caso do Ruby. Faz sentido, pois qualquer erro normalmente é enviado para essa saída.

Outra feature legal é a integração com o Git de forma que a aplicação é atualizada automaticamente, sempre que o repositório onde o código fonte está hospedado é atualizado. Falando nisso, você pode acessar o código fonte do Bitbot no Github: https://github.com/kemelzaidan/bitcoinconversions

Saiba mais

Se você quiser saber mais sobre esse projeto, poderá assistir a palestra que darei junto com o Lucas Uyezu, no dia 06/02/2015 às 17h30 no palco Júpiter durante a Campus Party.

O Lucas Uyezu é desenvolvedor aqui na Locaweb e foi meu mentor de Ruby, tirando as dúvidas que surgiam pelo caminho e contribuindo muito com o resultado. Pode-se dizer que sem ele, esse projeto não tinha saído. Valeu!

Não deixe de testar o nosso robô em https://twitter.com/bitconversions e de dizer o que achou do projeto. Esperamos você no palco de Desenvolvimento da Campus Party. Até lá! :-)