DEV Community

Cover image for Node.js: Utilizando filas de tarefas assíncronas com Bull+Redis
The Polymorphic Guy
The Polymorphic Guy

Posted on • Edited on • Originally published at cadutech.Medium

Node.js: Utilizando filas de tarefas assíncronas com Bull+Redis

Caro dev, a intenção desse post é mostrar passo a passo como implementar filas de tarefas assíncronas com a lib Bull e a gravação de seus logs no banco NoSQL [Redis](https://redis.io) com Node.js. **Obs**: _Será apresentada apenas uma forma de implementação, a que melhor funcionou pra mim._ (Caso já queira acessar o [repositório](https://github.com/prog-lessons/jobs-queue-redis)). # Cenário exemplo Um funcionário foi contratado e o sistema executa as tarefas: **1**) Envia e-mail do RH para ele. **2**) Envia e-mail para o lider da equipe, formalizando. **3**) Faz a persistencia dos dados do funcionário num txt. Teremos duas filas; uma para os jobs de envio de e-mails (_MailJobsQueue_) e outra para persistencia em arquivos (_PersistenceJobsQueue_). E dois “modelos” de jobs (_MailJob_ e _FilePersistenceJob_), permitindo haver **_n_** jobs de determinado modelo vinculado a uma determinada fila. O gatilho para esse processo será disparado por meio de uma web API. # Ambiente Primeiro, vamos subir o Redis num container docker. ``` docker pull redis docker images docker run --name redis -p 6379:6379 -d -t 84c5f6e03bf0 (O parâmetro após -t é o id da imagem) ``` Inicie o projeto com **npm init** no diretório desejado, aqui dei o nome background-jobs-queue-redis. Após responder as perguntas iniciais, será gerado o arquivo package.json. Adicione os seguintes pacotes ao projeto: -D significa dependencias de desenvolvimento, não obrigatórias em produção. Adicione “start”, “queue” aos scripts em package.json: ![](https://miro.medium.com/max/547/1*YBYXyo7NVayD2WvAyNldVQ.png) ### Utils: * Para testar o envio dos e-mails (lib Nodemailer), uso o serviço [Mailtrap](https://mailtrap.io/). Ao criar uma conta, serão fornecidas instruções de uso. ![](https://miro.medium.com/max/683/1*bZemAR1P3ab2u8vrw-Gw6Q.png) * Para consumir a web API, uso o [Postman](https://www.postman.com/downloads/). --- # Iniciando Abra a pasta do projeto com editor de sua preferência (aqui uso o VS Code). Crie o arquivo nodemon.json, o qual indicará ao nodemon, que os fontes JS do projeto serão executados com sucrase-node e não diretamente com o executavel do node. ![](https://miro.medium.com/max/480/1*uNBERKl_mhGa6tFSCjJOTA.png) Depois, o arquivo .env, que irá conter as variaveis de ambiente que serão usadas nos fontes JS do projeto. ![](https://miro.medium.com/max/474/1*J2QqpuY05JgSgow2-gRQFA.png) Estrutura de arquivos do projeto ![alt text](https://miro.medium.com/max/332/1*wNNL8RXOGuur0MaL4SmcEA.png "Estrutura de arquivos do projeto") ### src/config Esses fontes apenas exportam objetos literais com propriedades de configuração para envio dos e-mails e conexão com o Redis. ![](https://miro.medium.com/max/601/1*ijBkXNLZVSYN1D3weVml_A.png) # Definindo as filas ### src/app/queues Aqui, cada fonte JS corresponde a uma fila da aplicação. Apenas exportam objetos literais com o nome da fila e opções de configuração. Index.js exporta um objeto literal, sendo suas propriedades Q1, Q2, [objetos aninhados](https://www.tutorialspoint.com/how-to-access-nested-json-objects-in-javascript) contendo as propriedades _[name]_, _[options]_ do fonte associado. ![](https://miro.medium.com/max/608/1*MmvnZRLcaihRprCzdbA5HA.png) # Definindo modelos de jobs ### src/app/job-models Aqui, cada fonte JS descreve um “modelo” de job, o qual é vinculado à uma fila. A função _handle()_ será passada como argumento ([callback function](https://developer.mozilla.org/en-US/docs/Glossary/Callback_function)) para o método _process()_ do Bull (interface _Queue_), que apenas registra a função que deve ser executada quando entrarem novos jobs em determinada fila. No caso de _MailJob.js_, _handle()_ foi declarada como assíncrona, pois nao sabemos quanto tempo o servidor de e-mails levará pra responder, concluindo a tarefa (linha 11), então, enquanto isso, libera a aplicação pra continuar executando. Significa que a função _handle()_ fica suspensa/pausada, ou seja, a execução volta para a próxima linha de onde _handle()_ foi chamada. Quando o método _sendMail()_ estiver concluído, o fluxo de execução volta imediatamente para dentro de _handle()_, na próxima linha apoś **await** (linha 12). O conteúdo do parâmetro _data_ é passado pelo Bull quando _handle()_ for invocada. Repare que _data_ está entre {}, bem como a variavel _emailData_. Esse é o conceito de [desestruturação](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment) do JS. ![alt text](https://miro.medium.com/max/700/1*HmJJ6-HSDv-Rx2mBZbYQzw.png "MailJob.js") --- # Componentes base da aplicação ### src/app/lib _**GlobalDefs.js**_: Define tipos de jobs, algo como o enum em outras linguagens. ![](https://miro.medium.com/max/590/1*MGfcP4JDWQMv_1WWHJ8n5A.png) _**Mail.js**_: Exporta um objeto da classe _Mail_ (lib nodemailer) retornado por _createTransport()_, que terá seu método _sendMail()_ invocado em _src/app/job-models/MailJob.js_:11. ![](https://miro.medium.com/max/593/1*L5Al-v0csZAkOTgaOQAugw.png) _**Queue.js**_: Esse é, possivelmente, o fonte mais importante do projeto, onde a coisa realmente acontece. ![](https://miro.medium.com/max/700/1*glYK-Kg3Wb6L1RLQ4GVoIA.png) Na linha 4 é importado um objeto literal (_queues_) contendo todas as filas e na linha 5 (_jobs_) com todos os modelos de jobs. Na linha 7, _[Object.values](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/values)(queues)_ retorna um array de objetos, onde cada elemento corresponde a (_Q1_, _Q2_, …). ![alt text](https://miro.medium.com/max/700/1*KTepdmMtKLrn8IBetQTVNw.png "console.table(Object.Values(queues))") O método _[map()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map)_ de _[Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array)_ em JS tem como parâmetro uma callback function, que é executada sobre cada elemento do array, retornando um novo array. Na linha 7, _map()_ recebe uma [expressão lambda com arrow function](https://www.vinta.com.br/blog/2015/javascript-lambda-and-arrow-functions/) como argumento, adicionando pra cada elemento do array, uma nova propriedade _[bull]_, que contém uma instancia(objeto) de _Queue_. Esse objeto controlará a adição de jobs às filas e seus processamentos. A opção **stalledInterval: 0** é usada neste exemplo, pois nenhum job manterá a CPU muito ocupada ([Bull](https://optimalbits.github.io/bull/), seção “Stalled jobs”). _**addJob(**_*type, data*_**)**_: Como está nos comentários, basicamente busca o modelo de job (_job_) em _AllJobs_ pelo tipo (_type_) e, uma vez localizado, busca em _AllQueues_ a fila (_q_), tal que _q.bull.name_ === _job.queue_. Obtida q, adiciona-lhe os dados referentes ao job (_data_) e as opções de execução para esse job (_job.options_). _**process()**_: Percorre todos os modelos de jobs e, para cada um, identifica qual a fila vinculada e faz o mapeamento entre ela e a função que deve ser executada para seus jobs. --- # REST API ### src/app/controllers Aqui ficam os controladores das APIs. Esses fontes contém funções/manipuladores (handlers) dos dados enviados pela requisição HTTP e devolvem o resultado (geralmente um JSON). Aqui poderíamos considerar o [endpoint](https://stackoverflow.com/questions/29734121/what-is-endpoint-in-rest-architecture) `http://localhost:8080/users` uma [Web API](https://en.wikipedia.org/wiki/Web_API). _**UserController.js**_: Exporta a função _store(req, res)_ que irá tratar as requisições referentes ao recurso¹ _**users**_. [_req.body_] contém os campos/valores que foram enviados e [_res_] é para devolver a resposta ao cliente. ![](https://miro.medium.com/max/700/1*rifRVQr_XX2vB687oY14xQ.png) [1] “Toda aplicação gerencia algumas informações. Uma aplicação de um E-commerce, por exemplo, gerencia seus produtos, clientes, vendas, etc. Essas coisas que uma aplicação gerencia são chamadas de **recursos** no modelo REST.” ([REST: Princípios e boas práticas](https://blog.caelum.com.br/rest-principios-e-boas-praticas/amp/), “Identificação dos Recursos”) --- #Pontos de entrada A aplicação será executada a partir de 2 fontes: _server.js_ e _queue.js_. É interessante essa implementação que separa a execução em 2 processos. Suponha que o processo que adiciona jobs às filas tenha algum problema em algum ponto e aborte. Voce poderá solucionar o problema e reinicia-lo, enquanto o processo que efetivamente executa os jobs continua no ar. ![](https://miro.medium.com/max/607/1*OsCt5h5uIcyz5LBzjVKCSg.png) A linha 6 é necessária para que aplicação possa trabalhar com dados enviados com método POST (ou PUT) no formato JSON. Na linha 8, _store()_ tratará as requisições HTTP com método POST para a rota '/users'. Na linha 10 é onde o servidor web é levantado, na porta passada como argumento para _listen()_. --- # Executando Inicie os 2 scripts. ![](https://miro.medium.com/max/700/1*aGyLecrdb5nbTetQEWSe5Q.png) ![](https://miro.medium.com/max/700/1*ec6cp1jQMl0poTOik-RBjw.png) Abra o Postman (ou app de preferencia) e envie a requisição HTTP (método POST) com os dados do corpo da mensagem no formato JSON para a URL `http://localhost:8080/users`. ![](https://miro.medium.com/max/674/1*kiDUrpSVcIEFA9rJwLF1yQ.png) Confirme os dados de resposta e o STATUS 200 (OK). ![](https://miro.medium.com/max/700/1*5pZLawq13dXq7jE3QLewvA.png) No site do Mailtrap, veja que os e-mails foram enviados. ![](https://miro.medium.com/max/700/1*HS_OWVBlRAaDaXh94Wfxmg.png) ### Verificando logs dos jobs (Redis) Acesse o cliente do Redis conforme comando na imagem. Digite o comando `keys *` para listar todas chaves gravadas. ![](https://miro.medium.com/max/444/1*aXOFPdtR4Omt6v2Z_QcUYw.png) Foram completados com sucesso os 2 jobs da fila de envio de e-mails e o job de persistencia em arquivo texto. Para maiores detalhes sobre algum job especifico digite comando `HGETALL `. ![](https://miro.medium.com/max/700/1*7oPOVB__dmaDhMRk8Nl2ew.png) ### Persistência em txt ![](https://miro.medium.com/max/362/1*4TRbFHnj6GBkZo65TPA_gw.png)

That’s all folks! Espero que possa ajudar alguem, de alguma forma. Caso tenha sido útil, ajude compartilhando. Até a próxima. ;-) Contato.

Top comments (0)