Node.js: Utilizando filas de tarefas assíncronas com Bull+Redis
Tags: node, javascript, queues, jobs
Published: 2020-12-05 12:07:32

<center>
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:

<table>
  <tr>
    <td>npm i <a href="https://www.npmjs.com/package/bull">bull </a><a href= "https://www.npmjs.com/package/dotenv">dotenv </a><a href="https://www.npmjs.com/package/express">express </a> <a href="https://www.npmjs.com/package/nodemailer">nodemailer </a> 
<a href="https://www.npmjs.com/package/password-generator">password-generator</a> <a href="">redis</a> 
    </td>
  </tr>
  <tr>
   <td>npm i nodemon sucrase -D</td>
  </tr>
</table>

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

![](https://miro.medium.com/max/700/1*7oPOVB__dmaDhMRk8Nl2ew.png)

### Persistência em txt

![](https://miro.medium.com/max/362/1*4TRbFHnj6GBkZo65TPA_gw.png)
</center>
---

That’s all folks! Espero que possa ajudar alguem, de alguma forma. Caso tenha sido útil, ajude compartilhando. Até a próxima. ;-) [Contato](https://about.me/cadutech).