Skip to main content
Hangfire servisi src/DiyanetCleanArchitecture.Infrastructure.Jobs.Hangfire projesindedir. Arka plan ve gecikmeli işleri PostgreSQL storage üzerinde çalıştırır ve MediatR ile köprülenmiş bir command/event-bus sunar.

Storage — PostgreSQL

Hangfire job verileri ayrı bir hangfire şemasında tutulur. Bağlantı ConnectionStrings:HangfireConnection’dan gelir:
var storageOptions = new PostgreSqlStorageOptions
{
    SchemaName              = configuration.GetSection("Services:Hangfire:Schema").Value ?? "hangfire",
    QueuePollInterval       = TimeSpan.FromMilliseconds(200),
    InvisibilityTimeout     = TimeSpan.FromMinutes(5),
    DistributedLockTimeout  = TimeSpan.FromMinutes(5),
    PrepareSchemaIfNecessary= true,
    StartupConnectionMaxRetries = 2,
    AllowDegradedModeWithoutStorage = false,   // storage yoksa uygulama başlamaz
};

services.AddHangfire((provider, conf) =>
    conf.SetDataCompatibilityLevel(CompatibilityLevel.Version_170)
        .UseSimpleAssemblyNameTypeSerializer()
        .UseRecommendedSerializerSettings()
        .UsePostgreSqlStorage(opts =>
            opts.UseNpgsqlConnection(configuration.GetConnectionString("HangfireConnection")),
            storageOptions));
{
  "ConnectionStrings": {
    "HangfireConnection": "Host=localhost;Port=5432;Database=DiyanetCleanArchitecture;Username=postgres;Password=..."
  }
}
QueuePollInterval 200 ms’dir — Hangfire.PostgreSql 50 ms altına izin vermez; CPU sızıntısını önlemek için bilinçli olarak biraz yüksek tutulmuştur.

Sunucular ve kuyruklar

Her Hangfire server IHostedService olarak kaydedilir. Services:Hangfire:Servers[] listesindeki her giriş bir server üretir. Standart kurulumda iki server vardır — event-bus ve command-bus — her biri kendi kuyruğu ve DLX (dead-letter) kuyruğu ile:
{
  "Services": {
    "Hangfire": {
      "Path": "/jobs",
      "DashboardTitle": "DiyanetCleanArchitecture Hangfire Dashboard - Dev",
      "Username": "admin",
      "Password": "admin",
      "Schema": "hangfire",
      "Servers": [
        { "Name": "event-bus",   "WorkerCount": 5, "Queues": [ "event-queue",   "event-queue-dlx" ] },
        { "Name": "command-bus", "WorkerCount": 5, "Queues": [ "command-queue", "command-queue-dlx" ] }
      ]
    }
  }
}
foreach (var server in servers)
{
    services.AddHangfireServer(opt =>
    {
        opt.ServerName = server.Name;
        opt.WorkerCount = server.WorkerCount;
        opt.CancellationCheckInterval = TimeSpan.FromSeconds(10);
        opt.Queues = server.Queues;
    });
}

MediatR köprüsü

BuildingBlocks.Hangfire.MediatR.Extensions paketi, Hangfire job’larını MediatR command/event’lerine bağlar. DI’da şu servisler kayıt edilir:
services.AddScoped<ICommandBus, CommandBus>();
services.AddScoped<ICommandExecuter, CommandExecuter>();
services.AddScoped<IEventBus, EventBus>();
services.AddScoped<IEventDispatcher, EventDispatcher>();
services.AddSingleton<ITimeZoneResolver>(new TimeZoneConverterResolver());
  • ICommandBus — bir command’i command kuyruğuna enqueue eder; worker ICommandExecuter üzerinden MediatR Send çağırır.
  • IEventBus — bir event’i event kuyruğuna enqueue eder; IEventDispatcher MediatR Publish çağırır.
İşlenmeyen işler ilgili DLX kuyruğuna (*-dlx) düşer.

Dashboard — /jobs + BasicAuth

Dashboard Services:Hangfire:Path (default /jobs) altında host edilir ve BasicAuthAuthorizationFilter ile korunur (kullanıcı adı/şifre config’ten):
public static IApplicationBuilder UseHangfireDashboard(this IApplicationBuilder app, IConfiguration configuration)
{
    var authUser = new BasicAuthAuthorizationFilter(new BasicAuthAuthorizationFilterOptions
    {
        RequireSsl = false, SslRedirect = false, LoginCaseSensitive = true,
        Users = new[] { new BasicAuthAuthorizationUser
        {
            Login = configuration.GetSection("Services:Hangfire:Username").Value,
            PasswordClear = configuration.GetSection("Services:Hangfire:Password").Value
        }}
    });

    app.UseHangfireDashboard(configuration.GetSection("Services:Hangfire:Path").Value, new DashboardOptions
    {
        DashboardTitle = configuration.GetSection("Services:Hangfire:DashboardTitle").Value,
        IsReadOnlyFunc = ctx => false,
        Authorization = new[] { authUser }
    });

    return app;
}
RequireSsl ve SslRedirect false ayarlıdır — dashboard’a HTTP üzerinden erişilebildiği varsayılır (ters proxy TLS sonlandırması yapar). Public erişimde mutlaka güçlü BasicAuth kimlik bilgileri kullanın.

Health check

Hangfire server’lar ve storage erişimi [Ready] tag’li bir health check ile izlenir. Worker yoksa durum Degraded olur (API request/response çalışmaya devam etmeli):
services.AddHealthChecks()
    .AddHangfire(opts => opts.MinimumAvailableServers = 1,
        name: "hangfire",
        failureStatus: HealthStatus.Degraded,
        tags: new[] { HealthCheckTags.Ready, HealthCheckTags.Jobs });

Kullanım — RecurringJob / BackgroundJob

// Tek seferlik (fire-and-forget)
BackgroundJob.Enqueue<IMyJob>(j => j.RunAsync(payload));

// Gecikmeli
BackgroundJob.Schedule<IMyJob>(j => j.RunAsync(payload), TimeSpan.FromMinutes(10));

// Periyodik (cron)
RecurringJob.AddOrUpdate<IMyJob>("daily-cleanup", j => j.CleanupAsync(), Cron.Daily);
MediatR köprüsü üzerinden command/event enqueue:
await _commandBus.SendAsync(new MyCommand(...));   // command-queue
await _eventBus.PublishAsync(new MyEvent(...));     // event-queue

Events

Command/event bus akışı ve MassTransit Outbox ile ilişkisi.

Operasyon

Dashboard erişimi, izleme ve dağıtım notları.