# Hangfire Orchestrator

Especialista em background jobs, tarefas agendadas e processamento assíncrono com Hangfire.

## Responsabilidades

1. **Criar jobs** de background para processamento assíncrono
2. **Configurar schedules** com expressões cron
3. **Gerenciar filas** e prioridades
4. **Monitorar** execução via dashboard

## Triggers

Keywords: `scheduled`, `job`, `background`, `cron`, `recurring`, `batch`, `queue`, `hangfire`, `task`

---

## ⚠️ .NET 10 Compatibility

**ALWAYS check package version:**
- Hangfire.AspNetCore: **≥ 1.8.22** (required for .NET 10)
- No breaking changes from .NET 9

**Consult:** `framework/standards/dotnet10-compatibility.md`

## Por que Hangfire (não Azure Functions)

| Aspecto | Hangfire | Azure Functions |
|---------|----------|-----------------|
| Custo | Free (in-process) | Pay per execution |
| Dashboard | Built-in | App Insights separado |
| Debugging | Local fácil | Emulator necessário |
| Estado | Persistido SQL | Storage Account |
| Complexidade | Baixa | Média-Alta |

## Configuração Básica

```csharp
// Program.cs
builder.Services.AddHangfire(config => config
    .SetDataCompatibilityLevel(CompatibilityLevel.Version_180)
    .UseSimpleAssemblyNameTypeSerializer()
    .UseRecommendedSerializerSettings()
    .UseSqlServerStorage(
        builder.Configuration.GetConnectionString("HangfireConnection"),
        new SqlServerStorageOptions
        {
            CommandBatchMaxTimeout = TimeSpan.FromMinutes(5),
            QueuePollInterval = TimeSpan.Zero,
            UseRecommendedIsolationLevel = true,
            DisableGlobalLocks = true
        }));

builder.Services.AddHangfireServer(options =>
{
    options.WorkerCount = Environment.ProcessorCount * 2;
    options.Queues = new[] { "critical", "default", "low" };
});

// Middleware
app.UseHangfireDashboard("/hangfire", new DashboardOptions
{
    Authorization = new[] { new HangfireAuthorizationFilter() }
});
```

## Tipos de Jobs

### Fire-and-Forget (Uma vez)

```csharp
// Executa imediatamente em background
BackgroundJob.Enqueue(() => _emailService.SendWelcomeEmailAsync(userId));

// Com injeção de dependência
BackgroundJob.Enqueue<IEmailService>(service =>
    service.SendWelcomeEmailAsync(userId));
```

### Delayed (Atrasado)

```csharp
// Executa após delay
BackgroundJob.Schedule(() =>
    _orderService.CheckAbandonedCartAsync(cartId),
    TimeSpan.FromHours(24));

// Em data específica
BackgroundJob.Schedule(() =>
    _reminderService.SendReminderAsync(appointmentId),
    appointmentDate.AddHours(-1));
```

### Recurring (Recorrente)

```csharp
// Cron expression
RecurringJob.AddOrUpdate<IReportService>(
    "daily-sales-report",
    service => service.GenerateDailySalesReportAsync(),
    "0 8 * * *",  // Todo dia às 8h
    new RecurringJobOptions
    {
        TimeZone = TimeZoneInfo.FindSystemTimeZoneById("E. South America Standard Time")
    });

// Expressões comuns
// "0 * * * *"     - A cada hora
// "*/5 * * * *"   - A cada 5 minutos
// "0 8 * * 1-5"   - 8h em dias úteis
// "0 0 1 * *"     - Primeiro dia do mês
```

### Continuations (Encadeamento)

```csharp
var jobId = BackgroundJob.Enqueue(() => _importService.ImportDataAsync(fileId));

BackgroundJob.ContinueJobWith(jobId, () =>
    _notificationService.NotifyImportCompleteAsync(fileId));
```

### Batch (Lote)

```csharp
// Requer Hangfire.Pro ou implementação custom
var batchId = BatchJob.StartNew(batch =>
{
    foreach (var orderId in orderIds)
    {
        batch.Enqueue(() => _orderService.ProcessOrderAsync(orderId));
    }
});

BatchJob.ContinueBatchWith(batchId, batch =>
{
    batch.Enqueue(() => _reportService.GenerateBatchReportAsync(batchId));
});
```

## Estrutura de Job Service

```csharp
// Jobs/DailySalesReportJob.cs
public interface IDailySalesReportJob
{
    Task ExecuteAsync(CancellationToken ct = default);
}

public class DailySalesReportJob : IDailySalesReportJob
{
    private readonly IOrderRepository _orderRepository;
    private readonly IReportGenerator _reportGenerator;
    private readonly IEmailService _emailService;
    private readonly ILogger<DailySalesReportJob> _logger;

    public DailySalesReportJob(
        IOrderRepository orderRepository,
        IReportGenerator reportGenerator,
        IEmailService emailService,
        ILogger<DailySalesReportJob> logger)
    {
        _orderRepository = orderRepository;
        _reportGenerator = reportGenerator;
        _emailService = emailService;
        _logger = logger;
    }

    [AutomaticRetry(Attempts = 3)]
    [Queue("default")]
    public async Task ExecuteAsync(CancellationToken ct = default)
    {
        _logger.LogInformation("Starting daily sales report generation");

        var yesterday = DateTime.Today.AddDays(-1);
        var orders = await _orderRepository.GetByDateRangeAsync(
            yesterday, yesterday.AddDays(1), ct);

        var report = await _reportGenerator.GenerateAsync(orders, ct);

        await _emailService.SendReportAsync(report, ct);

        _logger.LogInformation("Daily sales report completed. Orders: {Count}", orders.Count);
    }
}

// Registro
RecurringJob.AddOrUpdate<IDailySalesReportJob>(
    "daily-sales-report",
    job => job.ExecuteAsync(default),
    "0 8 * * *");
```

## Retry e Error Handling

```csharp
[AutomaticRetry(Attempts = 5, DelaysInSeconds = new[] { 60, 300, 900, 3600, 7200 })]
[Queue("critical")]
public async Task ProcessPaymentAsync(int orderId)
{
    try
    {
        await _paymentService.ProcessAsync(orderId);
    }
    catch (PaymentGatewayException ex)
    {
        _logger.LogWarning(ex, "Payment failed for order {OrderId}, will retry", orderId);
        throw; // Rethrow para Hangfire retry
    }
    catch (Exception ex)
    {
        _logger.LogError(ex, "Unexpected error processing order {OrderId}", orderId);
        throw;
    }
}
```

## Dashboard Authorization

```csharp
public class HangfireAuthorizationFilter : IDashboardAuthorizationFilter
{
    public bool Authorize(DashboardContext context)
    {
        var httpContext = context.GetHttpContext();

        // Em produção, verificar autenticação/autorização
        if (!httpContext.User.Identity?.IsAuthenticated ?? true)
            return false;

        return httpContext.User.IsInRole("Admin");
    }
}
```

## Documentação de Referência

- [Hangfire Documentation](https://docs.hangfire.io/en/latest/)
- [Cron Expressions](https://crontab.guru/)
- [SQL Server Storage](https://docs.hangfire.io/en/latest/configuration/using-sql-server.html)

## Checklist de Jobs

- [ ] Job service com interface
- [ ] Injeção de dependência correta
- [ ] AutomaticRetry configurado
- [ ] Queue definida (critical/default/low)
- [ ] Logging estruturado
- [ ] CancellationToken propagado
- [ ] Dashboard protegido
- [ ] Timezone configurado para recorrentes

---

*MORPH-SPEC by Polymorphism Tech*
