ORTA
Job Filters & Middleware
Job filter'lar, job lifecycle'ının her aşamasına müdahale etmenizi sağlar — ASP.NET MVC action filter'larına benzer.
Filter Türleri
| Interface | Tetiklenme Zamanı | Kullanım |
|---|---|---|
IClientFilter |
Job oluşturulurken (Enqueue) | Validation, logging, tenant tagging |
IServerFilter |
Job execute edilirken | Timing, try/catch, distributed lock |
IElectStateFilter |
State değiştirilirken | Custom retry logic, alerting |
IApplyStateFilter |
State uygulandıktan sonra | Metrics, notification |
Karar Rehberi
| Durum | Öneri | Örnek veya gerekçe |
|---|---|---|
| Cross-cutting logging | Uygun: IServerFilter | Tüm job'lara execution time ekle |
| Retry özelleştirme | Uygun: IElectStateFilter | Kalıcı hata ayrımı |
| Tenant context propagation | Uygun: IClientFilter + IServerFilter | Multi-tenant SaaS |
| Basit try/catch | Uygun değil: Filter overkill | Tek job'a özel hata yakalama |
| DB transaction yönetimi | Uygun değil: Job içinde yap | Unit of work pattern |
Custom Filter: Execution Time Logger
public class LogExecutionTimeAttribute : JobFilterAttribute, IServerFilter
{
// NOT: Filter attribute'lar DI almaz! Serilog static Log kullanın
// veya GlobalJobFilters yerine IoC-based filter activation kullanın.
public void OnPerforming(PerformingContext context)
{
context.Items["StartTime"] = Stopwatch.StartNew();
}
public void OnPerformed(PerformedContext context)
{
if (context.Items.TryGetValue("StartTime", out var obj) && obj is Stopwatch sw)
{
sw.Stop();
var jobName = context.BackgroundJob.Job.Type.Name + "." +
context.BackgroundJob.Job.Method.Name;
// Structured logging
Log.Information("Job {JobName} completed in {ElapsedMs}ms. JobId: {JobId}",
jobName, sw.ElapsedMilliseconds, context.BackgroundJob.Id);
// Slow job alert
if (sw.ElapsedMilliseconds > 30000)
{
Log.Warning("SLOW JOB: {JobName} took {ElapsedMs}ms (threshold: 30s)",
jobName, sw.ElapsedMilliseconds);
}
}
}
}
// Kullanım — method seviyesinde
[LogExecutionTime]
public async Task ProcessLargeImportAsync(int importId) { ... }
// Veya global registration
GlobalJobFilters.Filters.Add(new LogExecutionTimeAttribute());AutomaticRetry Attribute
// Default: 10 retry, exponential backoff
[AutomaticRetry(Attempts = 5, DelaysInSeconds = new[] { 60, 300, 900, 3600, 7200 })]
public async Task SendEmailAsync(int emailId)
{
// 1dk, 5dk, 15dk, 1saat, 2saat arayla retry
}
// Retry kapatma (kritik — tek deneme)
[AutomaticRetry(Attempts = 0)]
public async Task ChargePaymentAsync(int paymentId)
{
// Ödeme çift çekilmesin! Idempotency key kullan.
}
// Başarısız olunca silme yerine bekletme
[AutomaticRetry(Attempts = 3, OnAttemptsExceeded = AttemptsExceededAction.Fail)]
public async Task ProcessWebhookAsync(int webhookId) { ... }Tenant Isolation Filter
public class TenantFilterAttribute : JobFilterAttribute, IClientFilter, IServerFilter
{
public void OnCreating(CreatingContext context)
{
// Job oluşturulurken mevcut tenant'ı kaydet
var tenantId = TenantContext.CurrentTenantId;
context.SetJobParameter("TenantId", tenantId);
}
public void OnCreated(CreatedContext context) { }
public void OnPerforming(PerformingContext context)
{
// Job çalışırken tenant context'i restore et
var tenantId = context.GetJobParameter<string>("TenantId");
TenantContext.SetCurrentTenant(tenantId);
}
public void OnPerformed(PerformedContext context)
{
TenantContext.Clear();
}
}Örnek: Multi-tenant SaaS'ta her background job kendi tenant'ının DB connection string'ini kullanmalıdır. TenantFilter ile job oluşturulurken tenant ID kaydedilir, execute sırasında doğru connection string inject edilir.