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 ;
});
}
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ı.