1. O cenário
Um pedido criado via API (Módulo 4) fica com status "Pendente" até que um processo de retaguarda o confirme — simulando, por exemplo, uma checagem de fraude assíncrona ou a integração com um sistema de pagamento externo. Este worker roda a cada 5 minutos, busca pedidos pendentes, "processa" cada um e atualiza o status via procedure.
2. A procedure de apoio
CREATE OR ALTER PROCEDURE dbo.usp_PedidoListarPendentes
AS
BEGIN
SET NOCOUNT ON;
SELECT Id, ClienteId, ValorTotal
FROM dbo.Pedido
WHERE Status = 'Pendente';
END;
GO
CREATE OR ALTER PROCEDURE dbo.usp_PedidoConfirmar
@PedidoId INT
AS
BEGIN
SET NOCOUNT ON;
SET XACT_ABORT ON;
UPDATE dbo.Pedido
SET Status = 'Confirmado'
WHERE Id = @PedidoId AND Status = 'Pendente';
END;
GO
3. O Repository
public interface IPedidoProcessamentoRepository
{
Task<List<PedidoPendente>> ListarPendentesAsync();
Task ConfirmarAsync(int pedidoId);
}
public record PedidoPendente(int Id, int ClienteId, decimal ValorTotal);
public class PedidoProcessamentoRepository : IPedidoProcessamentoRepository
{
private readonly string _connectionString;
public PedidoProcessamentoRepository(string connectionString) => _connectionString = connectionString;
public async Task<List<PedidoPendente>> ListarPendentesAsync()
{
await using var conexao = new SqlConnection(_connectionString);
var resultado = await conexao.QueryAsync<PedidoPendente>(
"dbo.usp_PedidoListarPendentes", commandType: CommandType.StoredProcedure);
return resultado.ToList();
}
public async Task ConfirmarAsync(int pedidoId)
{
await using var conexao = new SqlConnection(_connectionString);
await conexao.ExecuteAsync("dbo.usp_PedidoConfirmar",
new { PedidoId = pedidoId }, commandType: CommandType.StoredProcedure);
}
}
4. O Worker completo
public class ProcessadorPedidosWorker : BackgroundService
{
private readonly IServiceScopeFactory _scopeFactory;
private readonly ILogger<ProcessadorPedidosWorker> _logger;
private readonly WorkerOptions _opcoes;
public ProcessadorPedidosWorker(
IServiceScopeFactory scopeFactory,
ILogger<ProcessadorPedidosWorker> logger,
IOptions<WorkerOptions> opcoes)
{
_scopeFactory = scopeFactory;
_logger = logger;
_opcoes = opcoes.Value;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
using var temporizador = new PeriodicTimer(TimeSpan.FromMinutes(_opcoes.IntervaloEmMinutos));
while (await temporizador.WaitForNextTickAsync(stoppingToken))
{
try
{
await ProcessarPendentesAsync(stoppingToken);
}
catch (Exception ex)
{
_logger.LogError(ex, "Falha ao processar pedidos pendentes");
}
}
}
private async Task ProcessarPendentesAsync(CancellationToken stoppingToken)
{
using var escopo = _scopeFactory.CreateScope();
var repositorio = escopo.ServiceProvider.GetRequiredService<IPedidoProcessamentoRepository>();
var pendentes = await repositorio.ListarPendentesAsync();
_logger.LogInformation("{Total} pedidos pendentes encontrados", pendentes.Count);
foreach (var pedido in pendentes)
{
stoppingToken.ThrowIfCancellationRequested();
// simulação de verificação (chamaria um serviço externo de fraude/pagamento aqui)
await Task.Delay(200, stoppingToken);
await repositorio.ConfirmarAsync(pedido.Id);
_logger.LogInformation("Pedido {PedidoId} confirmado (Cliente {ClienteId}, Valor {Valor:C})",
pedido.Id, pedido.ClienteId, pedido.ValorTotal);
}
}
}
5. Program.cs
var builder = Host.CreateApplicationBuilder(args);
builder.Services.AddWindowsService(o => o.ServiceName = "ProcessadorDePedidos");
builder.Services.AddSystemd();
builder.Services.Configure<WorkerOptions>(builder.Configuration.GetSection("Worker"));
builder.Services.AddScoped<IPedidoProcessamentoRepository>(_ =>
new PedidoProcessamentoRepository(builder.Configuration.GetConnectionString("Default")!));
builder.Services.AddHostedService<ProcessadorPedidosWorker>();
var host = builder.Build();
host.Run();
6. O que praticar a partir daqui
Um worker de produção real cresceria em algumas direções diretas: métricas (quantos pedidos processados por execução, tempo médio), alertas quando a fila pendente cresce demais, e idealmente um mecanismo de lock distribuído se múltiplas instâncias do worker rodarem ao mesmo tempo (para não processar o mesmo pedido duas vezes). São todas extensões do que este capítulo já cobriu.
7. Onde isso te leva
Com Worker Services completos, o último módulo deste curso cobre o quarto e último modelo de aplicação C#: interfaces gráficas Desktop, onde a interação acontece através de cliques e data binding, em vez de requisições HTTP ou execução em segundo plano.