Background jobs com Solid Queue
Se a resposta HTTP só pode voltar depois que um e-mail terminar de ser enviado, o usuário está esperando por algo que não devia bloquear a requisição.
Enviar e-mail, chamar uma API externa lenta, gerar um relatório pesado — nada disso precisa (nem deveria) rodar dentro do ciclo de um request. Se acontece ali, o usuário fica esperando a resposta até esse trabalho terminar, e se o serviço externo estiver lento ou fora do ar, o request inteiro trava ou estoura timeout por causa de algo que não tinha relação direta com o que ele pediu.
deliver_now vs. deliver_later — a diferença que a lição de service objects não explicou
O service FinalizarPedido de lições anteriores já usava PedidoMailer.confirmacao(@pedido).deliver_later sem detalhar o porquê. Compare:
PedidoMailer.confirmacao(@pedido).deliver_now # espera o servidor de e-mail responder antes de continuarPedidoMailer.confirmacao(@pedido).deliver_later # devolve na hora; o envio roda depois, fora do requestdeliver_later não envia nada na hora — ele enfileira um job via Active Job. Quem realmente processa essa fila depois é o adapter configurado no projeto; em apps Rails mais novos, o padrão passou a ser o Solid Queue.
O que o Solid Queue resolve
Solid Queue grava os jobs pendentes numa tabela do próprio banco de dados relacional da aplicação, em vez de depender de um serviço externo à parte (como Redis, que ferramentas mais antigas como Sidekiq exigem) só pra manter a fila. Um processo separado (o worker) lê essa tabela e executa os jobs pendentes, na ordem e com as tentativas configuradas.
Um job próprio, além de mailer
class NotificarEstoqueBaixoJob < ApplicationJob
queue_as :default
retry_on StandardError, wait: :polynomially_longer, attempts: 5
def perform(produto)
return unless produto.estoque < 10
EstoqueMailer.alerta_baixo(produto).deliver_now
end
endNotificarEstoqueBaixoJob.perform_later(produto)perform_later enfileira o job e retorna imediatamente — quem chamou não espera perform rodar. retry_on StandardError, wait: :polynomially_longer, attempts: 5 diz ao Active Job: se perform levantar essa exceção, tente de novo automaticamente, até 5 vezes, esperando mais tempo a cada nova tentativa. Sem isso, uma falha temporária (rede instável, serviço externo fora do ar por alguns segundos) perderia o job silenciosamente, sem nenhuma nova tentativa.
Exercício
Uma action hoje chama RelatorioService.new(pedido).gerar_e_enviar de forma síncrona, travando a resposta até o relatório terminar. Transforme isso num job assíncrono chamado por essa action.