Recentemente, em um projeto, surgiu uma questão de performance para a exportação de um arquivo Excel: o cenário era o de um endpoint que tinha de fazer uma pesquisa na base de dados e retornar um grande volume de dados, para que posteriormente fosse exportado.

 

Esta mesma query, porém em um outro endpoint, retornava os dados para que o front-end pudesse mostrá-los ao utilizador, mas a questão da performance foi resolvida com paginação. Porém, o cenário de geração do arquivo não foi bem executado, pois o requisito era exportar tudo o que estava disponível, consoante o filtro aplicado.

 

Bem, então fazendo alguns testes percebi que o utilizador ficaria muito tempo aguardando todo o processo. Isso não seria bom, além de que ele poderia sair da página, ou até mesmo desistir do relatório. Foi aí que comecei a pensar em soluções para resolver este problema.

 

Outra questão muito comum em diversos projetos é ter um budget limitado. Logo comecei a pensar em utilizar uma fila: desta forma, o utilizador faria o pedido e não ficaria trancado na página aguardando.

 

Atualmente temos muitas opções de serviços para filas, como RabbitMQ, Kafka, entre outros, mas são ferramentas muito poderosas, que exigem implementações por vezes complexas, e para este cenário não seria viável.

 

Então lembrei que o .NET possui de forma nativa um serviço chamado “BackgroundService”, que implementa a interface “IHostedService”, e isso abre uma série de vantagens. Não preciso me preocupar com servidores externos, implementações complexas, e o melhor de tudo é que isso tudo corre no mesmo app service da minha API, de forma independente.

  

Vamos praticar...

Como sempre, faremos um exemplo real!

  1. Vamos criar um projeto de exemplo. Para tal, selecione a opção “Create a new project”.

    Background Service Queue 1
  2. Dê um nome para a solução.

    Background Service Queue 2
  3. Selecione a versão do .NET.

    Background Service Queue 3
  4. Criaremos as seguintes pastas lógicas:

    Background Service Queue 4
  5. Instale o pacote Bogus: "Install-Package bogus".

  6. Vamos criar a classe Customer e Invoice. Em Customer, estas são as propriedades:

    Background Service Queue 5

  7. Crie a classe Invoice.

    Background Service Queue 6

    Note que as propriedades são somente de leitura. Esta é uma boa prática, portanto para conseguir criar uma Invoice vamos precisar de um construtor, como este:

    Background Service Queue 7
  8. A classe Customer segue o mesmo modelo, pois seus setters são privados. Sendo assim vamos implementar um construtor e também o método que irá nos retornar uma listagem de Customers.

    Background Service Queue 8

    Perceba que Customer é criado instanciando a classe e passando para o construtor o nome e se está ativo. Note que o nome é representado por um texto que a cada iteração incrementa um número. Estar ativo vai depender se o número da iteração (i) é par ou ímpar.

    As Invoices funcionam da mesma forma.

    Background Service Queue 9

    Perceba que eu estou adicionando 5 invoices para cada Customer criado. Para isso, criaremos o método AddInvoice na classe Customer.

    Background Service Queue 10
  9. Vamos criar agora a interface responsável por enfileirar ou remover da fila nossos objetos. O nome da interface será “IBackgroundQueue”.

    Background Service Queue 11
  10. Criaremos então a classe concreta que implementará esta interface.

    Background Service Queue 12
  11. Crie uma outra interface. Chamaremos ela de “ICustomerPublisher”. Esta interface deve possuir um método de publicação.

    Background Service Queue 13
  12. Na sequência basta implementar a interface em uma classe chamada “CustomerPublisher”, dentro da pasta “Services”.

    Background Service Queue 14
  13. Grande parte do trabalho já está feito. Temos então:
              - IBackgroundQueue: interface para incluir e excluir da fila;
              - ICustomerPublisher: interface que trata da publicação na fila;
              - BackgroundQueue e CustomerPublisher: as implementações concretas das                  interfaces;
              - Models Customer e Invoice.

  14. Próximo passo é criar nosso worker, que vai ser o responsável por executar o processo da fila. Este processo ocorre em paralelo à API, sendo assim um serviço hospedado na mesma aplicação, não interferindo de fato na API.
    Criaremos uma classe chamada “CustomerBackgroundWorker”, que por sua vez deve herdar a classe abstrata “BackgroundService”. Esta última é a responsável por fornecer as funcionalidades que vamos precisar implementar na classe – exemplo é o método “ExecuteAsync”, que é uma sobrecarga obrigatória na nossa classe.

    Background Service Queue 15

    Importante salientar que precisamos injetar nosso serviço de fila. Também incluo aqui o ILogger, para que possamos visualizar o resultado na consola.

    A interface “IServiceScopeFactory” é muito importante – é ela que vai nos permitir utilizar o serviço de publicação. Normalmente iríamos utilizar o processo comum de injeção de dependência, quando a aplicação sobe, porém a construção da instância, quando utilizamos Hosted Services, é diferente. Há conflito na criação de escopo, portando a forma correta é fazendo isso dentro deste serviço com a interface “IServiceScopeFactory”.

  15. Agora implementamos o método que colocar nosso objeto na fila, ou tirar dela, por meio da publicação.

    Background Service Queue 16
  16. Não esqueça de adicionar nossas funcionalidades ao escopo de serviços dentro da classe “Program.cs”.

    Background Service Queue 17
  17. Finalmente, agora criaremos a Controller “CustomersController”. Vamos provocar um enfileiramento de clientes.

    Background Service Queue 18
  18. Agora, para testar, iremos via swagger clicar inúmeras vezes para criar as requests e observar os logs na consola. Podemos concluir que enviamos de forma assíncrona os clientes que posteriormente serão processados, e o consumer, que é quem faz a requisição, não ficará trancado esperando resposta da API.

    No meu caso, eu já havia clicado mais de 10 vezes, já tinha o retorno da API e o processo de publicação ainda ocorria, conforme imagem.

    Background Service Queue 19

 

Conclusão

Este é um ótimo recurso nativo e que pode nos ajudar imensamente quando queremos utilizar fila sem complexidade e para temas específicos. Um ótimo exemplo seria criar um serviço de envio de e-mails para uma determinada regra de negócio, por exemplo e-mail de processamento de pagamentos aos clientes.



Links úteis:
Partilha este artigo