Hazır! Ateşle — Hangfire
Hangfire, arka planda çalışacak işlerimizi (jobs) yönetmemizi sağlayan açık kaynak (open-source) kütüphanedir. Uygulamamızın yaşam döngüsünden bağımsız yani arka tarafta kendi halinde kodlarımızı işletmek istiyorsak kullanabileceğimiz kütüphaneler arasındadır. Dahili olarak sağlamış olduğu gösterge paneli (dashboard) ile de işlerimizi (jobs) zamanı gelmeden tetikleyebilir, durdurabilir kısaca kontrol edebiliriz. Bir örnek vermem gerekirse, her gün saat 04:00'te günlük (log) kayıtlarını silmek isteyebiliriz. Tabii geçmişte windows servislerle bu gibi periyodik işlemler yapılıyordu fakat bu işlem beraberinde windows işletim sistemine de bağımlılık yaratıyordu.
Hangfire’ın popüler muadili Quartz kütüphanesini incelemek isterseniz bu linkten, Emektar İşçi Hizmeti — Worker (IHostedService) içeriğine göz atmak için bu linkten 🔗 ulaşabilirsiniz.
Hangfire’ın resmî dokümanında da yazdığı gibi Net Framework 4.5 ve üstü ile Net Core 1.0 üstü versiyonları desteklemektedir. Ayrıca gerekli bilgileri kaydetmek için Microsoft SQL Server vb. veya No-SQL (Redis, MongoDB vb.) veri tabanı kullanılabilir. Hangfire kütüphanesini aynı zamanda message broker (mesaj kuyruk sistemlerinin genel adı) olarak da kullanılabilir. Eğer projeniz büyük ölçekli değilse, hangfire’ı mesaj kuyruk sistemi olarak da kullanılabilirsiniz. Örneğin blog sitenizde paylaştığınız gönderiye bir yorum geldiğinde e-posta ✉️ göndermek istiyorsanız hangfire iyi bir seçenektir. Büyük ölçekli projelerde ise RabbitMQ, Apache Kafka, MSMQ gibi sadece bu iş için özelleştirilmiş yapıları kullanmak performans açısından daha faydalı olacaktır.
Bunların yanı sıra hangfire loglama entegrasyonu da bizlere sunuyor. Bunun için appsettings.json dosyasında Logging altında hangfire’ı eklemeliyiz. Ben information ve sonrasını loglamayı tercih ettim.
Örneğin kullanıcı uygulamanıza kayıt olduğu zaman, e-posta gönderilebilir. Günlük/aylık/haftalık gibi periyod bazlı ⏱ işlemleriniz varsa hangfire kullanabilirsiniz. Bu işlem arka tarafta yapılacağı için request/response sürelerini azaltmaktadır. Yapılacak işler belirlediğiniz kaynağa (bu bir redis ya da postgresql olabilir.) kayıt edileceğinden dolayı uygulamamız yeniden başlatılsa dahi hangfire kaldığı yerden arka planda işleme devam ediyor. Bunun haricinde isterseniz hangfire işlerini (joblarını) bellekte tutabilirsiniz fakat önerilen bir yöntem değil çünkü uygulama yeniden başlatıldığında bu bilgiler bellekte olduğundan dolayı silinecektir. Çözüm olarak bilgileri kalıcı olarak muhafaza edebileceğiniz veri tabanı kullanmanız daha sağlıklı 🩺 olacaktır.
Çalışma Yapısı
Kütüphane 3 yapıdan oluşmaktadır. Client (Web, API veya Console App olabilir.), Job Storage ve Server. Client tarafında job oluştururuz ve job oluştuğu zaman storage’a kaydedilir. Client’a tekrar oluşturulan job ile ilgili bilgi dönülür ve job veri tabanında oluşturulur.
Server ile client farklı olduğu gibi aynı da olabilir. Server oluşan jobları veri tabanı ya da storage’den belirli aralıklarla ⏳ kontrol ederek çekmektedir. Çekilen joblar arka tarafta yaşamlarını sürdürür ve son durumları hakkında bilgileri güncellenir. (Başarılı, Başarısız, Tekrar kuyruğa alındı gibi.).
Hangfire işleri ve işe ait detayları (Almış olduğumuz parametreleri, job türü vb.) serialize ederek depoluyor. Bu nedenle jobları oluştururken parametreleri çok büyük tutmamaya özen göstermemizde fayda vardır, aksi halde storage de yer kaplayabilir. Örneğin bir metodumuz parametre olarak içinde 300 item olan List<T> alsaydı, bu parametre serilaze edilip storage’e kayıt edilecektir.
Peki Neden Kayıt Ediyor?
Uygulamanız çökerse 🧨 ya da iş hata alırsa yani aksi bir durum yaşanırsa tekrar aynı şekilde deneyebilmek için. O yüzden best practices açısından işleri tanımlarken parametre olarak çok büyük değerler göndermeyerek depolamanın artmasını önleyebiliriz.
Hangfire Storages: Firebase, Mongo, MySqlStorage, SqlServer.Memory, AzureCosmosDB, PostreSQL vb.
Hangfire IoC Container: Autofac, Ninject, Funq, LighInject, Windsor vb.
Hangfire İş Türleri
4 adet ücretsiz [FireAndForgot, Delayed, Recurring, Continuations] ve 2 adet ücretli [Batch, Batch Continuations] olmak üzere toplamda 6 iş türü vardır. Ücretli versiyona bu linkten, ücretsize ise bu linkten ulaşabilirsiniz.
FireAndForgot
BackgroundJob.Enqueue<IEmailSender>(emailSender => emailSender.SenderAsync(userId, message));
Devamlılığı olmayan tek seferde çalışacak (1 kere) kodunuz varsa kullanabileceğiniz tiptir. Örneğin sitenize kayıt olan kullanıcılara e-posta göndermek.
Delayed
BackgroundJob.Enqueue<IEmailSender>(emailSender => emailSender.SenderAsync(userId, message), TimeSpan.FromSeconds(scheduleTime));
Belirli bir süre sonra çalışması gereken işler için kullanılan tiptir. Örneğin kullanıcı sitenizden alışveriş yaptıktan 30 dakika sonra e-posta ile farklı ürün önermek isteyebilirsiniz. FireAndForgot tipinden tek farkı tanımlandığı andan itibaren verilen süre sonrasında çalışması gerektiğini bildiririz.(30 dakika sonra, 1 hafta sonra gibi.)
Recurring
RecurringJob.AddOrUpdate<DeleteLogManager>("recurringJobId",manager => manager.DeleteLogAsync(), "35 15 * * *", TimeZoneInfo.Local);
Belirli aralıklarla çalışmasını istenilen işler için kullanılan tiptir. Örneğin her ayın 1'inde rapor 📊 oluşturmak ya da her haftanın 2 günü çalışmasını istediğiniz iş varsa bu iş tipi işinizi görecektir.
Recurring iş tipi tanımlarken cron expression özelliğine ihtiyaç duyabiliriz. CronExpression özellikle unix veya linux işletim sistemlerine de istenilen görevleri belirli zaman aralığında ve arka planda gerçekleştirmek için kullanılan bir araçtır. Bu araç ile bir zaman belirtebiliyoruz. Cron değeri oluşturmak için örnek site: https://crontab.guru/
Continuations
Background.Job.ContinueJobWith<DeleteLogStatusManager>("parentJobId", manager => manager.ReportDeleteLogStatusAsync(message));
Oluşturmuş olduğunuz diğer işlerden sonra çalışacak işler için kullanılan tiptir. Kısaca, bir işten sonra çalışacak iştir. Bir iş oluşturulurken Id değer döner ve bu değer belirteç olacaktır. İlgili iş tanım id’ye sahip işlemi bitirdikten son bu iş çalışacaktır.
Batch ve Batch Continuations (Ücretli)
Toplu işlem gerçekleştireceğinizde kullanacağınız iş tipidir. Örneğin 500 tane iş gerçekleştireceğiniz zaman bunu for döngüsüyle dönüp oluştururken hata meydana gelmesi olasıdır. Bu iş türü ile kolay bir şekilde hataları handle edebilir, yakalayabiliriz. Yani kısa grup halinde çalıştılan iş tipidir. Batch Continuations ise grup halinde çalıştırılan işlemleri takiben yapılacak olan işler içindir.
Nasıl Projeye Dahil Edebilirim?
Hangfire.AspNetCore ve Hangfire.Core bu işin temelini oluşturan iki pakettir. Hangfire.Dashboard.Basic.Authentication hangfire’ın bize sağlamış olduğu gösterge paneline (dashboard) kimlik doğrulama (authentication) işlemi için kullanıyoruz. Örnek projemde jobları MSSQL Server üzerinde depolayacağım için Hangfire.SqlServer paketini kurdum. Eğer siz bellekte veya farklı bir storage’de tutmak istiyorsanız, Hangfire.MemoryStorage.Core ya da ilgili paketi kurmalısınız. (Redis, Mongo, MySQL gibi)
config.UseSqlServerStorage
ile Hangfire tanımlı jobları nerede depolayacağı hakkında connection string ifadesini tanımlıyoruz. Anlaşılacağı üzere dilerseniz hangfire’ı farklı konumda depolayabilirsiniz. Ben örnek olması açısından uygulamam ile hangfire’ı uygulamam ile aynı veri tabanında depoladım. 🗄
Uygulama ayağa kalktığında veri tabanı tabloları otomatik olarak oluşmaktadır fakat veri tabanını otomatik oluşturmadığını unutmayalım. PrepareSchemaIfNecessary=true
app.UseHangfireDashboard()
ile hangfire gösterge panelinin entegrasyonunu sağlıyor.
Hangfire Dashboard
Gösterge Paneli (Dashboard) bize hazır gelen içeriktir. İşlerimizi takip edebilir, başarısız olan işlerimizin Failed kısmından neden başarısız olduğuna dair bilgi alabilir veya o anda çalışan işleri proccessing kısmından görüntüleyebiliriz.
Bir iş hata aldığında varsayılan (default) olarak hangfire hata alan işi 10 kere dener. Yukarıda ConfigureServices içerisinde GlobalJobFilters ile bunu 7 olarak değiştirdim. Bu eğer bir iş hata alırsa ve o işe özel bir attempts değeri verilmemişse 6 kere daha denenir sonucunda succeeded ya da failed sekmesinde yer alacaktır.
Hangfire.Dashboard.Basic.Authentication 🔐 paketini güvenlik amacıyla kurmuştuk. IDashboardAuthorizationFilter interface ile kullanıcı gösterge paneline (dashboarda) girmeye yetkili mi? Diye kontrol edebiliriz.
Beni Hangfire Kullanmaya İkna Eder Misin?
- Net Core projeleri için bir iki satır kod ile entegrasyonunu kolayca sağlayabiliriz.
- Gösterge Paneli (dashboard) ile yönetilebilirliği kolaydır ve işlerin durumları hakkında anlık bilgi alabiliriz.
- Tanımlı işleri depoladığı için(MSSQL Server, MongoDB, PostgreSQL vb.) iş tamamlanmadığı sürece yani herhangi bir hata durumunda attempts değerini de dikkate alarak iş tekrar tekrar çalıştırılacaktır.
- Uygulamaların response süreleri çok önemlidir. Kullanıcıyı anlık bilgilendirmeyeceğin işleri arka planda yaparak kullanıcıya iyi bir deneyim sunabilirsin. Sonuçta kullanıcı üye olduğunda hoş geldin mailini atmak için bekletmenin bir anlamı yok.
Sektörde Neler Oluyor? 🏬
Stackshare verilerine göre Hangfire kullanan bazı şirketler;
bilisimHR, Working With, Warren, AspNet Zero, V-Raptor, Customer Group, TrackJS, Gbase