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

3

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.

Image of Datadog

The Essential Toolkit for Front-end Developers

Take a user-centric approach to front-end monitoring that evolves alongside increasingly complex frameworks and single-page applications.

Get The Kit

Top comments (0)

nextjs tutorial video

Youtube Tutorial Series 📺

So you built a Next.js app, but you need a clear view of the entire operation flow to be able to identify performance bottlenecks before you launch. But how do you get started? Get the essentials on tracing for Next.js from @nikolovlazar in this video series 👀

Watch the Youtube series

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay