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:

### 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.

* 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.

Depois, o arquivo .env, que irá conter as variaveis de ambiente que serão usadas nos fontes JS do projeto.

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.

# 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.

# 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.

---
# Componentes base da aplicação
### src/app/lib
_**GlobalDefs.js**_: Define tipos de jobs, algo como o enum em outras linguagens.

_**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.

_**Queue.js**_: Esse é, possivelmente, o fonte mais importante do projeto, onde a coisa realmente acontece.

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_, …).
)")
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.

[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.

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.


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

Confirme os dados de resposta e o STATUS 200 (OK).

No site do Mailtrap, veja que os e-mails foram enviados.

### Verificando logs dos jobs (Redis)
Acesse o cliente do Redis conforme comando na imagem. Digite o comando `keys *` para listar todas chaves gravadas.

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

### Persistência em txt

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)