İletişimdeki Gizli Kilit: Outbox Pattern İle Mesajlarınızı Güçlendirin
The Secret Key to Communication: Strengthen Your Messages with Outbox Pattern
Bir mektup yazıldığında postaya teslim edilirdi. Teslimat gibi diğer kalan işlemler, posta memurları tarafından gerçekleştirildi. Mektubun varış süresi ve güvenilirliği mektubu yazan kişinin tarafında değildi. Ancak gönderilmek istenen mektup, posta teşkilatanın çalışmadığı bir döneme denk gelirse, mektup bekletilir ve uygun vakitte teslim edilirdi. İlgili senaryoyu yazılım geliştirme alanında açıklamak gerekirse, dağıtık mimarilerdeki servisler arası iletişimde kaybolan mesajlar konusu ele alınır. Özellikle bir servisin veriyi üretmesi kadar, diğer servise ulaştırmasının da sorumluluğu olduğunu unutmamak gereklidir. Bu bağlamda Outbox Design Pattern konusuna odaklanılmalıdır.
Outbox Design Pattern Nedir?
Asenkron sistemlerde, mesajların kaybolma riski, bağlantı kopması, hata alınması gibi sorunlar veri kayıplarına ve tutarsızlıklara yol açabilir. Bu durumu önlemek için kullanılan Outbox Design Pattern, mesaj kaybını engelleyerek, bağlantı tekrar kurulduğunda mesajın güvenli bir şekilde hedefe iletilmesini sağlar, böylece sistemler arası iletişimde güvenilirlik ve tutarlılık sağlanmış olur.
Outbox pattern, Guaranteed Delivery Pattern (Garantili Teslimat Deseni)’a dayanmaktadır.
Outbox Design Pattern’ın Teorik Davranışı Nasıldır?
Bir servise gelen istek sonucunda farklı bir servise veya message broker’a yeni bir mesaj (message) veya olay (event) yayımlanacaksa, bu işlemi hemen gerçekleştirmek yerine, hedef servise ulaşıp ulaşmadığından emin olunmalıdır. Zira dağıtık mimarilerde servislerin geçici sorunlar yaşaması normaldir. İlgili servis ayakta değilse, bu serviste yapılan işlemler bütünsel tutarsızlığa neden olabilir. Bu durumda, iletişim işlemini doğrudan dış servis veya message broker ile yapmak yerine, ilgili mesajı giden kutusuna (Outbox) işleyip bekletmek daha güvenli olacaktır. Daha sonra, bu tabloyu düzenli aralıklarla tarayan ve kaydedilen mesajları hedefe ileten Outbox Publisher Application ile mimari tasarım, servisler arası iletişimin daha güvenli ve tutarlı olmasını sağlar. Bu sayede, olası veri kayıpları riski azaltılır ve mesajın hedefe en az bir kere ulaşması garanti altına alınarak servisler arasında loosely coupled (gevşek bağlılık) sağlanır.
Outbox Design Pattern, asenkron servisler arası iletişimde verilerin Outbox’ta tutularak, olası aksaklıkların, sistem kesintilerinin veya bağlantı kopukluklarının iletişim ve veri transferi üzerinde olumsuz etki yaratmasını önler. Bu sayede, mesajların hedef servislere başarılı bir şekilde iletilip iletilmediği güvence altına alınır ve veri tutarsızlığı riski minimize edilir. Bu avantajlarla, projelerde güvenli ve bütünsel bir iletişim süreci sağlanmış olur.
Microservice yaklaşımında, her kritik mesaj taşıyan servisin, kendi Outbox tablosunu kullanarak mesajları güvenli bir şekilde saklaması gerekmektedir. Bu mesajlar daha sonra bir yayımcı (publisher) aracılığıyla hedef servise ya da message broker’a iletilmelidir.
Outbox Pattern Hangi Durumlarda Kullanılır?
Bir servisin mesajları doğrudan göndermek yerine, bu mesajları bir veritabanı tablosuna kaydederek belirli aralıklarla hedef servislere iletmeyi sağlayan bir patterndır. Mesajların güvenli bir şekilde en az bir kere iletilmesini garantilemek için kullanılır. Özellikle aynı anda iki işlemin gerçekleştiği durumlar için, örneğin bir e-ticaret uygulamasındaki ‘Order Service’ üzerindeki sipariş kaydı ve aynı zamanda ‘OrderCreatedEvent’ adlı bir olayın message broker’a yazılması gibi durumlar için uygun bir seçenektir.
Dual Write, belirli bir olayın gerçekleştiği durumlarda hem veritabanına hem de bir message broker gibi iki farklı yapıya aynı anda veri yazma işlemidir. Ancak, bu işlem sırasında yaşanabilecek aksilikler veya tutarsızlıklar, uygulama bütünlüğünü olumsuz etkileyebilir. Bu tutarsızlıklar, sistem sağlıklıyken fark edilmeyebilir ancak uzun vadede ortaya çıkabilir. Outbox pattern, Dual Write’tan kaynaklanan olası tutarsızlıkları sıfıra indirmek için kullanılır. Veritabanında yapılan değişikliklerin ardından yürütülecek işlemleri veri tabanında bir görev olarak saklamaya dayanır. Böylece, bir transaction içinde hem veri tabanındaki işlemi gerçekleştirir hem de bu işlemden sonra yapılacak aksiyon için işaret bırakır. Dual Write, dağıtık, olay tabanlı uygulamalarda sıkça sorunlara neden olan bir durumdur ve bu sorunları tespit etmek, maliyetleri ölçmek ve düzeltmek zor ve zahmetli olabilir.
Outbox pattern’in temel özelliği, geçici bir mesaj/olay deposu olmasıdır. Mesajlar hedef servise ya da mesaj aracına ulaştırıldığında ise silinmeli veya işlendiğine dair güncellenmelidir.
Outbox Pattern’da Mesajlar Nasıl Publish Edilmelidir?
Outbox Pattern’da mesajları publish etmenin iki ana yolu vardır: Pulling Publisher ve Transaction Log Tailing.
Pulling Publisher, belirli aralıklarla Outbox tablosunu sorgulayarak mesajları yayımlayan bir uygulama geliştirme yöntemidir. Ancak sık sorgulama, veritabanı maliyetini artırabilir.
Transaction Log Tailing ise Outbox tablosunun transaction loglarını okuyarak değişiklikleri takip eder ve mesajları yayımlar. İki yöntemin de uygulanabilirliği projenin ihtiyaçlarına ve kullanılan teknolojiye bağlıdır.
Outbox modeli, dağıtık mimarilerde atomik bir davranışın gerçekleştirilmesine olanak tanır.
Idempotents Problemi
Outbox pattern kullanırken, publish edilen mesajların işlendiğini belirlemek veya tablodan silmek, nadiren de olsa oluşabilecek bir handikap içerir. Bu durum, Outbox tablosundaki işaretleme veya silme işlemlerinin, ilgili veritabanıyla olası bir iletişim hatası nedeniyle gerçekleştirilememesi durumunu ifade eder. Mesaj yayınlanmış olabilir ancak Outbox tablosuna bu durum yansıtılamaz. Veritabanı ile tekrar iletişim kurulduğunda, işlenmiş olan mesaj tekrar işleme tabi tutulabilir, bu da uygulama açısından bütünsel tutarlılığı etkileyebilecek bir durumu özetler.
Bu durumla başa çıkmak için, mesajları/event’leri tüketen consumer’ların Idempotent olarak tasarlanması önerilir. Idempotent, matematikte ve bilgisayar bilimlerinde kullanılan, ilk işlem haricinde sonraki tüm işlemler için etkisi olmayan ve sonucu değiştirmeden uygulanabilen bir özellik ifade eder.
Rest mimarisindeki get, put ve delete işlemleri idempotent özellik gösterirken, post işlemi idempotent değildir. İdempotentlik, matematiksel bir terim olarak herhangi bir işlemin tekrarlanması sonucunda ilk işlemin dışında ekstra bir sonuç üretmemesi anlamına gelir. Örneğin, bir kullanıcıyı silme işlemi idempotenttir, çünkü tekrarlanan isteklerde aynı kullanıcı silinmediği için sonuç değişmez. Bunun yanı sıra, post işlemi idempotent özellik göstermez, çünkü her istekte aynı veriyi ekleyerek sonucu etkiler. Günlük hayattan bir örnekle de açıklanan idempotent kavramı, X bankasının bankomatında bakiye sorgulama işlemine benzetilmiştir. Bakiye sorgulama işlemi idempotentken, para çekme veya yatırma işlemi idempotent olmayan bir işlemdir, çünkü her işlem sonrasında bakiye değişir.
Distributed yapılanmalarda, idempotent davranış genel bir prensiptir, yani bir mesajın birden çok kez yayınlanması aynı etkiyi üretmeli ve güvenlice yeniden gönderilebilmelidir. Bu nedenle, servisler arası iletişimde kullanılacak mesajlar veya event’lerin idempotent olarak tasarlanması, doğru ve tutarlı davranış için kritik bir öneme sahiptir.
Yayınlanacak message/event’larda idempotency nasıl sağlanır?
Yayınlanacak mesajlarda idempotency sağlamanın yolu, her bir mesaj veya event için özel bir anahtar oluşturmak ve consumer’ların bu anahtar aracılığıyla daha önce tüketilip tüketilmediğini kontrol etmelerini sağlamaktır. Örneğin, Outbox table’a eklenen verilere IdempotentToken değeri ekleyerek bu kontrolü yapabiliriz. Mesajlar, consumer tarafından önce Inbox Table adlı bir tabloya kaydedilir ve daha sonra işlenir. Eğer aynı IdempotentToken değerine sahip bir kayıt varsa işlem gerçekleştirilmez, aksi halde işleme alınır. Bu yöntem, Inbox Pattern’ını temel alır.
Inbox Pattern, Outbox Pattern’a benzer bir yapıya sahiptir. Outbox Pattern’da işlemler gerçekleştirildikten sonra yayınlanacak mesajlar Outbox table’a kaydedilir ve ardından bir publisher aracılığıyla yayınlanırken, Inbox Pattern’da işlenecek mesajlar önce Inbox table’a eklenir ve daha sonra işlenirler. Örneğin, bir siparişi veritabanına kaydetmek ve ardından stok bilgisini güncellemek için Outbox Pattern kullanılırken, stok bilgilerini güncellemekten sorumlu olan consumer, ilgili mesajları Inbox table’a işleyip daha sonra işleme alır. Ancak burada, işlenen mesajların Inbox table’da tamamlandığına dair güncelleme yapılması gereklidir.
Outbox Table’ın Şeması Nasıl Olmalıdır?
Outbox Table’ın şeması, yayınlanacak mesajların bilgilerini depolamak için tasarlanmış bir yapıya sahip olmalıdır. Tipik olarak, Outbox Table şemasında aşağıdaki alanlar bulunabilir:
CREATE TABLE [dbo].[OutboxTable]
(
[IdempotentToken] [uniqueidentifier] NOT NULL,
[MessageType] [nvarchar](255) NOT NULL,
[Payload] [nvarchar](max) NOT NULL,
[CreatedDate] [datetime2](7) NOT NULL,
[ProcessedDate] [datetime2](7) NULL,
[IsProcessed] BIT DEFAULT 0
)
Bu kod, Outbox Table
adında bir tablo oluşturur. Tablonun alanları şu şekildedir:
IdempotentToken, consumer’lar tarafından yinelenebilecek mesajların tespit edilebilmesi için mesaja idempotent özelliği ekleyen ve bir yandan da mesajın benzersiz kimliğe sahip olmasını sağlayan veriyi tutar.
MessageType, mesajın türünü temsil eden karakter dizisi tipinde bir alan. Örneğin, “OrderCreated” veya “PaymentReceived” gibi.
Payload: Mesajın veri içeriğini temsil eden uzun metin tipinde bir alan. JSON veya XML verilerini depolamak için kullanılabilir.
CreatedDate: Mesajın oluşturulma tarihini temsil eden bir datetime tipinde alan.
ProcessedDate: Mesajın işlendiği tarih bilgisini tutar. Mesaj yayınlanmadığı sürece null değerine sahip olacaktır. Yayınlandıktan sonra o anın tarih değeri atanacaktır. İsterseniz bu bilgiyi işlemek yerine mesajı silebilirsiniz!
IsProcessed: Mesajın işlenip işlenmediğini belirten bir bit tipinde bayrak alanı. Varsayılan olarak "0" (false) olarak ayarlanır.