Derinlemesine yazılım eğitimleri için kanalımı takip edebilirsiniz...

Asp.NET Core + MongoDB + Event Store İle Event Sourcing Read Data Store Uygulaması(Örneklendirme)

Merhaba,

Önceki Asp.NET Core + Event Store İle Event Sourcing Uygulaması(Örneklendirme) başlıklı yazımda bir veri üzerinde yapılan tüm işlemleri Event Store tool’u ile kayıt altına almış ve böylece Event Sourcing’in Write Data Store kanadını tamamlamıştık. Bu içeriğimizde ise ilgili verilere dair sorgulama ihtiyaçlarına istinaden Event Store tool’undaki event’leri şartlı sorgulayamadığımızdan/sorgulayamayacağımızdan dolayı Event Sourcing’in Read Data Store kısmıyla ilgilenecek ve gelen event’lerin bir bütünü temsil edecek şekilde sadece read işlemleri için herhangi bir veritabanına kaydedilmesini ve işlenmesini sağlamış olacağız. Bu veritabanı relational olabileceği gibi NoSQL bir yaklaşımı da benimsemiş herhangi bir teknoloji olabilecektir. Bizler bu içeriğimizde MongoDB üzerinden örneklendirme yapıyor olacağız. Anlayacağınız güzel ve heyecanlı bir serinin devamı niteliğinde olacak bu içeriğimiz, sizlerde yaşattığı kadar bende de heyecan yaratmakta olduğu için hiç vakit kaybetmeden konuya giriş yapalım istiyorum…

Başlarken

Asp.NET Core + MongoDB + Event Store İle Event Sourcing Read Data Store Uygulaması(Örneklendirme)

Yapılan değişiklik neticesinde eklenen Shared katmanı…

İlk olarak, yukarıda konuyla alakalı olarak referans ettiğim ilgili makalede sunulan mimaride ufakta olsa bir değişiklik yaparak başlamak istiyorum. Bu değişiklik, bu içeriğimizde uygulayacağımız Read Data Store operasyonunun sorumluluğunu sıradan bir Console uygulamasına vereceğimizden kaynaklanmaktadır. Bu console uygulaması, Event Store’a işlenen herhangi bir event durumunda subscribe olunan messaging üzerinden tetiklenecek ve veritabanında ilgili data’ya dair create, update yahut delete operasyonlarını gerçekleştirecektir. Haliyle önceki yazıda sunulan Model ve Event Type class’larının bu console uygulamasından erişilebilir olması gerekecektir. Haliyle mimaride bahsedilen class’ları her iki uygulamadan da erişilebilir hale getirebilmek için ‘Shared’ isimli class library’e almış ve diğer iki uygulama tarafından referans etmiş bulunmaktayım. Yapılan değişiklikleri daha net anlayabilmek için içeriğimizin sonunda paylaşmış olduğum projenin son halini indirip fiziksel gözlem yapabilirsiniz.

MongoDB.Driver ve EventStore.ClientAPI Kütüphanelerinin Yüklenmesi ve Temel Konfigürasyonlar

İlk olarak Read Data Store operasyonunu yürütecek olan Console uygulamamızda MongoDB ve Event Store işlemlerinin yürütülebilmesi için sırasıyla MongoDB.Driver ve EventStore.Client kütüphanelerinin yüklenmesi gerekmektedir.

Ardından her iki sunucuyla ilgili konfigürasyonları aşağıdaki gibi gerçekleştirelim;

MongoDBConfiguration:

    static public class MongoDBConfiguration
    {
        static IMongoDatabase database;
        public static void GetDatabase()
        {
            if (database == null)
            {
                MongoClient mongoClient = new("mongodb://localhost:27017");
                database = mongoClient.GetDatabase("EventStoreReadDB");
            }
        }
        public static IMongoCollection<T> Collection<T>(string collectionName)
        {
            GetDatabase();
            IMongoCollection<T> collection = database.GetCollection<T>(collectionName);
            return collection;
        }
    }

Yukarıdaki kod bloğunu incelerseniz eğer ‘GetDatabase’ metodu içerisinde veritabanı yoksa oluşturulmakta ardından ‘Collection’ metodu içerisinde ise parametre değerine uygun collection geriye dönülmektedir.

EventStoreConfiguration:

    public static class EventStoreConfiguration
    {
        public static async Task<IEventStoreConnection> Connect()
        {
            IEventStoreConnection connection = EventStoreConnection.Create(
              connectionString: "ConnectTo=tcp://localhost:1115;DefaultUserCredentials=admin:changeit;UseSslConnection=true;TargetHost=eventstore.org;ValidateServer=false",
              connectionName: "Console_Application",
              builder: ConnectionSettings.Create().KeepReconnecting()
             );

            await connection.ConnectAsync();
            connection.Connected += (object sender, ClientConnectionEventArgs e) => Console.WriteLine("Event Store dinlenmektedir...");
            return connection;
        }
    }

Bu kod bloğunda ise Event Store’a bağlantı sağlamış olan bir connection yaratılmaktadır.

Read Data Store’a Event’lerin İşlenmesi

Event Store’a event eklendikçe Read Data Store veritabanındaki(MongoDB) ilgili veriye bu event’leri işleyebilmek için yukarıda oluşturulmuş olan ‘EventStoreConfiguration’ konfigürasyonundaki connection’a aşağıdaki extension metodun yazılması çözümlerden biri olacaktır:

    static public class StreamSubscriber
    {
        public static async Task UserSubscribe(this IEventStoreConnection connection, IMongoCollection<User> userCollection)
        {
            await connection.SubscribeToAllAsync(
                resolveLinkTos: true,
                eventAppeared: async (eventStoreSubscription, resolvedEvent) =>
                {
                    //Return Type class'ı farklı bir class library ya da proje de ise bu şekilde metinsel olarak nitelendirilip
                    //Type.GetType(...)'a verilmelidir.
                    var returnType = $"{Encoding.UTF8.GetString(resolvedEvent.Event.Metadata)}, Shared, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null";
                    Type eventType = Type.GetType(returnType);
                    object @event = JsonSerializer.Deserialize(Encoding.UTF8.GetString(resolvedEvent.Event.Data), eventType);

                    User user = null;
                    UpdateDefinition<User> update = null;
                    switch (@event)
                    {
                        case UserCreated o:
                            //User'ı veritabanına ekle
                            user = new()
                            {
                                Id = o.UserId,
                                Email = o.Email,
                                EmailApprove = o.EmailApprove,
                                Name = o.Name,
                                UserName = o.UserName
                            };
                            await userCollection.InsertOneAsync(user);
                            break;
                        case UserNameChanged o:
                            //User'ı veritabanından çek ve adını güncelle
                            update = Builders<User>.Update.Set(u => u.Name, o.NewName);
                            await userCollection.UpdateOneAsync(f => f.Id == o.UserId, update);
                            break;
                        case UserEmailApproved o:
                            //User'ı veritabanından çek ve email'i onayla
                            update = Builders<User>.Update.Set(u => u.EmailApprove, true);
                            await userCollection.UpdateOneAsync(f => f.Id == o.UserId, update);
                            break;
                    }

                },
                subscriptionDropped: (eventStoreSubscription, subscriptionDropReason, exception) => Console.WriteLine($"Bağlantı kopmuştur. {subscriptionDropReason}")
                );
        }
    }

Yukarıdaki kodun izahını yapmamız gerekirse eğer ‘UserSubscribe’ metodu içerisindeki 5. satırda çağrılan SubscribeToAllAsync metodu eşliğinde Event Store’da ki tüm stream’lere subscribe olunmaktadır. 11 ile 13. satır aralığında o anda JSON olarak eklenen event’in hangi türde(EventType) olduğu Metadata bilgisinden edinilerek o tür bir object’e dönüştürülme işlemleri gerçekleştirilmektedir.(Çünkü önceki makalede bu bilgiyi ilgili event’in Metadata’sına biz koymuştuk) 17 ile 41. satır aralığında ise ilgili event’in hangi türde olduğu tespit edilerek gerekli işlemler gerçekleştirilmektedir. Misal; eğer event türü UserCreated ise ‘userCollection’a ekleme işlemi gerçekleştirilmekte, yok eğer UserNameChanged ise ilgili data elde edilip adı değiştirilmekte yahut UserEmailApproved ise yine ilgili data elde edilerek bu seferde email onayı verilmektedir.

Farkındaysanız eğer, Event Sourcing operasyonunda Write Data Store’a eklenen event her neyse bunun Read Data Store’a yansıması Metadata üzerinden taşınan class türü bilgisiyle sağlanmaktadır. Bunun için yazımızın başında bahsedilen ‘Shared’ isimli katman oluşturularak ilgili class’ların her iki data store’u besleyen uygulama taraflarından erişilebilir olması sağlanmıştır. Hatta 11. satıra göz atarsanız, farklı bir projede ki class’ın object’ini deserialize işlemi ile elde etmek icap ettiğinde bir tek class’ın fullname değeri yeterli olmamakta ayrıca yanına {Class Name}, Shared, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null bilgisinin de eklenmesi gerekmektedir. Bu da bu tarz durumlar için ayrı bir genel kültür mahiyetinde bilgidir.

Program.cs’in Yapılandırılması

Tüm bu yapılan konfigürasyonlar ve abonelikler neticesinde console uygulamasındaki ‘Program.cs’ dosyasının içeriği aşağıdaki gibi olacaktır.

    class Program
    {
        static async Task Main(string[] args)
        {
            IEventStoreConnection connection = await EventStoreConfiguration.Connect();

            IMongoCollection<User> userCollection = MongoDBConfiguration.Collection<User>("users");

            await connection.UserSubscribe(userCollection);

            Console.Read();
        }
    }
Test Edelim

Şimdi yaptığımız bu çalışmalar neticesinde event sourcing yapılanmasını test edelim.

  • Test Adımı 1 : Kullanıcı ekleme(UserCreated)
    Request Read Data Store
    Asp.NET Core + MongoDB + Event Store İle Event Sourcing Read Data Store Uygulaması(Örneklendirme) Asp.NET Core + MongoDB + Event Store İle Event Sourcing Read Data Store Uygulaması(Örneklendirme)
  • Test Adımı 2 : Kullanıcı adını değiştirme(UserNameChanged)
    Request Read Data Store
    Asp.NET Core + MongoDB + Event Store İle Event Sourcing Read Data Store Uygulaması(Örneklendirme) Asp.NET Core + MongoDB + Event Store İle Event Sourcing Read Data Store Uygulaması(Örneklendirme)
  • Test Adımı 3 : Kullanıcı e-mail’ini onaylama(UserEmailApproved)
    Request Read Data Store
    Asp.NET Core + MongoDB + Event Store İle Event Sourcing Read Data Store Uygulaması(Örneklendirme) Asp.NET Core + MongoDB + Event Store İle Event Sourcing Read Data Store Uygulaması(Örneklendirme)

Evet… Görüldüğü üzere Event Store’da ki tüm event’lere karşılık ilgili data üzerinde gerekli işlemler gerçekleştirilmiş ve böylece event sourcing pattern’ının bizlere sunduğu tasarım uygulanmıştır. Haliyle artık nihai olarak verilerimiz salt bir değerden ziyade olaylar zincirinin bir bütün olarak bir araya gelmesiyle daha geniş anlam kazanmakta ve ihtiyaç doğrultusunda bu anlamı gözlemleyebilmek için event store tool’undan ilgili dataya özel bir sorgulama ile data’nın yaşam sürecini seyredebilmekteyiz.

Asp.NET Core + MongoDB + Event Store İle Event Sourcing Read Data Store Uygulaması(Örneklendirme)

Mevcut bir datanın var olduğu sürece yaşadığı tüm etkinlikleri gözlemleyebilmenin getirdiği avantajı tadabilmek için bir örnek…

İlgilenenlerin faydalanması dileğiyle…
Sonraki yazılarımda görüşmek üzere…
İyi çalışmalar…

Not : Örnek projeyi indirebilmek için buraya tıklayınız.

Bunlar da hoşunuza gidebilir...

Bir cevap yazın

E-posta hesabınız yayımlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir

*