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)