CQRS / User Management — 1
Selamlar dostlar, yeni bir makale ile sizlerleyim. Takip eden arkadaşların bildiği üzere CQRS pattern uygulayarak bir kullanıcı yönetimi projesi geliştireceğimizi ve bunu makale serisi olarak yayınlayacağımı söylemiştim.(İncelemek isterseniz ilk ve ikinci makaleye göz atabilirsiniz)
Bu bağlamda projenin command kısmını bitirmiş bulunmaktayım, projenin query kısmına ise bir sonraki makalede göz atacağız. Hazırsanız başlayalım.
Docker
Öncelikle, projemizde kullanacağımız tüm dış yapıları docker container olarak ayağa kaldıracağımızı önceki makalede söylemiştim. Command projesinde PostgreSQL, Redis, RabbitMQ ve Query projesinde de MongoDB kullanacağımızdan dolayı, bu bileşenlerin hepsini docker üzerinde çalışır hale getirelim.
Öncelikle image dosyalarını pull edelim.
docker pull rabbitmq:latest
docker pull postgres:latest
docker pull redis:latest
docker pull mongo:latest
“docker image ls” komutu ile local docker’ımız üzerindeki image dosyalarını görebiliriz.
Şimdi her birini container olarak ayağa kaldıralım.
-docker run — name CqrsPostgresql -e POSTGRES_PASSWORD=123456 -d -p 7000:5432 postgres
-docker run -d — hostname my-rabbit — name CqrsRabbitmq -e guest-e RABBITMQ_DEFAULT_PASS=123456 -p 5672:5672 -p 15672:15672 rabbitmq:3-management
-docker run -d -p 7002:6379 — name CqrsRedis redis — requirepass “123456”
-docker run -d -p 7003:8081 — name CqrsMongo -e MONGO_INITDB_ROOT_USERNAME=mongoadmin -e MONGO_INITDB_ROOT_PASSWORD=123456 mongo
Artık containerları projemizde kullanabiliriz.
Proje Yapısı
Command projesinde mimari olarak onion architecture kullanıldı. Projede 5 adet katman bulunmakta, bunlar Domain, Application, Infrastructure, Persistence ve API’dır.
Domain : Projenin en temel yapı taşlarının bulunduğu katmandır.
Application : Tüm uygulamada kullanılan olan yapıların ve temel arayüzlerin bulunduğu katmandır.
Infrastructure : Dış teknoloji bağımlılıklarının, ve dış servislere isteklerin yönetildiği altyapı katmandır.
Persistence : Veri Tabanı işlemlerinin yapıldığı katmandır.
API : Projenin dışarıya açılan kapısıdır.
Teknik Yapılar
Distributed Lock
Projemizde redis kullanarak cache’leme işlemlerini yapmaktayız. Kurmuş olduğumuz bu cache mekanizmasını kullanan, distributed lock mekanizması mevcut.
Multithread yapılarda birden fazla kaynak tarafından paylaşılan veriler üzerinde tutarsızlıklar oluşabilmektedir. Veri tabanındaki bir kullanıcıyı birden fazla thread aynı anda işleyebilir, bir thread, sistemden bir kullanıcıyı silmiş ise, ve aynı anda başka bir thread o kullanıcı üzerinde güncelleme işlemi yapmaya çalışırsa ne olur? Program hataya düşecektir. Bu ve benzeri tutarsızlıkları önlemek amacıyla, projemizde basit düzeyde distributed lock mekanizması kullandık.
Distributed Lock, Redis üzerinden çalışır. Bir işlemde kilitlenmek istenen kaynak için, bir lock objesi oluşturulur ve redise kaydedilir. Process işlemini tamamladığında lock objesini kaldırır, ve bu kaynağı başka processler tarafından yeniden işlenebilir hale getirir. DistributedLock implementasyonunu aşağıda görebilirsiniz.
Burada tek bir redis container üzerinden lock örneği gerçekleştirdik, ancak profesyonel projelerde çok daha güvenli ve gelişmiş lock mekanizmaları gerekebilmektedir. Örneğin bu yapıda, redis instance’ımız down olduğu anda lock mekanizmamız çalışmaz hale gelecektir. Bu tarz sorunları çözen lock yapıları için Redlock ve benzeri lock mekanizmaları ve algoritmalarına göz atabilirsiniz.
Distributed Lock mekanizmasının kullanımına bakacak olursak, command ve handler objelerinin yönetimi için mediatr kütüphanesini kullanacağımızı belirtmiştik. Bu noktada, hem mediatr kütüphanesine olan bağımlılığı azaltmak, hem de tıpkı filter sınıflarındaki mantık gibi handler methotundan önce ve sonra işlemler gerçekleştirebilmek adına, kendi arayüzümüzü yazalım .
Obje yönetimi için bu arayüzü kullanacağız. Sonrasinda mediatr kütüphanesini kullanarak bu işlemi gerçekleştiren implementasyon sınıfımızı yazalım.
Görüldüğü üzere eğer dışarıdan bir lock objesi değeri alırsa, hazırlamış olduğumuz distributedlock mekanizmasını kullanarak lock alınmaya çalışılıyoruz. Eğer lock alamazsak bu işlemin kullanmak istediği kaynak veya kaynakların başka bir process tarafından kilitlenmiş ve işlenmekte olduğunu anlıyoruz, ve handler methotu çalıştırmadan response dönüyoruz. Lock alınırsa handler methot çalıştırılacak, sonrasında lock kaldırılacak ve response dönülecektir.
Event Publisher
Command ve query projeleri arasında veri tutarlılığını sağlamak için rabbitmq aracılığıyla asenkron mesajlaşma yöntemini kullanacağımızdan bahsetmiştik. Bu, proje yapımızı Consistency durumundan Eventual Consistency durumuna geçirecektir. Yani Command veri tabanında yapılan değişikliklerin query veri tabanına yansıması biraz zaman alacak, ancak elinde sonunda iki veri tabanı tutarlı hale gelecektir.
Burada mesaj iletişimi için “Event Driven Design” adı verilen olay güdümlü programlama gerçekleştirdik. Command projesinde bir veri üzerinde değişiklik olduğunda, bu işlemi haber etmek amacıyla bir event objesi rabbitmq kuyruğuna gönderilir. Kuyruğu dinleyen query projesi, bu event objesini aldığında ilgili değişikliği kendi veri tabanına yansıtmak amacıyla harekete geçer. Burada masstransit framework kullanarak olay güdümlü programlamayı oldukça pratik bir halde kullanabildik.
Öncelikle, bir event olarak tasarlanan her obje, IIntegrationEvent arayüzünü implemente etmelidir.
Bugün masstransit üzerinden yaptığımız işlemi yarın bir gün doğrudan rabbitmq client üzerinden, veya başka bir message broker üzerinden yapma ihtiyacı doğabilir. Bu yüzden Masstransit frameworke olan bağımlılığı düşürmek için, kendi arayüzümüzü yazıyoruz.
Bu arayüzü masstransit framework kullanarak implemente eden sınıfımıza göz atalım.
Görüldüğü üzere, IntegrationEvent listemiz mevcut. Business Service sınıfları içerisinde işlemler gerçekleşirken, servis sınıfları bu sınıfı kullanarak query tarafına göndermek istedikleri eventleri kayıt edebilmektedirler. Kayıt edilen eventler listede saklanır. Servis sınıfı içerisindeki işlem tamamlandığı zamanda da , Masstransit framework’ün bize sağladığı IPublishEndpoint arayüzü üzerinden ilgili eventleri publish etmektedir. Qeery projesinde bu eventler dinlenmekte ve consume edilmektedir.
Multithread yapıyı göz önünde bulundurarak, Bu sınıfı IoC Container’a “Scoped” olarak tanımlanmıştır.
Bu sayede her bir istek için ayrı bir IntegrationEventPublisher sınıfı oluşturulacak,her isteğin event listesi kendisine özel olacaktır. Farklı isteklerin farklı listelere event kayıt etmesi, bir isteğin kendisine ait olmayan eventleri publish etmesi gibi sorunların önüne geçilmiştir.
Mediatr Behaviour
Projemizde Mediatr kütüphanemizi kullandığımızı belirtmiştik. Transaction bütünlüğünü sağlamak, ve işlemlerin sonunda kayıt edilmiş eventleri publish etmek amacıyla bir pipeline sınıfı eklenmiştir.
Bu yapı sayesinde, servis sınıfları içerisinde yapılan tüm işlemler bir iç transaction olarak gerçekleştirilip, işlemin sonunda tüm işlemler veri tabanına commit edilmektedir. Commit işlemi başarılı olursa da, yapılan işlem esnasında kayıt edilmiş olan eventlar, query tarafına publish edilmektedir.
Projemizdeki teknik yapılara detaylıca göz attık. Şimdi, projede gerçekleştirilen işlemlerden ikisine örnek olarak göz atalım. Diğer tüm işlemleri kaynak kodlardan inceleyebilirsiniz.
Kullanıcı Ekleme
Controller tarafında kullanıcı ekleme işlemi aşağıdaki gibidir.
Servis tarafında ise gerçekleştirilen işlemler aşağıdaki gibidir.
Bu işlemin sonunda da yukarıda göz attığımız TransactionalBehaviour sınıfı araya girerek, transactionu commitleyecek ve register edilen eventleri publish edecektir.
Kullanıcıya Rol Atama
Controller tarafında kullanıcıya rol atama işlemi aşağıdaki gibidir.
Servis tarafında ise gerçekleştirilen işlemler aşağıdaki gibidir.
Görüldüğü üzere, işlemlere başlamadan önce ilgili user bilgisi için lock alınmaya çalışılmakta. Çünkü bu kullanıcıya rol atama işlemi yapılırken, aynı zamanda paralel olarak çalışan başka bir thread, bu kullanıcıya farklı roller atayabilir, veya veri tabanından kullanıcıyı silebilir. Bu sorunların yaşanmaması adına işlemin başında lock alınıp, işlem tamamlandığında lock kaldırılmaktadır.
Command projesi üzerinde daha bir çok işlem bulunmakta, ancak çok uzun olacağı için hepsini bu makale üzerinde inceleyemeyeceğiz. Proje üzerinde kullanılan teknolojilerden, teknik altyapılardan ve bir kaç operasyondan elimden geldiğince bahsetmeye çalıştım. Tüm kaynak kodlara buradan erişebilirsiniz.
Bir sonraki makalemiz son makale olacak, ve Query projesi üzerinde duracağız. O zamana kadar kendinize çok iyi bakın, görüşmek üzere.