Event Sourcing: Olay Tabanlı Mimari
Event Sourcing (Olay Tabanlı Mimari), günümüzde giderek daha popüler hale gelen bir yazılım mimari yaklaşımıdır. Geleneksel veritabanı tabanlı mimarilerin yerine alternatif olarak öne çıkmaktadır. Event Sourcing, sistemdeki tüm durum değişikliklerini (olayları) kaydetmeyi ve bu olaylardan sistemin mevcut durumunu oluşturmayı amaçlayan bir tasarım desenidir. Bu sayede, sistemin geçmiş durumu tamamen yeniden oluşturulabilir ve her bir durum değişikliği geri alınabilir, sorgulanabilir veya analiz edilebilir.
Event Sourcing'in Temel Kavramları
Event Sourcing'in temel kavramlarına bir göz atalım:
1. Olaylar (Events): Bir olay, sistemdeki bir durum değişikliğini temsil eder. Örneğin, bir kullanıcının hesap oluşturması, bir öğe eklemesi veya bir siparişin iptal edilmesi gibi eylemler olayları tetikler. Her olay, zaman damgasıyla işaretlenmiş ve sistemde meydana gelen gerçek bir olayı temsil eder.
2. Durum (State): Bir sistemin geçerli durumunu ifade eder. Durum, olayların uygulanmasıyla zamanla değişir. Eğer tüm olaylar doğru bir şekilde uygulanırsa, sistemin mevcut durumu elde edilir.
3. Komut (Command): Bir komut, sisteme bir eylemi gerçekleştirmesi için talimat veren istemci tarafından gönderilen bir istektir. Komutlar, olayların oluşmasına neden olabilir ve sistem durumunu değiştirir.
4. Etkinlik Güdümlü Mimari (Event-Driven Architecture - EDA): Event Sourcing, Etkinlik Güdümlü Mimari'nin temelini oluşturur. EDA, sistemdeki olayların tetiklediği mesajlaşma ve işlemleri içeren bir mimari desendir. Bir olay meydana geldiğinde, ilgili bileşenler otomatik olarak haberdar edilir ve gereken eylemler gerçekleştirilir.
Event Sourcing'in Avantajları
Event Sourcing yaklaşımının çeşitli avantajları vardır:
1. Durum Geçmişinin Tam Olarak Takip Edilmesi: Event Sourcing, sistemdeki tüm durum değişikliklerini kaydettiği için, herhangi bir zamandaki sistemin geçmiş durumu tam olarak takip edilebilir. Bu durum, denetim izini ve hata ayıklama süreçlerini kolaylaştırır.
2. Geri Alınabilirlik ve Geçmişe Dönük Analiz: Olayların kaydedilmesi, geçmiş durumları tekrar oluşturma ve sistem durumunu geri alma olanağı sağlar. Bu, hatalı işlemleri geri almayı veya sistemdeki eğilimleri analiz etmeyi kolaylaştırır.
3. Servis Ölçeklendirme: Event Sourcing, olaylar aracılığıyla farklı bileşenler arasındaki asenkron iletişimi sağlar. Bu, belirli işlemleri parçalara bölerek, servislerin bağımsız olarak ölçeklendirilmesini mümkün kılar.
4. Uzun Süreli Verilerin Saklanması: Geleneksel veritabanı tabanlı mimarilerde, durum değişiklikleri genellikle üzerine yazma şeklinde yapılır. Event Sourcing ise olayları birer geçmiş olarak saklayarak uzun süreli veri saklama ihtiyacını karşılar.
Event Sourcing'in Zorlukları
Event Sourcing yaklaşımıyla karşılaşılan bazı zorluklar şunlardır:
1. Etkinlik Yönetimi: Büyük sistemlerde olayların yönetilmesi ve sürekliliğinin sağlanması karmaşık olabilir.
2. Eşleştirme ve Gerçek Zamanlı İşleme: Olayları durumla eşlemek, özellikle yüksek hacimli veri akışında zorluğa neden olabilir.
3. Sorgu Performansı: Durumu tamamen olaylardan oluşturan sistemler, bazı sorguların performansını etkileyebilir.
Event Sourcing, geleneksel veritabanı tabanlı mimarilere alternatif bir yaklaşımdır ve sistemlerin durum değişikliklerini olaylar aracılığıyla yönetir. Bu sayede, sistemdeki durum geçmişi takip edilebilir, geri alınabilirlik sağlanır ve uzun süreli veri saklama ihtiyacı karşılanır. Ancak, karmaşıklık ve performans konuları dikkate alınmalıdır. Event Sourcing, doğru bir şekilde uygulandığında, ölçeklenebilir ve dayanıklı sistemlerin inşasında güçlü bir araç haline gelir.
Aggregate Nedir?
Aggregate, ilgili olayları bir araya getiren ve bir bütün olarak ele alınan bir yapıdır. Bir Aggregate, genellikle tek bir işlem birimi, bir varlık veya bir nesne olarak düşünülebilir. Aggregate'lar, veri tabanı tablolarına veya sınıf yapısına benzemezler, ancak birbiriyle ilişkili olayların mantıksal bir koleksiyonunu temsil ederler.
Aggregate'lar Neden Kullanılır?
Event Sourcing ve Aggregate kullanmanın birkaç temel nedeni vardır:
-
İşlem Birimi: Aggregate'lar, birbirleriyle ilişkili olan olayların bir işlem birimi olarak gruplandırılmasını sağlar. Bu, veri bütünlüğünün korunmasını ve sistemin tutarlı bir durumda kalmasını sağlar.
-
Performans ve Ölçeklenebilirlik: Bir uygulama, her biri kendi Aggregate'ına ait olayları işleyerek performansı artırabilir ve ölçeklenebilirliği sağlayabilir. Böylece, farklı Aggregate'lar arasındaki işlem trafiği daha iyi dağıtılabilir.
-
Sürdürülebilirlik ve Bakım Kolaylığı: Aggregate'lar, işlevsel ve mantıksal olarak birbirinden bağımsız olduğu için, bakım ve geliştirme süreçlerini kolaylaştırır.
Aggregate'lar Nasıl Kullanılır?
Aggregate'lar, olayların mantıksal bir koleksiyonunu temsil eder. Bir Aggregate, tipik olarak bir kimlik (ID) ve ilgili olayları işleyen yöntemleri içerir. Yeni bir olay gelirse, ilgili Aggregate, olayı işleyerek durumunu günceller ve mevcut durumu elde etmek için diğer olaylarla birleştirir. Bu sayede, sistemdeki değişiklikler tüm Aggregate'lar üzerinden tutarlı bir şekilde işlenir.
Örnek
Bir e-ticaret uygulaması düşünelim. E-ticaret uygulamasında her bir sipariş, bir Aggregate olarak düşünülebilir. Her yeni sipariş, Sipariş Aggregate'ı tarafından işlenir ve bu Aggregate, siparişle ilgili tüm olayları saklar. Örneğin, yeni bir sipariş oluşturulduğunda, ödeme yapıldığında veya sipariş iptal edildiğinde ilgili olaylar Sipariş Aggregate'ında saklanır ve Aggregate, bu olayları işleyerek siparişin durumunu günceller.
Event Sourcing, bir uygulamanın durumunu olaylar (events) aracılığıyla takip eden bir mimari desenidir. Event Sourcing'in temel amacı, tüm durum değişikliklerini olaylar halinde saklamak ve bu olaylar aracılığıyla mevcut durumu oluşturmaktır. Aggregates (Türkçe: Özetleyiciler), Event Sourcing mimarisinde önemli bir kavramdır ve sistemin durumunu organize etmek ve yönetmek için kullanılır.
Projection Nedir?
Event Sourcing'de "Projection" (Türkçe: Yansıtma veya Görüntüleme), sistemdeki olayların sürekli olarak işlenerek, farklı veri yapılarına dönüştürülmesi ve belirli sorguların veya analizlerin daha verimli ve hızlı bir şekilde gerçekleştirilmesini sağlayan bir kavramdır.
Event Sourcing'de olaylar (events) tüm durum değişikliklerini temsil eder ve sistem durumu bu olaylar üzerinden yeniden oluşturulabilir. Ancak, bazı sorgu veya analiz işlemleri için doğrudan olaylar üzerinden çalışmak verimlilik ve performans açısından yetersiz olabilir. İşte bu noktada "Projection" devreye girer.
Projection Nasıl Çalışır?
Projection, olayları sürekli olarak dinleyen ve belirli veri yapılarına dönüştüren mekanizmalardır. Olaylar, Projection tarafından işlenerek farklı veri tabanları, cache'ler veya analiz araçları gibi çeşitli hedeflere yansıtılır. Bu yansıtılmış veriler, farklı sorgu veya analiz işlemleri için daha uygun bir yapıya sahip olabilir ve bu sayede belirli sorguların daha hızlı ve etkin bir şekilde gerçekleştirilmesine olanak tanır.
Örnek
E-ticaret uygulaması örneğimize devam edelim. E-ticaret uygulamasında, her bir sipariş olayı bir "Sipariş Oluşturuldu", "Ödeme Yapıldı" veya "Sipariş İptal Edildi" gibi olaylar olabilir. Bu olaylar, Event Sourcing'e göre saklanır ve sistemin mevcut durumu bu olaylar üzerinden oluşturulur.
Ancak, bir kullanıcının son 24 saat içinde verdiği siparişlerin sayısını hızlıca sorgulamak istediğimizi düşünelim. Tüm olayları sırayla işlemek yerine, "Son 24 saatteki Siparişler" adında bir Projection oluşturabiliriz. Bu Projection, sadece son 24 saat içindeki "Sipariş Oluşturuldu" olaylarını dinler ve bu olayları bir tabloda toplar. Bu tablo, sorgu işlemleri için uygun bir yapıya sahip olur ve sonuçların hızlı bir şekilde elde edilmesini sağlar.
Projection'ların Faydaları
-
Performans Artışı: Projection'lar, belirli sorgu veya analiz işlemlerine özelleştirilmiş veri yapıları oluşturarak performansı artırır. Bu, tüm olayları sırayla işlemekten daha verimli olabilir.
-
Ölçeklenebilirlik: Projection'lar, verileri farklı hedeflere yansıtarak uygulamayı parçalara ayırmayı ve bileşenleri bağımsız olarak ölçeklendirmeyi kolaylaştırır.
-
Sorgu Kolaylığı: Yansıtılmış veri yapıları, belirli sorgu işlemlerine daha uygun hale gelir ve sorgu işlemlerinin karmaşıklığını azaltır.
-
Veri Entegrasyonu: Projection'lar, farklı hizmetler veya sistemler arasında veri entegrasyonu için kullanılabilir.
Event Sourcing'de "Projection", olayları dinleyerek farklı veri yapılarına dönüştüren mekanizmalardır. Projection'lar, veri tabanları, cache'ler veya analiz araçları gibi farklı hedeflere yansıtılan verilerin oluşturulmasına olanak tanır. Bu, belirli sorgu ve analiz işlemlerinin daha hızlı ve etkin bir şekilde gerçekleştirilmesini sağlar ve Event Sourcing uygulamalarının performans ve ölçeklenebilirlik açısından daha etkili olmasına katkıda bulunur.
CQRS ve Event Sourcing: Uygulama Tasarımında İkilinin Dansı
CQRS ve Event Sourcing, modern uygulama tasarımında giderek daha fazla dikkat çeken iki önemli mimari desendir. Her ikisi de karmaşık yazılım sistemlerini daha kolay anlaşılır, ölçeklenebilir ve sürdürülebilir hale getirmeyi hedefler. CQRS ve Event Sourcing, tek bir sistemi oluşturan iki ayrı, ancak birbirini tamamlayan yaklaşımlardır.
CQRS Nedir?
CQRS, Command Query Responsibility Segregation'ın kısaltmasıdır ve temelde bir sorumluluk ayrıştırma deseni olarak kabul edilir. Geleneksel mimarilere göre farklılaşan CQRS, komutları (Command) ve sorguları (Query) ayrı bileşenlere ayırarak işlemleri farklı şekillerde işlemeyi amaçlar. Komutlar, veri değişikliklerini gerçekleştirmek için kullanılırken, sorgular, veriyi okumak ve sunmak için kullanılır. Bu sayede, veri okuma ve veri yazma işlemleri farklı yollarla ele alınır ve her tür işlem için uygun optimizasyonlar yapılabilir.
CQRS ve Event Sourcing'in İlişkisi
CQRS ve Event Sourcing birbirini tamamlayan iki yaklaşımdır ve birlikte kullanıldığında bazı avantajlar sunar:
1. Sorumluluk Ayrıştırma: CQRS, komutları ve sorguları farklı bileşenlere ayırdığı için uygulamanın karmaşıklığını azaltır. Event Sourcing ise her bir durum değişikliğini olaylar aracılığıyla takip ederek uygulamanın durumunu izler. Bu iki yaklaşımın birleşimi, uygulamanın farklı işlemleri için farklı bileşenler kullanmasını sağlayarak kod tabanını daha okunabilir ve sürdürülebilir hale getirir.
2. Geri Alınabilirlik: Event Sourcing, tüm durum değişikliklerini kaydettiği için geri alınabilirlik özelliğini destekler. CQRS, veri okuma ve yazma işlemlerini ayrı bileşenlerde ele aldığı için, geri alma işlemleri daha da kolaylaşır.
3. Ölçeklenebilirlik: CQRS ve Event Sourcing'in birlikte kullanılması, ölçeklenebilirliği artırır. Veri yazma ve okuma işlemleri farklı bileşenlerde işlendiği için, bu bileşenler bağımsız olarak ölçeklendirilebilir.
4. Olaylar ile Durum Arasındaki Bağlantı: CQRS, olayları (komutların sonucunu) sorgulamak için kullanılır. Event Sourcing ise durumun olaylarla temsil edildiği bir yaklaşımdır. Dolayısıyla, CQRS, Event Sourcing ile tutarlı bir şekilde çalışarak sistem durumunu dinamik olarak sorgulamayı mümkün kılar.
CQRS ve Event Sourcing, modern uygulama tasarımında önemli iki mimari desenidir. CQRS, komutları ve sorguları farklılaştırarak sorumlulukları ayrıştırırken, Event Sourcing, olaylar aracılığıyla uygulamanın durumunu takip eder. Bu iki yaklaşımın birlikte kullanılması, uygulamaların karmaşıklığını azaltır, ölçeklenebilirliği artırır ve geri alınabilirlik gibi önemli avantajlar sağlar. Ancak, CQRS ve Event Sourcing'in uygulama gereksinimlerine uygun bir şekilde tasarlanması ve uygulanması için dikkatli bir çaba gereklidir.
Event Store: Olay Tabanlı Veri Depolama
Event Store, Event Sourcing mimarisini destekleyen bir veri tabanıdır. Event Sourcing, bir sistemin durumunu olaylar aracılığıyla takip ettiği bir tasarım desenidir. Bu yaklaşımda, tüm sistem durumu, geçmişte gerçekleşen olayların bir koleksiyonu olarak saklanır ve her bir olay sistemin geçerli durumunu oluşturan tekil bir değişikliği temsil eder. Event Store, bu olayların depolanması ve yönetilmesi için kullanılan özel bir veritabanıdır.
Event Store'un Temel Özellikleri
Event Store'un dikkate değer özellikleri şunlardır:
1. Olayları Merkeze Alan Yapı: Event Store, olayları merkeze alan bir veri depolama yaklaşımını benimser. Olaylar, diğer geleneksel veritabanı sistemlerinde olduğu gibi üzerine yazma ya da değiştirme işlemleriyle güncellenmez. Bunun yerine, yeni bir olay oluşturulur ve mevcut olaylarla birlikte saklanır.
2. Yüksek Performans: Event Store, olayların çok hızlı bir şekilde kaydedilmesini ve okunmasını sağlayacak şekilde optimize edilmiştir. Olayları zaman damgası ile sıralar ve olayların yayınlanmasını, aboneler tarafından asenkron olarak dinlenmesini ve işlenmesini destekler.
3. Dayanıklılık ve Güvenilirlik: Event Store, olayların kaybolmasını önlemek için dayanıklılık ve güvenilirlik özelliklerine sahiptir. Olaylar güvenli bir şekilde saklanır ve sistem çeşitli başarısızlık senaryolarında dahi veri bütünlüğünü korur.
4. Geri Alınabilirlik ve Geçmişe Dönük Analiz: Event Store, tüm olayların saklandığı bir geçmişe sahip olduğundan, sistemdeki durum değişiklikleri geri alınabilir ve geçmişe dönük analiz yapılabilir. Bu, hataları izlemenin ve analiz etmenin kolaylaştırılmasını sağlar.
5. Çoklu Okuma ve Asenkron Haberleşme: Event Store, olayları asenkron bir şekilde dinleyen ve işleyen birden çok abone ile çalışabilir. Bu sayede sistem bileşenleri arasında esnek ve ölçeklenebilir iletişim sağlar.
Event Store'un Uygulama Senaryoları
Event Store, çeşitli uygulama senaryolarında kullanılabilir:
1. Yüksek Olabilirlikli Uygulamalar: Event Store, yüksek kullanılabilirliğe ihtiyaç duyan sistemlerde tercih edilir. Olaylar, kaybolma riskine karşı korunarak sistemin dayanıklılığı arttırılır.
2. Finansal Uygulamalar: Finansal uygulamalar, işlem geçmişinin doğru ve eksiksiz bir şekilde takip edilmesini gerektirir. Event Store, finansal verilerin güvenilir bir şekilde saklanması ve analiz edilmesi için kullanılabilir.
3. İş Süreç Yönetimi: Event Store, iş süreçlerinin izlenmesi ve yönetilmesi için kullanılabilir. Olaylar, iş süreçlerinin aşamalarını temsil eder ve süreçlerdeki aksaklıkların belirlenmesini sağlar.
4. Takip ve Denetim Amaçlı Uygulamalar: Event Store, sistemin durum değişikliklerinin takip edilmesi ve denetim izinin oluşturulması için idealdir.
Event Store, olay tabanlı mimarilere geçiş yapmak isteyen yazılım geliştiriciler için güçlü bir veri depolama çözümüdür. Olay tabanlı mimariler, sistemin durumunu olaylar aracılığıyla takip ederek geri alınabilirlik, denetim izi ve analiz kolaylığı gibi avantajlar sağlar. Event Store, yüksek performans, dayanıklılık ve güvenilirlik gibi temel özellikleri ile bu tür mimarilerin etkili bir şekilde uygulanmasına katkıda bulunur.
DDD hakkındaki bilgi almak için aşağıdaki linkten blog yazımı okuyabilirsiniz.
Domain Driven Design Nedir?
KURULUM VE KODLAMARA BAŞLAYALIM
Event Store Kurulumu
https://www.eventstore.com/downloads linkinden işletim sisteminize uygun sürümü indirip, kuruyoruz.
https://hub.docker.com/r/eventstore/eventstore buradan docker'da hızlıca container oluşturabilirsiniz.
EventStore'u aşağıdaki komutla cmd.exe üzerinden çalıştırıyoruz: (Bu projede sertifikalarla uğraşmamak için geliştirici modunda çalıştırılıp, tls/ssl özelliği kapatılmıştır. Production ortamında çalıştırmak için gerekli konfigurasyonları yapmanız gerek.)
EventStore.ClusterNode.exe --dev --enable-external-tcp --disable-external-tcp-tls
EventStore Admin UI Dashboard sayfasına giriyoruz.
https://localhost:2113/
Varsayılan Kimlik Bilgileri
Username: admin
Password: changeit
Eventlerimizi oluşturduk.
Core > Events > TodoListItem > TodoListItemCreated.cs
public class TodoListItemCreated : IEvent
{
public Guid TodoListItemId { get; set; }
public Guid TodoListId { get; set; }
public string Title { get; set; }
public string Description { get; set; }
public bool IsDone { get; set; }
}
Aggreatelerimizi oluşturduk.
Application> Aggregates > TodoListItemAggregate.cs
public class TodoListItemAggregate : BaseAggregate
{
public void Created(TodoListItem model)
{
if (CheckStreamName())
throw new StreamNotFoundException();
TodoListItemCreated todoListItemCreated = new()
{
TodoListItemId = model.Id,
TodoListId = model.TodoListId,
Title = model.Title,
Description = model.Description,
IsDone = model.IsDone
};
events.Add(todoListItemCreated);
}
public void Updated(Guid todoListItemId, bool isDone)
{
if (CheckStreamName())
throw new StreamNotFoundException();
TodoListItemUpdated todoListItemUpdated = new()
{
TodoListItemId = todoListItemId,
IsDone = isDone
};
events.Add(todoListItemUpdated);
}
public void Deleted(Guid todoListItemId)
{
if (CheckStreamName())
throw new StreamNotFoundException();
TodoListItemDeleted todoListItemDeleted = new()
{
TodoListItemId = todoListItemId
};
events.Add(todoListItemDeleted);
}
}
AggregateRepository oluşturduk.
Infrastructure > Repositories > AggregateRepository.cs
public class AggregateRepository : IAggregateRepository
{
readonly IEventStoreConnection _connection;
public AggregateRepository(IEventStoreConnection connection)
=> _connection = connection;
public async Task SaveAsync<T>(T aggregate) where T : BaseAggregate, new()
{
List<EventData> events = aggregate.GetEvents
.Select(@event => new EventData(
eventId: Guid.NewGuid(),
type: @event.GetType().Name,
isJson: true,
data: Encoding.UTF8.GetBytes(JsonSerializer.Serialize(
value: @event,
inputType: @event.GetType(),
new JsonSerializerOptions() { WriteIndented = true }
)),
metadata: Encoding.UTF8.GetBytes(@event.GetType().FullName))
)
.ToList();
if (!events.Any())
return;
await _connection.AppendToStreamAsync(aggregate.StreamName, ExpectedVersion.Any, events);
aggregate.GetEvents.Clear();
}
public async Task<dynamic> GetEvents(string streamName)
{
long nextSliceStart = 0L;
List<ResolvedEvent> events = new();
StreamEventsSlice readEvents = null;
do
{
readEvents = await _connection.ReadStreamEventsForwardAsync(
stream: streamName,
start: nextSliceStart,
count: 4096,
resolveLinkTos: true
);
if (readEvents.Events.Length > 0)
events.AddRange(readEvents.Events);
nextSliceStart = readEvents.NextEventNumber;
} while (!readEvents.IsEndOfStream);
return events.Select(@event => new
{
@event.Event.EventNumber,
@event.Event.EventType,
@event.Event.Created,
@event.Event.EventId,
@event.Event.EventStreamId,
Data = JsonSerializer.Deserialize(
json: Encoding.UTF8.GetString(@event.Event.Data),
returnType: Type.GetType(Encoding.UTF8.GetString(@event.Event.Metadata))
),
Metadata = Encoding.UTF8.GetString(@event.Event.Metadata)
});
}
}
CQRS Notificationlar aracılığıyla eventlerimizi event store'a kaydediyoruz.
Application > Notifications > TodoListItem > Created > TodoListItemCreatedNotification.cs
public class TodoListItemCreatedNotification : INotification
{
public TodoListItem Model { get; set; }
public class TodoListItemCreatedNotificationHandler : INotificationHandler<TodoListItemCreatedNotification>
{
private readonly IAggregateRepository _aggregateRepository;
private readonly TodoListItemAggregate _todoListItemAggregate;
public TodoListItemCreatedNotificationHandler(IAggregateRepository aggregateRepository, TodoListItemAggregate todoListItemAggregate)
{
_aggregateRepository = aggregateRepository;
_todoListItemAggregate = todoListItemAggregate;
}
public async Task Handle(TodoListItemCreatedNotification notification, CancellationToken cancellationToken)
{
_todoListItemAggregate.SetStreamName($"todolistitem-{notification.Model.Id}");
_todoListItemAggregate.Created(notification.Model);
await _aggregateRepository.SaveAsync(_todoListItemAggregate);
}
}
}
Projeyi çalıştırıp, todo list item ekledikten sonra;
Oluşan eventleri event store'a kaydettiğimiz basit bir uygulama yazdık.
Bu kodları daha detaylı incelemek için;
Github Proje Linki: AyazDuru.Samples.EventSourcingWithCQRSAndEventStore
Sağlıcakla kalın..
(⌐■_■)