sametcancelik.dev
Geri Dön

Dağıtık Sistemlerde Veri Kaybetmeme Sanatı: .NET ile Transactional Outbox Pattern

06.07.2026

Dağıtık Sistemlerde Veri Kaybetmeme Sanatı: .NET ile Transactional Outbox Pattern

Event-Driven bir mimaride her şey harika görünür: Servisler asenkron konuşur, bağımlılıklar minimumdur ve sistem teorik olarak ölçeklenebilirdir. Ancak acı gerçek, sistem production’a çıktığında ve saniyede yüzlerce istek almaya başladığında yüzünü gösterir.

Bir sipariş alındığında (OrderCreated), hem veritabanına bu siparişi kaydetmek hem de Message Broker'a (örneğin RabbitMQ) bir event fırlatmak istersiniz.

Peki ya sipariş veritabanına başarıyla yazıldıktan tam o mikro saniyede Message Broker çökerse ya da network giderse? Veya tam tersi; event fırlatılır ama veritabanı timeout olursa?

İşte bu durum, sisteminizde veri tutarsızlığı (data inconsistency) fırtınası yaratır. Dağıtık sistemlerde bu krizi çözmek için iki aşamalı commit (2PC) gibi hantal ve performansı baltalayan yapılar yerine, Transactional Outbox Pattern kullanılır.

Outbox Pattern Nedir?

Buradaki temel felsefe şudur: Network üzerindeki harici bir bileşene (Message Broker) güvenme, atomik işlem kabiliyeti olan kendi veritabanına güven.

Event'i doğrudan broker'a fırlatmak yerine, uygulamanın ana veritabanında "Outbox" adında bir tablo oluşturursunuz. Siparişi kaydettiğiniz veritabanı transaction'ı (DbContext.Database.BeginTransaction()) içerisine, fırlatacağınız event'i de bir satır olarak bu Outbox tablosuna yazarsınız.

RDBMS dünyasının sunduğu ACID kuralları sayesinde ya sipariş ve event satırı birlikte kaydedilir ya da ikisi birden rollback olur. Risk sıfırlanır. Daha sonra arka planda çalışan bağımsız bir süreç (Message Relay / Worker), bu tabloyu periyodik olarak okur, yeni mesajları broker'a basar ve basılanları tablodan temizler.

.NET ve MassTransit İle Bunu Kurmak Neden Çok Kolay?

Eskiden bu Outbox tablosunu dinleyen, arka planda çalışan (BackgroundService / Worker) süreçleri elle yazmak, "at-least-once delivery" kurallarını yönetmek tam bir işkenceydi.

Bugün .NET ekosisteminde MassTransit, sunduğu entegre Outbox desteğiyle bu işi mimari açıdan tek satır koda indirgiyor:

C#

services.AddMassTransit(x =>
{
x.AddEntityFrameworkOutbox<OrderDbContext>(o =>
{
o.UseSqlServer(); // Veya PostgreSQL
o.UseBusOutbox(); // Kuyruğa güvenli basım logic'i
});
// ...
});

MassTransit, veritabanındaki o Outbox tablosunu arka planda kendisi oluşturup yönetir. Event'lerin kuyruğa güvenle gittiğinden emin olduktan sonra tablodan temizler. Size de sadece standart Publish metodu çağırmak kalır; gerisini altyapı halleder.

Kritik Not: Idempotent Consumer

Outbox Pattern kullandığınızda, mesajın kuyruğa en az bir kere (at-least-once delivery) gideceğini garanti edersiniz. Ancak network kesintilerinde, bir mesaj broker'a başarıyla gitmesine rağmen "gitti" yanıtı işleyiciye ulaşamayabilir. Bu durumda mesaj mükerrer olarak tekrar gönderilir.

Dolayısıyla, Outbox pattern uyguladığınız sistemlerde mesajı tüketen tarafların (Consumer) Idempotent olması zorunludur. Yani tüketici servis, aynı ID'ye sahip bir event'i ikinci kez aldığında bunu fark etmeli ve aynı işlemi veritabanında mükerrer şekilde uygulamamalıdır.

Özetle;

Mikroservisleri sadece HTTP bağımlılığından kurtarmak yetmez, network'ün kopma ihtimaline karşı veri tutarlılığını da sağlama almak gerekir. .NET ve MassTransit ikilisi, Transactional Outbox şablonunu projelerinize entegre ederken sizi tekerleği yeniden icat etmekten kurtarır.