Olay kaynağı, modern dağıtık sistemlerde güçlü bir mimari desen olarak ortaya çıkmış olup, değiştirilemez bir denetim izi sunar ve karmaşık zamansal sorgulamaları mümkün kılar. Ancak etkin bir şekilde uygulanması, özellikle projeksiyonları yönetme ve şema evrimini ele alma konusunda önemli zorluklar doğurur. Geleneksel CRUD uygulamalarında verilerin yerinde değiştirilmesinin aksine, olay kaynağı durumunuzu bir olay akışından yeniden oluşturmanızı gerektirir; bu da komutlar, olaylar ve projeksiyonlar arasındaki ayrımı kritik hale getirir.
Temel Zorluk: Değiştirilemez Olaylar vs. Değiştirilebilir Projeksiyonlar
Olay kaynaklı bir sistemde "gerçeğin tek kaynağı" olayların sırasıdır. Bu olaylar, sistemin geçmişinin bütünlüğünü korumak için değiştirilemez kalmalıdır. Ancak, projeksiyonlanmış görünümler—ön yüzünüzün veya API'nizin tüketdiği okuma modelleri—iş gereksinimleri değiştikçe evrim geçirmelidir. Bu ikilem, benzersiz bir mühendislik sorunu yaratır: Geçmişi yeniden yazmadan veya güncellemeler sırasında veri tutarsızlığına neden olmadan okuma modellerinizi nasıl güncellersiniz?
Çözüm, projeksiyonları veri modelinizde birinci sınıf vatandaşlar olarak ele almaktır. Onlar basit veritabanı tabloları değil; sürümlendirilmesi, taşınması ve potansiyel olarak yeniden oluşturulması gereken ayrı bileşenlerdir.
Olaylarda Şema Evrimini Yönetme
Olay kaynağında en kalıcı sorunlardan biri şema kaymasıdır. Uygulamanız, bir olaya yeni alanlar ekleyecek şekilde evrim geçirdiğinde, henüz güncellenmemiş eski hizmetlerle geriye dönük uyumluluğu nasıl sağlarsınız?
Altın kural şudur: Mevcut bir olay özelliğinin adını veya türünü asla değiştirmeyin. Her zaman yeni özellikler ekleyin. Bir özelliği değiştirmeniz gerekirse, yeni bir olay türü oluşturun. Bunu kolaylaştırmak için olay şemanız bir sürüm numarası içermelidir.
Basit bir sipariş oluşturma olayını ele alalım. Başlangıç durumu şu şekilde görünebilir:
{
"eventId": "123e4567-e89b-12d3-a456-426614174000",
"aggregateId": "order-001",
"timestamp": "2023-10-27T10:00:00Z",
"type": "OrderCreated",
"version": 1,
"customerId": "cust-123",
"totalAmount": 150.00,
"currency": "USD"
}
Daha sonra, sipariş oluşturulduğu anda kargo adresini izlemeniz gerekirse, mevcut olayı değiştirmemelisiniz. Bunun yerine, yeni bir olay türü tanıtabilir veya eski yapıyı deserialization (serileştirme) mantığınızda desteklemeye devam ederken null olabilir bir alan ekleyebilirsiniz. Newtonsoft.Json veya System.Text.Json kütüphanelerinin ignoreDataMember öznitelikleri ile kullanımı, bu varyasyonları zarif bir şekilde yönetmeye yardımcı olabilir.
Güçlü Projeksiyonlar Tasarlama
Projeksiyonlar, değiştirilemez olayları sorgulanabilir okuma modellerine dönüştürdüğümüz mekanizmadır. Kimlik benzeri (idempotent) olmalı ve olayları üreten alan mantığından yalıtılmış olmalıdır.
İyi tasarlanmış bir projeksiyon işleyicisi şunları yapmalıdır:
- Belirli olay türlerine abone olur.
- Olay verilerini okuma veritabanı (genellikle ilişkisel veya belge deposu) için uygun bir biçime dönüştürür.
- Eksik veya sırası bozuk olayları zarif bir şekilde işler.
İşte C# dilinde kavramsal bir projeksiyon işleyicisi örneği:
public class OrderTotalProjection : IHandleEvents<OrderCreated>, IHandleEvents<OrderCancelled>
{
private readonly IDocumentSession _session;
public OrderTotalProjection(IDocumentSession session)
{
_session = session;
}
public void Handle(OrderCreated @event)
{
var projection = new OrderProjection
{
OrderId = @event.AggregateId,
Status = "Open",
Total = @event.TotalAmount
};
// Kimlik benzerliliği sağlamak için Upsert mantığı
_session.Store(projection);
}
public void Handle(OrderCancelled @event)
{
var projection = await _session.LoadAsync<OrderProjection>(@event.AggregateId);
if (projection != null)
{
projection.Status = "Cancelled";
projection.Total = 0;
_session.Store(projection);
}
}
}
Prodüksiyonda Projeksiyonları Taşıma
Okuma modelinizin şeması değiştiğinde—örneğin OrderProjection nesnesine bir shippingAddress alanı eklendiğinde—sadece bir veritabanı taşıması çalıştıramazsınız. Olay akışından projeksiyon verilerini yeniden oluşturmanız gerekir. Bu genellikle şu şekilde yapılır:
- Yeni projeksiyon kodunu eskisiyle birlikte dağıtmak.
- Etkilenen tüm topluluklar (aggregates) için tüm olayları yeniden oynatan bir arka plan işçisi çalıştırmak.
- Yeni şema ile okuma veritabanını güncellemek.
- Arka plan doldurma (backfill) işlemi tamamlandıktan sonra API'yi yeni görünüme sorgu yapacak şekilde değiştirmek.
Sonuç
Olay kaynağı için veri modelleme, bir zihniyet değişikliği gerektirir. Artık depolama için tablolar tasarlamıyorsunuz; geçmiş için akışlar ve erişim için görünümler tasarlıyorsunuz. Olay evrimini projeksiyon güncellemelerinden kesinlikle ayırarak ve projeksiyonların kimlik benzeri ve sürümlendirilmiş olduğundan emin olarak, sadece değişime karşı dirençli değil, aynı zamanda tüm iş faaliyetlerinin şeffaf ve denetlenebilir bir geçmişini koruyan dağıtık sistemler oluşturabilirsiniz. Karmaşıklığı kucaklayın; sisteminiz size eşsiz bir esneklik ve izlenebilirlikle karşılık verecektir.