Modern yazılım mühendisliği ortamında, veri tutarlılığını korurken milyonlarca eşzamanlı kullanıcıyı yönetebilen sistemler kurmak artık bir lüks değil, bir zorunluluktur. Geleneksel CRUD (Oluştur, Oku, Güncelle, Sil) mimarileri yüksek işleme kapasitesi altında genellikle zorlanır; bu da veritabanı çakışmalarına, karmaşık işlem yönetimine ve hata ayıklaması zor durum sorunlarına yol açar. Bu darboğazları aşmak için ileri düzey ekipler giderek daha fazla Komut Sorgu Sorumluluk Ayrımı (CQRS) ve Olay Kaynağı kombinasyonuna yönelmektedir.
Temel Kavramları Anlamak
CQRS ve Olay Kaynağı genellikle birlikte anılır, ancak farklı sorunlara çözüm getirirler. CQRS, okuma ve yazma işlemlerini ayrı modellerde ayıran bir mimari kalıptır. Geleneksel bir sistemde tek bir veritabanı hem sorguları hem de güncellemeleri yönetir. CQRS'de "Komutlar" durumu değiştirir (yazma tarafı), "Sorgular" ise durumu getirir (okuma tarafı). Bu ayrım, ekiplerin okuma ve yazma işlemlerini bağımsız olarak ölçeklendirmesini ve her modeli belirli kullanım durumuna göre optimize etmesini sağlar.
Olay Kaynağı bu yaklaşımı yazma tarafında bir adım ileriye taşır. Bir varlığın mevcut durumunu depolamak yerine, olay kaynağı bu varlıktaki değişiklikleri temsil eden değişmez (immutable) olayların bir dizisini depolar. Mevcut durum doğrudan saklanmaz; bu olaylar yeniden oynatarak türetilir. Bu, eksiksiz bir denetim izi sağlar, zamansal sorgulamayı (herhangi bir anda durumun ne olduğunu bilmek) basitleştirir ve veri depolamayı iş mantığından ayırır.
Neden Yüksek İşleme Kapasitesi İçin Bu Mimariliyi Seçmeliyiz?
Yüksek işleme kapasiteli dağıtık sistemler için temel avantajlar performans ve dayanıklılıktır. Okuma ve yazma yollarını ayırarak, okuma ağırlıklı yüklerin yazma işlemlerini engellemesini önlersiniz. Ayrıca, olaylar ekleme tabanlı ve değişmez olduğu için geleneksel ilişkisel veritabanları yerine yüksek performanslı günlük sistemlerine (Apache Kafka veya AWS Kinesis gibi) yazılabilirler; bu da yazma işleme kapasitesini önemli ölçüde artırır.
Bir finansal ticaret platformunu ele alalım. Her ticaret yürütme işlemi bir olaydır. Bu olayları depolayarak, mükemmel bir geçmiş tutarak yasal uyumluluğu sağlamanın yanı sıra, karmaşık geçmiş tabloları olmadan herhangi bir milisaniyedeki portföy değerini yeniden oluşturmanıza da olanak tanır.
Uygulama Örneği: Basit Bir Sipariş Servisi
Sadeleştirilmiş bir alanda kavramsal bir uygulamaya bakalım. Akışı göstermek için bir olay ve bir komut tanımlayacağız.
// Değişmez olayı tanımla
class OrderCreatedEvent {
constructor(orderId, customerId, items) {
this.orderId = orderId;
this.customerId = customerId;
this.items = items;
this.timestamp = new Date();
}
}
// Komutu tanımla
class PlaceOrderCommand {
constructor(userId, orderDetails) {
this.userId = userId;
this.orderDetails = orderDetails;
}
}
// Aggregate root komutu işler ve olaylar üretir
class OrderAggregate {
constructor() {
this.events = [];
}
// Komutu uygula
async placeOrder(command) {
// 1. İş kurallarını doğrula
if (command.orderDetails.items.length === 0) {
throw new Error("Sipariş boş olamaz");
}
// 2. Olayı oluştur
const event = new OrderCreatedEvent(
generateId(),
command.userId,
command.orderDetails.items
);
// 3. Olayı mevcut duruma uygula
this.applyEvent(event);
// 4. Olayı olay deposuna kaydet (Yazma Tarafı)
await eventStore.save([event]);
}
// Olayı iç duruma uygula
applyEvent(event) {
if (event instanceof OrderCreatedEvent) {
this.id = event.orderId;
this.customerId = event.customerId;
this.items = event.items;
}
}
}
Okumalar İçin Durumu Yeniden Oluşturma
Okuma tarafında, ayrı bir projeksiyon motoru olay akışını dinler. Bu olayları tüketir ve hızlı sorgulama için optimize edilmiş normalleştirilmemiş okuma modellerini (Elasticsearch veya SQL'de materyalleştirilmiş bir görünüm gibi) günceller. Bu, yazma yükü artsa bile API yanıtlarınızın anında olmasını sağlar.
// Projeksiyon İşleyicisi (Okuma Tarafı)
class OrderProjection {
constructor(readDb) {
this.readDb = readDb;
}
handleEvent(event) {
if (event.type === 'ORDER_CREATED') {
// Hızlı okuma için normalleştirilmemiş bir tabloya ekle
this.readDb.insert({
orderId: event.payload.orderId,
customerId: event.payload.customerId,
itemsCount: event.payload.items.length,
createdAt: event.payload.timestamp
});
}
}
}
Sonuç
Olay Kaynağı ve CQRS uygulamak sihirli bir değnek değildir; olay sürümleme, nihai tutarlılık ve operasyonel yük açısından karmaşıklık getirir. Ancak, ölçeklenebilirlik, denetlenebilirlik ve performansın hayati önem taşıdığı yüksek işleme kapasiteli dağıtık sistemler için avantajlar maliyetlerden çok daha fazladır. Bu kalıpları ustalaşarak, hem dayanıklı hem de kullanıcılarınızın artan taleplerine göre evrilebilen sistemler inşa edebilirsiniz.