Outbox Tasarım Deseni

Adem Catamak
4 min readJul 19, 2020

You can access the English version via this link.

Geliştirdiğimiz uygulamaların birçok dış bileşen ile iletişime geçmesi gerekebilmektedir. Bir web hizmeti almak, mesajlaşma sistemi ile iletişime geçmek, veri tabanı ile etkileşime geçmek gibi işlemler örnek olarak sıralanabilir. Birden fazla dış bileşeni bir arada ve tek bir transaction (işlemsel bütün) içerisinde kullanmak her zaman mümkün olamayabilir. Bütünlüğü göz ardı ederek geliştirme yaptığımız durumda da uygulamamızın güvenilebilirliği düşer. Farklı bileşenleri transactional olarak outbox tasarım deseni aracılığı ile nasıl kullanabileceğimiz bu yazının konusu olacaktır.

Bir üyelik sistemi hazırladığımızı düşünelim. Kayıt işleminden sonra kullanıcının verdiği e-posta adresine doğrulama maili atmamız gereksin. Kullanıcıyı veri tabanına kaydettikten sonra mail sistemi ile iletişime geçerek mail atılmasını sağlamaya çalışabiliriz.

İşlem bütünlüğü sağlanmadığı için çıkabilecek birçok sorun olduğunu görebiliriz. Örneğin; veri tabanına kayıt işlemini yaptıktan sonra uygulamamız beklenmedik bir şekilde kapanabilir; veri tabanına kayıt işlemi yaptıktan sonra uygulamamızın çalıştığı makina network sorunları yaşayıp mail servisine ulaşamayabilir veya mail servisi o anda çalışmıyor olabilir. Tüm bu senaryolarda kullanıcıyı sisteme kabul etmiş fakat yürütmemiz gereken tüm adımları yürütememiş olduk. Yani kullanıcıyı sisteme ekledik fakat kullanıcı kayıt işlemini tamamlayamadık.

Mikroservis mimarisi gibi dağıtık mimarilerde de bu duruma benzer sorunlar yer almaktadır. Domain içerisinde oluşan olayların dış dünyaya duyurulması çoğu zaman mesajlaşma sistemi ile yapılmaktadır. Domain içerisinde olayın oluşması ve olayın mesajlaşma sistemine iletilmesi transactional bir bütünlük içerisinde yapılamadığı durumda sistemde aksaklıklar meydana gelmeye başlayacaktır.

İşte bu anda outbox tasarım deseni bizi kurtarmak için ortaya çıkmaktadır. Aslında çok basit bir mantıkla çalışmaktadır. Çözüm; veri tabanı üzerinde değişiklik yapılmasının ardından yürütülecek olan işlemi, veri tabanı içerisine bir görev olarak saklama temeline dayanmaktadır. Yani bir transaction içerisinde hem veri tabanında yapmak istediğimiz işlemi yapıyoruz hem de bu işlemden sonra yürüteceğimiz aksiyon için işaret bırakıyoruz. Bir arka plan uygulamasıyla da görevleri belli aralıklarla tarıyoruz. Yeni bir görev bulunursa, arka plan servisi aracılığı ile bu görevi işliyoruz ve görevi tamamlandı olarak işaretliyoruz. Outbox tasarım desenini bu şekilde özetleyebiliriz.

Mesajlaşma sistemi ile ilgili örneğimizi düşünürsek; kullanıcıyı veri tabanına kaydederken aynı transaction içerisinde KullanıcıOluşturuldu durumunu anlatan mesajı da veri tabanına kaydedeceğiz. Bu mesajın mesajlaşma sistemine gönderilmesi için bir görevi de mesajı kaydederken veri tabanına ekleyeceğiz. Arka plan servisimiz belli aralıklarla tabloları kontrol ederek işlenmemiş görevler için mesajlaşma sistemi ile iletişime geçecektir.

Bu yöntemde mesajın en az bir kere mesajlaşma sistemine iletimi garanti altındadır. Görev alınır, işlenir fakat veri tabanında tamamlandı olarak işaretlenemezse tekrar bu görev yürütülecektir. Outbox tasarım desenini kullanırken bu durumu göz önünde bulundurmayı ihmal etmemeliyiz.

MessageStorage adındaki kütüphane aracılığı ile yukarıdaki gibi karşılaşabileceğimiz sorunları nasıl çözebileceğimize dair bir örnek oluşturalım.

Bunun için .NetCore ile bir uygulama oluşturarak işe başlayalım. Bu işlemden sonra NuGet üzerinden MessageStorage.Db.MsSql , MessageStrorage.Db.MsSql.DI.Extensions ve MessageStorage.AspNetCore paketlerini indirelim ve kodlama kısmına geçelim.

1- Veri tabanı bağlantısını kurmak için DbRepositoryConfiguration sınıfından bir örnek oluşturarak işlemimize devam edelim.

2- Bu işlemin ardından veri tabanı tablolarının oluşması için aşağıdaki satırı kullanalım.

3- Handler olarak isimledirdiğimiz arka plan görevlerimizi bağımlılık olarak sistemimize ekleyelim.

4- Sisteme mesaj eklemek için kullanacağımız arayüz, mesajlarla birlikte kaydettiğimiz görevleri yürütecek arka plan uygulaması, sistemde kayıtlı görev sayısına erişmemizi sağlayacak monitör objesi gibi bağımlılıkları da ekleyelim.

Bu 4 adım aracılığı ile Startup sınıfımızdaki ayarlamaları tamamlamış oluyoruz. Ayarlamalarımızı yaptıktan sonra mesajları ve bu mesajlar için oluşturmak istediğimiz görevlerimizi tanımlayabiliriz. AccountEvent ve AccountCreatedEvent adında mesaj tipleri oluşturarak işe başlayabiliriz.

Bu mesajlar için yürütülecek görevleri Handler tipinden sınıflar aracılığı ile tanımlayacağız. Bir mesaj için birden fazla Handler işlem yürütebilir. Örneğin; AccountCreatedEvent tipinden olan bir obje aynı zamanda AccountEvent tipine de uyduğu için hem AccountCreatedEventHandler hem de AccountEventHandler aynı mesaj için çalışacaktır.

Aşağıdaki örnekte görülebileceği üzere, EntityFramework aracılığı ile bir hesap kaydı yapılırken üyelik oluşturulduğuna dair bir mesaj da veri tabanına kaydedilecektir. Bu mesaj kaydı ile birlikte iki görev de veri tabanına kaydedilecektir.

Çıktı aşağıdaki gibi olacaktır.

Umarım yararlı bir yazı olmuştur. Paket ile alakalı istek ve önerileriniz için GitHub üzerindeki “Issues” kısmını kullanabilirsiniz, açık işler için PR atabilirsiniz :)

--

--