Introdução
Esses conceitos formam a espinha dorsal de qualquer sistema de processamento assíncrono. Vale entender cada um de forma independente.
Job
No contexto da programação, um job é simplesmente uma tarefa que precisa ser executada. Um boco de trabalho, com início, meio e fim. A palavra vem do inglês mesmo: job = trabalho, tarefa.
// Isso é um job — qualquer método .NET
public async Task ProcessarTransacao(int tenantId, string transacaoId)
{
// lógica aqui
}
Job vs. Código Comum
A diferença fundamental é que código comum executa junto com quem pediu. Um job executa separado, no seu próprio tempo:
Código comum:
Cliente → Requisição → Servidor processa tudo → Resposta → Cliente
Com jobs:
Cliente → Requisição → Servidor responde imediatamente
↓
Job 1: envia email (depois)
Job 2: atualiza estoque (depois)
Job 3: gera nota fiscal (depois)
Ideia Central
Pense no seu computador. Quando você abre o Outlook às 8h da manhã e ele automaticamente verifica novos e-mails, sincroniza o calendário e baixa anexos — cada uma dessas operações é um job. Alguém programou: "toda vez que o programa abrir, execute essas tarefas".
Em software, um job é qualquer operação que você quer executar de forma controlada, separada do fluxo principal da aplicação.
Jobs no dia a dia de sum sistema
Pense em um e-commerce. Quando um cliente finaliza uma compra, várias coisas precisam acontecer:
- Enviar e-mail de confirmação
- Atualizar o estoque
- Notificar o vendedor
- Gerar nota fiscal
- Registrar no sistema financeiro
Você poderia fazer tudo isso dentro da requisição HTTP — o cliente clica em "Comprar" e fica esperando enquanto o servidor faz tudo. Mas isso é lento e frágil. Se qualquer etapa falhar, o cliente vê um erro.
A solução é transformar cada uma dessas tarefas em um job: o servidor responde imediatamente "compra realizada!" e delega o restante para ser executado em background, de forma assíncrona.
Anatomia de um Job
Todo job tem três características:
O que fazer — o código que será executado, normalmente um método. Ex: public void EnviarEmailConfirmacao(int pedidoId)
Quando fazer — agora, daqui a 30 minutos, todo dia às 9h:
O que fazer se falhar — tentar de novo? Quantas vezes? Notificar alguém?
Por que persistir Jobs?
Se você simplesmente usar Task.Run(() => EnviarEmail()), o job existe só na memória. Se o servidor reiniciar — por um deploy, uma falha, qualquer motivo — o job desaparece e o e-mail nunca é enviado.
Por isso sistemas de jobs sérios gravam a tarefa em um banco de dados antes de executar. Assim, mesmo que o servidor caia no meio da execução, o job pode ser retomado depois.
É exatamente isso que o Hangfire faz, e agora que você entende o conceito de job, o Hangfire vai fazer muito mais sentido.
Worker
Um worker é uma thread que fica olhando a fila de jobs e os executa. O número de workers é configurável por servidor.
Um worker é uma unidade de execução — uma thread (ou processo) que fica em loop contínuo fazendo basicamente:
- "Tem job disponível na fila?"
- Se sim, pega e executa
- Se não, dorme um tempo e volta ao passo 1
O número de workers define o paralelismo real do sistema. Com 5 workers, você processa até 5 jobs simultaneamente. Mais workers significa mais throughput, mas também mais pressão sobre recursos compartilhados (banco, memória, CPU).
Workers podem ser thread-based (dentro do mesmo processo da aplicação) ou process-based (processos separados, como no modelo do Celery com Python). Cada modelo tem tradeoffs de isolamento, overhead e simplicidade operacional.
Storage
O storage é a camada de persistência que conecta quem enfileira jobs com quem os executa. Funciona como um contrato durável: mesmo que a aplicação caia, os jobs não se perdem.
O storage precisa resolver alguns problemas não triviais:
- Mutual exclusion — garantir que dois workers não peguem o mesmo job
- Visibilidade — jobs precisam ficar "invisíveis" enquanto estão sendo processados
- Reentrada — se um worker morrer no meio, o job volta à fila
- Scheduling — suporte a jobs agendados para o futuro
Bancos relacionais, Redis, SQS, Kafka — qualquer coisa pode ser storage, desde que resolva esses problemas. A escolha impacta diretamente latência, throughput e confiabilidade.
Servidor
O "servidor" nesse contexto é uma instância da aplicação que tem workers ativos. É o processo que efetivamente consome a fila.
A separação entre quem enfileira e quem processa é intencional e poderosa. Você pode ter:
- Aplicação web que só enfileira (sem workers)
- Processo dedicado que só processa (sem receber requisições HTTP)
- Ou os dois juntos, no mesmo processo
Múltiplos servidores apontando pro mesmo storage formam um pool distribuído de workers — é assim que você escala horizontalmente o processamento.
Como os três se relacionam
[Produtor] → [Storage] ←→ [Servidor A: worker 1, worker 2]
[Servidor B: worker 1, worker 2]
Top comments (0)