Önceden servislerimiz aşağıdaki gibi olurdu.
Bu servisimiz yüzlerce satır koddan oluşurdu. Bu karmaşadan kurtulmak için CQRS ile servislerimizi "Command" ve "Query" olmak üzere ikiye böldük. CRUD işlemlerimizi "Command" servisleri ile, sorgulama işlemlerimizi "Query" servisleri ile yaptık. Ortaya aşağıdaki gibi iki ayrı servis çıktı.
Bu şekilde az da olsa okunabilirlik sağlamış olduk ama halen god class'lar ortaya çıkıyordu. Mediator ile birlikte "UseCase"ler hayatımıza girdi. Bu sefer her bir "UseCase" için ayrı handler'lar oluşturmaya başladık. Ortaya aşağıdaki gibi 13 ayrı handler çıktı.
Bu yaklaşım ile her bir handler sadece kendi işini yapar oldu. Kod satırları daha da azaldı, okunabilirlik daha da arttı. Bu da bize kolay yönetilebilirlik sağladı.
Bu sefer performans üzerinde çalıştık. Ortaya çıkan her veritabanı teknolojisi farklı özellikler sunuyordu. Kimisinin yazma hızı çok iyiydi, kimisinin okuma hızı. Madem her şeyi böldük… "Command" işlemleri için yazma hızı iyi veritabanı teknolojilerini, "Query" işlemleri için okuma hızı iyi veritabanı teknolojilerini seçtik. Ortada bir sorun vardı. İki farklı veritabanı teknolojisindeki verileri senkronize etmemiz gerekiyordu.
Bu noktada "Messaging" teknolojileri imdadımıza yetişti. Command handler'da verileri veritabanına yazdıktan sonra aynı verileri kuyruklara mesaj olarak gönderdik. O kuyrukları dinleyen consumer'lar yazdık. Consumer'lar ile gelen mesajları "Query" işlemleri için seçtiğimiz veritabanına aktardık. Bu şekilde iki farklı veritabanı teknolojileri arasında senkronizasyon sağladık.
Daha sonra her şeyin CRUD olmadığını anladık. Her şey veriyi eklemek, güncellemek ve silmek değildi. Neyi güncelliyorduk?
Meetup uygulamasını düşünelim.
Örneğin bir etkinlik tanımlıyoruz. Bu bir insert'dir.
Etkinliği tanımladıktan sonra etkinlik içeriğini güncelleyebiliyoruz. Bu bir update'dir.
Etkinliğe katılmayı düşünenler etkinliğe gideceklerini belirtiyor. Bu etkinlik için update midir? Update değilse nedir?
Etkinlik başlamadan 1 saat önce etkinliğe kayıt alınmaması için etkinliğin durumunu "Stopped" olarak işaretliyoruz. Peki bu bir update midir?
Etkinlik sona erdikten sonra etkinliğin durumunu "Completed" olarak işaretliyoruz. Peki bu update midir?
Etkinlik sahibi olarak etkinliğe fotoğraflar yüklüyoruz. Bu da mı update'dir?
Etkinliğe katılanlar etkinliğe yorum giriyorlar. Update mi sizce?
Gördüğünüz gibi her şey Insert, Update ve Delete olarak düşünülemezdi. Aslında her şey aşağıdakiler gibi bir event'ti.
- MeetupRegistered
- MeetupJoined
- MeetupStopped
- MeetupCompleted
- MeetupAddedPhoto
- MeetupAddedComment
Her şey bir event ise verilerimizi klasik yöntemdeki gibi ilişkili tablolarda satırlar halinde tutmaya gerek yoktu. O zaman aşağıdaki görseldeki gibi bir tane tablo oluşturup her event'i bu tabloya bir satır olarak ekledik.
Baktığımızda tanımladığımız etkinlik bu satırların birleşiminden ibaret. Her bir event'in versiyon numarası var. Aşağıdan yukarıya doğru birleştirerek çıktığımızda aşağıdaki gibi bir sonuç ortaya çıkıyor.
"Command" işlemlerinde verilerimizi yukarıdaki gibi event olarak bir veritabanına kaydediyoruz dedik. Fakat bu satırlarda sorgulama yapamayacağımız için bu event'leri aynı zamanda "Query" için seçtiğimiz veritabanına da yazmamız gerekli. Örneğin; meetup uygulamasında "tamamlanmamış etkinlikleri" göstermek istediğimiz zaman yukarıdaki event'lerde bir sorgulama yapamayız.
Önceki paragraflarda bahsettiğim gibi etkinlik bilgilerini "Command" işlemlerinde veritabanına event olarak yazdıktan sonra bu bilgileri bir "messaging" kuyruğuna göndermeliyiz. Bu kuyruğu dinleyen bir consumer yazmalıyız. Bu consumer aracılığıyla "MeetupRegistered" event'lerini dinleyip gerekli bilgiler ile "Query" veritabanına etkinlik bilgilerini yazmalıyız. Yine aynı şekilde "MeetupCompleted" event'lerini de dinleyip "Query" veritabanından eklinlik bilgilerini silmeliyiz.
Yukarıda da gördüğünüz gibi "Command" bölgesinde API'den gelen event'ler event store'a yazılır. Bu event'ler aynı zamanda kuyruğa gönderilir. "Projection" bölgesinde kuyruktaki mesajlar işlenerek query store'a yazılır. "Query" bölgesinde sorgulama işlemleri yapılarak API'den sonuç döndürülür.
Event Sourcing dediğimiz şey tam olarak budur. Aggregate, Projection, Snapshot vb. gibi kendine has terimleri var. Peki "Command" için, "Messaging " için ve "Query" için seçeceğimiz teknolojiler ne olabilir?
Event'ler için "MSSQL" kullanılabilir. "Messaging" için "RabbitMQ" kullanılabilir. "Query" için ise "RavenDB, ElasticSearch, Couchbase" gibi teknolojiler kullanılabilir. Fakat Event Sourcing için .NET dünyasında "Event Store" adında bir teknoloji var. Bu teknoloji "Aggregate" ve "Projection" için çözüm sunmaktadır. Yani event'leri kaydedebileceğimiz store'u sağlamakla birlikte "Query" veritabanlarına kayıt atabilmemiz için gerekli olan "Messaging" ve "Projection" hizmetini de sağlamaktadır.
Blog'umda teorik bilgilerden çok pratiğe yer veriyorum. Yani Tutorial tarzı yazılar yazmayı tercih ediyorum. Fakat Event Sourcing için öncesinden teorik bilgi aktarmak istedim. Yakın zamanda teoriyi pratiğe dökeceğimiz bir video ile karşınıza gelmek niyetindeyim. O video'da uzun uzadıya şu nedir bu nedir gibi konulara girmeden örnek bir uygulama geliştireceğim. Bu sebeple bu teorik yazı ile video'ya temel hazırlamak istedim.
Kolay gelsin.
Yorumlar
Çok güzel bir anlatım olmuş. Karşımıza çıkacak canavarları, ve bu canavarları yenmek için izlenebilecek yöntemleri kısa ve öz bir şekilde betimlemişsiniz.
Biraz geç keşfettim ama büyük bir merakla okumaya devam edeceğim yazılarınızı.
Kolay gelsin.
Herhangi bir serviste hata olması durumunda sistemi tutarlı hale getirebilmek adına bir dizi işlemi gerçekleştirmek için saga pattern'ini kullanıyoruz. Özetle asıl amaç rollback ve retry mekanizmaları ile hizmetler arası tutarlığı sağlamak. Business'a göre değişkenlik gösterebilir ama bunu uygulamak uygulama içerisindeki karmaşıklığı artırabilir. Ayrıca saga sürecindeki iş bittiğinde event'ler kaybolacaktır.
Event Sourcing her şeyi bir event olarak kaydettiği için atomic'liği çok basit bir şekilde sağlıyor. En önemlisi herhangi bir zamanda uygulamanın state'ini olayları baştan sona tekrar uygulayarak yeniden oluşturmasına imkan vermesi. Bu da canlı ortamda ortaya çıkan geçici hataların telafisine imkan verir.
Teknik sebeplerden dolayı video konusunda henüz sonuç alamadım. Sözüm yerde kalmasın diye de videoda anlatacaklarımı seri yazı halinde yazmak istedim. Şimdilik iki seri yazıyı yazdım. Devamı gelecek.
İlginiz için teşekkür ederim.
Ben teşekkür ederim. Event Store'un birinci bölümü ile devam edebilirsiniz. Diğer bölümleri de yakında yazacağım.
https://www.ahmetkucukoglu.com/asp-net-core-ile-event-sourcing-01-store/