API simples que gera arquivos de forma assíncrona, com Java e Spring? Aqui tem!
Tags: java, springboot, async, files
Published: 2024-04-19 17:48:45

E aí, tudo certo?

Nesse artigo rápido, decidi escrever sobre geração de arquivos assíncronos em APIs, pois vi esse tema sendo debatido no Twitter, onde alguém pediu por alguma forma bacana de contruir uma. Entendi que o tema interessou algumas pessoas e cá estamos.

Quando falamos sobre uma API responsável por gerar um arquivo (.txt, .csv, etc), as primeiras dúvidas que surgem, geralmente são: Mas a resposta da API vai ser o arquivo? Vai ser um JSON? Se for um JSON, é o front que vai gerar o arquivo? Mas espera... Então a geração vai ser síncrona? Se for, o front vai ter que ficar aguardando? E se o arquivo for gigante? Enfim, many many doubts!

Vou compartilhar aqui uma estratégia que já usei e gostei, achei simples e dinâmica. Gosto de explicá-la usando a analogia do food truck. Quando você pede um hambúrguer em um food truck, como funciona? Uma das formas é a seguinte:

1. Você faz o pedido no caixa, paga e recebe uma senha;
2. Você fica de olho no painel de senhas e quando chega a sua, vai até a bancada e retira seu hambúrguer.

Notou que isso é assíncrono? Você não fica no caixa esperando o seu hambúrguer ficar pronto para que o caixa atenda a próxima pessoa. O caixa delega a geração do hambúrguer à alguém para que ele fique livre e atenda o próximo cliente.

A geração de arquivos através de uma API que eu vou mostrar aqui funciona exatamente da mesma forma. Vamos lá! Primeiramente, eu criei uma API que recebe uma requisição contendo os parâmetros para a geração do arquivo (um intervalo de datas, outros dados relevantes, etc). A única responsabilidade dessa API é responder com um hash, um UUID, ou qualquer código de identificação única. Ou seja, essa API nunca retorna um erro de negócio, sempre retorna um hash, e esse retorno deve ser instantâneo.

Esse hash é a "senha". Assim, a API já fica livre para antender uma próxima requisição. Aí você pergunta: Tá, mas e o arquivo? Pois bem. A API, antes de retornar o hash na resposta, monta um **evento** referente à geração do arquivo e publica esse evento através do nosso querido **ApplicationEventPublisher**!


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/spkvn1ww8vj1sqnsnb01.png)

Essa publicação, feita pela API, é capturada por um listener construído especificamente para isso. Esse listener é o responsável por dar seguimento à geração do arquivo. Dessa forma, temos a geração assíncrona. Ou seja, a API monta e publica o evento, logo em seguida devolve o hash como resposta da requisição e encerra seu trabalho. Ela não tem controle sobre quando o listener irá capturar o evento e gerar o arquivo, são processamentos diferentes.


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/d2axx8atzx3a5i2zi6bd.png)

E a pergunta final: Cadê o arquivo? Onde ele vai parar e como o usuário tem acesso a ele? Aqui, eu uso uma tabela de status de geração dos arquivos. Uma tabela simples, no banco de dados, que informa em que pé está a geração requisitada. 

A chave dela é o hash gerado pela API. Antes da geração ser iniciada pelo listener, é inserido um registro referente à essa etapa com o hash e o status "PROCESSING". Caso a geração do arquivo ocorra com sucesso, é possível upar esse arquivo em um servidor, obter um link para download do mesmo e atualizar a tabela pelo hash, trocando o status para "DONE" e adicionando o link para download. Caso ocorra algum erro na geração do arquivo, é possível alterar o status para "ERROR" e adicionar uma descrição do erro que ocorreu. A granularidade desse erro fica a critério de você, querido padawan.

Por fim, uma boa pedida é ter uma segunda API para consultar o status da geração do arquivo, através do hash, e obter o link de download do mesmo, ou o erro. Não sei se essa é a melhor das melhores estratégias para gerar um arquivo assíncrono, mas veio bem a calhar e trouxe dinamismo à minha aplicação. Espero ter ajudado e até a próxima =)