Neste artigo, criaremos uma lista de streaming de tweets com base em uma consulta de pesquisa inserida pelo usuário. Os tweets serão buscados usando a API de streaming do Twitter, armazenados em uma lista Redis e atualizados no front-end usando Socket.io. Usaremos principalmente o Redis como uma camada de cache para buscar tweets.
Introdução
Aqui está uma breve descrição das tecnologias que usaremos:
Redis
O Redis é um armazenamento de estrutura de dados na memória de código aberto (licenciado BSD), usado como banco de dados, cache e agente de mensagens. Ele suporta estruturas de dados como strings, hashes, listas, conjuntos, conjuntos ordenados com consultas de intervalo, bitmaps, hiperloglogs e índices geoespaciais com consultas de raio.
Node.js
O Node.js é uma plataforma criada no tempo de execução JavaScript do Chrome para criar facilmente aplicativos de rede rápidos e escalonáveis. O Node.js usa um modelo de E/S sem bloqueio e orientado a eventos que o torna leve e eficiente e, portanto, perfeito para aplicativos em tempo real com uso intenso de dados executados em dispositivos distribuídos.
Express.js
Express.js é uma estrutura Node.js. Você pode criar o servidor e o código do lado do servidor para um aplicativo como a maioria das outras linguagens da Web, mas usando JavaScript.
Socket.IO
Socket.IO é uma biblioteca JavaScript para aplicações web em tempo real. Ele permite a comunicação bidirecional em tempo real entre clientes e servidores da Web. Ele tem duas partes:uma biblioteca do lado do cliente que é executada no navegador e uma biblioteca do lado do servidor para Node.js. Ambos os componentes têm APIs quase idênticas.
Heroku
Heroku é uma plataforma em nuvem que permite que as empresas criem, entreguem, monitorem e dimensionem aplicativos — é a maneira mais rápida de passar da ideia ao URL, ignorando todas essas dores de cabeça de infraestrutura.
Este artigo pressupõe que você já tenha o Redis, Node.js e o Heroku Toolbelt instalados em sua máquina.
Configuração
- Faça o download do código no seguinte repositório: https://github.com/Scalegrid/code-samples/tree/sg-redis-node-socket-twitter-search/node-socket-redis-twitter-hashtags
- Execute npm install para instalar os componentes necessários
- Finalmente, você pode iniciar o servidor do nó fazendo “node index.js”. Você também pode executar “nodemon” que também observa as alterações de arquivos.
Você também pode acessar uma versão hospedada deste aplicativo aqui: https://node-socket-redis-stream-tweet.herokuapp.com/
O Processo
Aqui está uma breve descrição do processo que usaremos para construir o aplicativo de demonstração:
1. Começaremos aceitando uma consulta de pesquisa do usuário. A consulta pode ser menções do Twitter, hashtags ou qualquer texto de pesquisa aleatório.
2. Assim que tivermos a consulta de pesquisa, a enviaremos para a API de streaming do Twitter para buscar os tweets. Como é um stream, estaremos ouvindo quando os tweets forem enviados pela API.
3. Assim que um tweet for recuperado, vamos armazená-lo em uma lista Redis e transmiti-lo para o front-end.
O que são listas do Redis?
As listas Redis são implementadas por meio de listas vinculadas. Isso significa que, mesmo que você tenha milhões de elementos dentro de uma lista, a operação de adicionar um novo elemento no início ou no final da lista é realizada em tempo constante. A velocidade de adicionar um novo elemento com o comando LPUSH ao cabeçalho de uma lista com dez elementos é a mesma de adicionar um elemento ao cabeçalho de uma lista com 10 milhões de elementos.
Em nosso aplicativo, estaremos armazenando os tweets recebidos via API em uma lista chamada “tweets”. Usaremos LPUSH para enviar o tweet recém-recebido para a lista, cortá-lo usando LTRIM, que restringe a quantidade de espaço em disco usado (já que escrever um fluxo pode ocupar muito espaço), buscar o tweet mais recente usando LRANGE e transmiti-lo para o front-end onde será anexado à lista de streaming.
O que é LPUSH, LTRIM e LRANGE?
Esses são um conjunto de comandos do Redis usados para adicionar dados a uma lista. Aqui está uma breve descrição:
LPUSH
Insira todos os valores especificados no início da lista armazenada na chave. Se a chave não existir, ela será criada como uma lista vazia antes de executar as operações de push. Quando a chave contém um valor que não é uma lista, um erro é retornado.
redis> LPUSH mylist "world" (integer) 1 redis> LPUSH mylist "hello" (integer) 2 redis> LRANGE mylist 0 -1 1) "hello" 2) "world"
LTRIM
Apare uma lista existente para que ela contenha apenas o intervalo de elementos especificado. Tanto o start quanto o stop são índices baseados em zero, onde 0 é o primeiro elemento da lista (o cabeçalho), 1 o próximo elemento e assim por diante.
redis> RPUSH mylist "one" (integer) 1 redis> RPUSH mylist "two" (integer) 2 redis> RPUSH mylist "three" (integer) 3 redis> LTRIM mylist 1 -1 "OK" redis> LRANGE mylist 0 -1 1) "two" 2) "three"
LRANGE
Retorna os elementos especificados da lista armazenada na chave. Os deslocamentos iniciais e finais são índices baseados em zero, com 0 sendo o primeiro elemento da lista (o cabeçalho da lista), 1 sendo o próximo e assim por diante.
Esses deslocamentos também podem ser números negativos indicando posições no final da lista. Por exemplo, -1 é o último elemento da lista, -2 o penúltimo e assim por diante.
redis> RPUSH mylist "one" (integer) 1 redis> RPUSH mylist "two" (integer) 2 redis> RPUSH mylist "three" (integer) 3 redis> LRANGE mylist 0 0 1) "one" redis> LRANGE mylist -3 2 1) "one" 2) "two" 3) "three"
Criando o aplicativo
Nossa demonstração requer um front-end e um back-end. Nosso front-end é uma caixa de texto bem simples com um botão que será usado para iniciar o fluxo.
$('body').on('click', '.btn-search', function() { $('#tweets_area').empty(); $(this).text('Streaming...').attr('disabled', true); $.ajax({ url: '/search', type: 'POST', data: { val: $.trim($('.search-txt').val()) } }); });
Precisamos de uma função auxiliar para construir uma caixa de tweets assim que recebermos o tweet do nosso back-end:
var _buildTweetBox = function(status) { var html = ''; html += '<div class="media tweet-single">'; html += ' <div class="media-left">'; html += ' <a href="https://twitter.com/' + status.user.screen_name + '" target="_blank" title="' + status.user.name + '">'; html += ' <img class="media-object" src="' + status.user.profile_image_url_https + '" alt="' + status.user.name + '" />'; html += ' </a>'; html += ' </div>'; html += ' <div class="media-body">'; html += ' <h5 class="media-heading"><a href="https://twitter.com/' + status.user.screen_name + '" target="_blank">' + status.user.screen_name + '</a></h5>'; html += '<p class="tweet-body" title="View full tweet" data-link="https://twitter.com/' + status.user.screen_name + '/status/' + status.id_str + '">' + status.text + '</p>'; html += ' </div>'; html += '</div>'; $('#tweets_area').prepend(html); $('#tweets_area').find('.tweet-single').first().fadeIn('slow'); };
Também precisamos de um ouvinte para interromper a transmissão e evitar adicionar mais tweets à lista de transmissão:
socket.on('stream:destroy', function(status) { $('.btn-search').text('Start streaming').removeAttr('disabled'); $('.alert-warning').fadeIn('slow'); setTimeout(function() { $('.alert-warning').fadeOut('slow'); }, STREAM_END_TIMEOUT * 1000); });
Vamos passar para o lado do back-end e começar a escrever nossa API /search.
/** * API - Search */ app.post('/search', function(req, res, next) { _searchTwitter(req.body.val); res.send({ status: 'OK' }); }); /** * Stream data from Twitter for input text * * 1. Use the Twitter streaming API to track a specific value entered by the user * 2. Once we have the data from Twitter, add it to a Redis list using LPUSH * 3. After adding to list, limit the list using LTRIM so the stream doesn't overflow the disk * 4. Use LRANGE to fetch the latest tweet and emit it to the front-end using Socket.io * * @param {String} val Query String * @return */ var _searchTwitter = function(val) { twit.stream('statuses/filter', {track: val}, function(stream) { stream.on('data', function(data) { client.lpush('tweets', JSON.stringify(data), function() { client.ltrim('tweets', 0, TWEETS_TO_KEEP, function() { client.lrange('tweets', 0, 1, function(err, tweetListStr) { io.emit('savedTweetToRedis', JSON.parse(tweetListStr[0])); }); }); }); }); stream.on('destroy', function(response) { io.emit('stream:destroy'); }); stream.on('end', function(response) { io.emit('stream:destroy'); }); setTimeout(stream.destroy, STREAM_TIMEOUT * 1000); }); }
O código acima contém o núcleo do nosso back-end. Depois que uma solicitação é recebida em /search, iniciamos o stream usando a API de streaming do Twitter que retorna um objeto stream.
twit.stream('statuses/filter', {track: val}, function(stream) {});
Podemos ouvir o objeto stream para uma chave chamada “data” que nos enviará um novo tweet quando disponível.
stream.on('data', function(data) {});
O objeto “data” contém o tweet JSON que pode ser algo assim (parte da resposta foi omitida):
{ "created_at": "Wed Jul 26 08:01:56 +0000 2017", "id": 890119982641803300, "id_str": "890119982641803264", "text": "RT @FoxNews: Jim DeMint: \"There is no better man than Jeff Sessions, and no greater supporter...of [President #Trump's] agenda.\"… ", "source": "<a href=\"http://twitter.com/download/android\" rel=\"nofollow\">Twitter for Android</a>", "truncated": false, "in_reply_to_status_id": null, "in_reply_to_status_id_str": null, "in_reply_to_user_id": null, "in_reply_to_user_id_str": null, "in_reply_to_screen_name": null, "user": { "id": 4833141138, "id_str": "4833141138", "name": "randy joe davis", "screen_name": "randyjoedavis1", "location": null, "url": null, "description": "Conservative Patriot, retired military, retired DOD civilian. cattle farmer, horseman, adventurer. Lovin Life ! GO HOGS !!", "protected": false, "verified": false, "followers_count": 226, "friends_count": 346, "listed_count": 0, "favourites_count": 3751, "statuses_count": 1339, "created_at": "Sat Jan 30 03:39:16 +0000 2016", "utc_offset": null, "time_zone": null, "geo_enabled": false, "lang": "en", "contributors_enabled": false, "is_translator": false, "profile_background_color": "F5F8FA", "profile_background_image_url": "", "profile_background_image_url_https": "", "profile_background_tile": false, "profile_link_color": "1DA1F2", "profile_sidebar_border_color": "C0DEED", "profile_sidebar_fill_color": "DDEEF6", "profile_text_color": "333333", "profile_use_background_image": true, "profile_image_url": "http://pbs.twimg.com/profile_images/883522005210943488/rqyyXlEX_normal.jpg", "profile_image_url_https": "https://pbs.twimg.com/profile_images/883522005210943488/rqyyXlEX_normal.jpg", "default_profile": true, "default_profile_image": false, "following": null, "follow_request_sent": null, "notifications": null } }
Armazenamos essa resposta em uma lista Redis chamada “tweets” usando LPUSH:
client.lpush('tweets', JSON.stringify(data), function() {});
Depois que o tweet for salvo, cortamos a lista usando LTRIM para manter um número máximo de tweets (para que nosso espaço em disco não fique cheio):
client.ltrim('tweets', 0, TWEETS_TO_KEEP, function() {});
Depois de cortar a lista, buscamos o tweet mais recente usando LRANGE e o emitimos para o front-end:
client.lrange('tweets', 0, 1, function(err, tweetListStr) { io.emit('savedTweetToRedis', JSON.parse(tweetListStr[0])); });
Como este é um aplicativo de demonstração, também precisamos destruir manualmente o fluxo após um tempo específico para que ele não continue gravando no disco:
stream.on('end', function(response) { io.emit('stream:destroy'); }); setTimeout(stream.destroy, STREAM_TIMEOUT * 1000);
E pronto! Inicie o servidor usando npm start e aproveite a experiência de streaming.
Uma demonstração do aplicativo está disponível aqui: https://node-socket-redis-stream-tweet.herokuapp.com/
Para implantar este aplicativo no Heroku, confira seus documentos:https://devcenter.heroku.com/categories/deployment
Todo o código-fonte também está disponível no GitHub para você bifurcar e trabalhar: https://github.com/Scalegrid/code-samples/tree/sg-redis-node-socket-twitter-search/node-socket-redis -twitter-hashtags
Como sempre, se você criar algo incrível, envie-nos um tweet sobre isso @scalegridio.
Se precisar de ajuda com gerenciamento e hospedagem para Redis™*, entre em contato conosco em [email protected] para obter mais informações.