Event Sourcing

Engin UNAL
Bilişim Hareketi
Published in
6 min readMar 1, 2022

Event Sourcing, uygulamanızın son durumunu kayıt altında tutmak yerine, onu mevcut durumuna getiren olay akışını kaydetme fikrine dayanan bir yazılım mimarisi konseptidir.

Bu konunun daha net anlaşılabilmesi için öncelikle konuyla ilgili terminoloji konusunda bilgi vererek başlayacağım. Event, state, projection gibi temel terimlerin ne ifade ettiğine bakalım.

Event

Event’ler, gerçekleşmiş bir işlemi temsil eden ve bu işlemle ilgili gereken tüm veriyi içeren kayıtlardır. Değiştirilemezdir(immutable) ve tarihsel sırada saklanırlar. Gerçekleşmekte olan değil gerçekleşmiş işlemlerin kayıtlarıdır, bu nedenle geçmiş zaman isimlendirmesiyle tutulurlar. Sistemi kullanan kullanıcılar doğrudan event veritabanına yazmazlar, yazma işlemi Command yapıları gerçekleştirilir. Bunu CQRS başlığında inceleyeceğiz.

Herhangi bir andaki sistem durumuna, kaydedilen event’leri kullanarak ulaşabilirsiniz. Kaydedilen event’ler öyle bilgiler içermelidir ki sistemin herhangi bir andaki durumunu bu event’lerle tekrar oluşturabilmeliyiz.

İsminde event geçmesi Event Driven Architecure ile karıştırılmasına neden olabiliyor. Event Driven Architecure, sistemin üyeleri arasındaki haberleşmenin event ile yapılmasını temel alır. Event Sourcing ise event verisinin tarihsel sırada kaydedilmesi ile ilgilidir. Notifikasyon veya Message Bus yapılarındaki event mantığı gibi düşünülmemelidir. Bu nedenle event veritabanını Message Bus olarak kullanmaktan kaçınılmalıdır.

State

Sistemin o andaki durumunu ifade eder. Genellikle geliştirilen uygulamalar, verilerini ilişkisel veya NOSQL veritabanlarında tutmakta ve bu veriler sistemin son durumunu saklamaktadır. Böylece durum değişikliği yapan event kayıtları yerine son durum saklanmaktadır.

Örneğin sipariş tablomuz olsun. Burada siparişin son durumu saklandığından durum değişiklikleri güncelleme olarak gelecek ve veritabanındaki sipariş kaydı, güncellenmiş değerleri içeren yeni kayıtla değiştirilmiş olacaktır.

Örnekteki gibi sipariş bilgisini değiştirmek state değişikliği anlamına gelir ve uygulamamız yeni bir state’e geçer. Başta da belirttiğim gibi pek çok uygulama tasarım gereği state değişikliklerini değil son state bilgisini tutar. O ana kadar olan state değişiklikleri tarihsel bir sırada saklanmaz veya herhangi bir andaki sistem state fotoğrafını almak isterseniz alamazsınız. Bu mimarilerde state üzerindeki değişiklikler genelde log yapılarıyla takip edilir.

Projection

Projection, event akışını kullanarak state bilgisini oluşturmanın bir yoludur. Kaydedilmiş event’ler kullanılarak istenen bir andaki state bilgisini oluşturmak için kullanılır. Son state bilgisini bir yerde tutup bunu güncellemek, sorgulama işlemlerinin hızlı olabilmesi için başka bir veri kaynağına özetlenmiş bilgi aktarımı vs. farklı şekillerde kullanımları vardır.

Sistem, sorgulama sürecinde doğrudan event’ler üzerinden sorgulama yapmaz. Sorgular, bunun için optimize edilmiş projection’lara yapılır. Projection yapan yapılara projector denir. Projector, event akışını işleyen ve bundan projection üreten yapılardır. Projector’ler birbirinden bağımsız yapılardır ve diğer projector’ler ile bir paylaşım yapmazlar. Bu sayede istenildiği zaman değiştirilip yeniden üretilebilirler bu da sistemin işleyişini etkilemez.

Örnekle devam edersek; Son state bilgisini tuttuğunuz bir veritabanımız var diyelim. Doğal olarak state bilgisini sürekli güncellemeniz gerekir. Her event neticesinde bunu işleyip veritabanınızı günceller ve son state’i bu şekilde tutabilirsiniz. Veya sorgulama için düşünelim, sorgulama yapılması için bir servis oluşturmak istedik ve yapılan sorgulamanın tüm event’lere erişip veriyi toplayıp sonuç dönmesini performans olarak tercih etmiyoruz. Bu durumda event’ler akarken sorgulama için kullanacağımız veritabanının da güncellenmesi ve her sorgunun bu veritabanına yapılması işimizi çözecektir.

Temelde projection, event’lerin başka bir veri modeline dönüştürülmesi işlemidir.

Projection is just a little piece of code that goes over an event stream and it produces a piece of state. [Greg Young]

Reactor

Event Store’daki(event veritabanı) event’leri dinleyen ve kendi içinde tanımlanmış kurallara göre bu event’lere reaksiyon veren yapılardır. Örneğin kullanıcı oluşturma event’lerini dinleyen bir reactor olsun, yeni bir kullanıcı yaratıldığında kullanıcının email adresini doğrulamak için bu adrese kontrol email’i göndersin. Burada reactor, tanımlanmış iş kuralına uygun event’leri yakalar yani email gönderilecek kullanıcıların event’lerini algılar devamında bunlara email gönderir ve sonucunu yine event store’a yazar. Özetle reactor, aynı projector gibi event kayıtlarını işleme alır, reaksiyon vereceği event’leri yakaladığında bunlara bir aksiyon üretir.

CQRS Kullanımı

CQRS yazımda Command ve Query yapılarına değinmiştim. Event Sourcing uygulamalarında genellikle CQRS ile birlikte kullanılmaktadır. Süreç nasıl ilerliyor Command ve Query tarafından kısaca bakalım.

Command : State değişimi amaçlı bir request gelir buna command diyelim ve Command Handler tarafından karşılanır. Request kontrol edilir ve geçerliyse işleme alınır ve event olarak kaydedilir. Yani Command Handler aldığı bir command nesnesini event’e dönüştürür, bu event geçmiş zamanda gerçekleşmiş bir olay olarak kaydedilir. Event Sourcing şu anda gerçekleşmekte olanı değil gerçekleşmiş olanı yani tarihçeyi tutar. Bu nedenle event geçmiş zaman adıyla kaydedilir.

Query : Event verilerinden sonuç döndüren yapılardır. Event’leri temel alır, event veritabanını tarayarak istenen sonucu döndürür. Fakat bazı durumlarda bu işlemin uzun sürmesi veya istenildiği kadar hızlı olmaması gibi problemler karşımıza çıkar. Bu durumda ise projection kavramı devreye girer ve event’ler kullanılarak hazırlanmış view yapıları sonuç olarak döndürülür.

Özet

Buraya kadar anlatılanları şema üzerinde incelersek:

Bazı önemli kavramları incelediğimize göre bunları kullanarak Event Sourcing konusunda daha rahat tanım yapabiliriz. Event Sourcing temel veri kaynağı olarak event yapılarını kullanan ve bu eventlerden sistemin istenen bir andaki state’ine ulaşabildiğimiz mimari tasarım kalıbıdır. Event’ler değiştirilemezdir(immutable), tarihsel sırada akış olarak saklanır, sadece event’ler kullanılarak sistemin istenen bir andaki state’ini oluşturabileceğimiz yeterli veriyi içermelidir.

Event Sourcing ensures that all changes to application state are stored as a sequence of events. [Martin Fowler]

Nerelerde Kullanılıyor?

Event Sourcing çok geniş bir alanda aslında kullananların bazılarının farkında olmadan uyguladıkları bir tasarımdır. Birkaç örnek ile devam edelim.

Örneğin bankacılık alanındaki hesap kayıtları güzel bir örnektir. Hesabınızdaki bakiye, gerçekleşen işlemlerden üretilebilen bir state bilgisidir. Yani para ekleme, çekme veya diğer işlemlerin her biri ayrı birer kayıt olarak saklanır ve gerektiğinde kullanılarak state üretilir. Her bakiye sorgulamasında elbette baştan itibaren tüm event kayıtları çekilip hesaplama yapılmaz. İşlemin hızlı olabilmesi için projection yapılmıştır ve her gelen işlem sonrası bakiye’ye projection yapılarak güncel tutulur. Diyelim ki bakiye bilgisinde uyuşmazlık var, bu durumda geçmişten bugüne kadar tüm hareketler hesaplanarak bakiye tekrar oluşturulabilir.

Muhasebede kullanılan Defter(Ledger) kayıtları da yapılan tüm işlemlerin saklandığı kayıtlardır. Veya devlet servis kayıtları da buna örnektir, devlet ile ilgili işlemlerin kayıtlarının değiştirilemez olarak tutulması ve çok kullanıcının erişeceği düşünülürse ölçeklemeye imkan verebilmesi önemli gereksinimledir.

İlginç gelebilecek diğer bir örnek ise Blockchain mimarileridir. İlgi alanım olması nedeniyle bu yapıları araştırdığım yıllardaki yazılarımı incelemenizi öneririm. Blockchain yapıları veriyi genellikle distributed ledger yani dağıtık defter olarak paylaşır. Veri paylaşımı tamamen ayrı bir yazı konusu biz şimdi tutulan veri üzerinden devam edelim yani dağıtık defter’deki defter’i ele alalım. Yaygın olarak kullanılan blockchain sistemlerinde defter, tüm hareketlerin saklandığı ve başlangıçtan itibaren üzerine eklenerek büyüyen veri havuzudur. Bu havuzda tüm transfer işlemleri tutulur. Bu da sistemin istenen bir andaki durumunu üretme imkanı sağlar. Blockchain mimarilerinden en bilineni Bitcoin’i ele alalım.

Bitcon, yapılan her işlemi baştan itibaren veritabanında saklı tutar. Yapılan her transfer UTXO kuralıyla korunur. Blok’tan birşey silinmez(append only), transaction’lar değiştirilemez(immutable). Kullandığımız wallet’lar ise aslında event verisine projection yapan uygulamalardır. Temelde wallet’ın hesabınızı görüntülerken yaptığı iş, size ait tüm işlem geçmişini toplar, projection yapar ve son durumu yani güncel state bilgisini üretir.

Artıları / Eksileri ?

  • Sistemi, istenildiği bir ana tekrar döndürme imkanı var. Yani sistem state’ini tekrar oluşturabilme avantajı sağlar.
  • Sadece ekleme(append only) model ile çalıştığından önemli bir performans ve ölçekleme avantajı sağlar.
  • Audit log’lara bağlı kalınmasına gerek kalmıyor, event’ler zaten tüm gereken bilgiyi içerir.
  • Data model, domail model ile bağlı olmak zorunda değil(decoupled).
  • Versiyonlama çok hassas bir konu bunun iyi yapılması zaman alabilir.
  • Event schema değişimi olduğu durumlarda bunun yönetilmesi de daha zor ve zaman alacaktır.
  • State yönetimi ve projections konularında yazılım ekiplerinin iş yükünü artıracaktır.

Kısa Bir Örnek

Basit bir kodlama örneği ile yazıyı sonlandıracağım. Örnekte en basitinden bir hesaba para ekleme/çıkarma işlemlerini modelleyelim. Bunu yaparken kodun çok kısa olmasını istediğimden veritabanı kullanmayıp bir liste içerisine event verisini kaydedeceğim.

Öncelikle entity ile başlayalım. AccountEntity sınıfında Balance değeri tutulmakta ve Deposit, Withdraw metodlarıyla hesaba para ekleme ve hesaptan para çekme işlemleri yapılmakta. Bu işlemlerin her biri bir event üretmekte. Bu event’ler AccountEvents sınıfında bulunmakta.

Event’ler _eventStore isminde bir listede tutulmakta, domain tarafından yani account tarafından üretilen her event bu listeye eklenmekte.

Örneğin Deposit metodunu çağırdığımızda para ekleme işlemi Deposited isminde bir event üretip fırlatmakta devamında bu event listeye kaydedilirken event’i handle eden Apply metodu Balance üzerinde değişikliği yapmakta.

Örnek uygulamada yeni bir account açılıyor, 15 TL ve 5 TL para ekleme yapılıyor, 10 TL çekim işlemi sonrası Balance değeri alınıyor. Sonrasında bu event’ler alınarak sıfırdan bir Account nesnesi için sadece bu event’ler kullanılarak state oluşturuluyor ve event’lerin sonucundaki Balance state değeri yazdırılıyor.

Github linki :

Kısa kısa bu konuyla ilgili bazı başlıklara değinmeye çalıştım. Bu yola çıkacaklar için söyleyebileceğim; Araştırıp anlamaya çalıştığım süreçte diyebileceğim şimdiye kadar alışık olmadığım yaklaşımlar ve farklı sorunlar içermesi başta zorlayıcı gelebilir. CAP teoreminin AP tarafına yakın görünüyor. Bu yazıda amacım production’a uygun bir yapının anlatımını yapmak yerine (ki yapı oldukça karmaşıklaşacaktır) temel olarak nedir? Neler anlatlıyor? Sorularına özet cevaplar verebilmekti.

Benim de yararlandığım kaynaklar olan Greg Young, Martin Fowler gibi ustaların seminerleri ve kitaplarını önermekteyim, ayrıca Alexey Zimarev’in uygulama adımındaki yol gösterici yazıları ve eğitimleri de oldukça değerli incelemenizi öneririm.

Teşekkürler.

Engin ÜNAL

--

--