Vertical Slice Architecture Example

Samet Çınar
SabancıDx
Published in
5 min readJun 27, 2021
Vertical Slice Architecture

Vertical Slice mimarisini kullanarak örnek bir proje oluşturdum. Proje içerisinde Vertical Slice mimarisi ile beraber MediatR, CQRS, Migration gibi kütüphanelerde kullandım. “Vertical Slice Architecture Nedir?” sorusunun cevabı için “N-Tier Arcihtecture” ile kıyasladığım yazıyı inceleyebilirsiniz.

Örnek projede kullandığımız “Mediator Pattern” yaklaşımını bizler için oldukça basit bir hale getiren “MediatR” paketini kullanıyoruz. Bu pakette “Vertical Slice Architecture” fikrini ortaya atan kişi tarafından oluşturulan bir kütüphane. Jimmy Bogard’ın aynı zamanda “AutoMapper” adında bir çoğumuzun kullandığı bir kütüphanesi daha var. Bu yüzden Jimmy abimizi takip etmenizi öneririm. :)

Mediator Pattern

Mediator Pattern’ı anlatan bir çok güzel görsel var buda onlardan bir tanesi. Bir trafik polisinin görevini düşünün trafikte seyir halinde devam eden araçlar kendileri arasında “sen geç abi, dur ben geçeyim” tarzında tartışmalar girdiğinde ortaya çıkan sonucu düşünün. Bir de trafik polisinin araç araç yaptığı yönlendirme sonucunda düzenli geçişlerden çıkan sonucu düşünün.

Bizimde MediatR’a vereceğim görev şu olacak. Biz Query ya da Command feature için belirlediğimiz request cağırıldığında ilgili Handler’ın cağırılmasını bize sağlayacak. Yani method cağırırken aslında sadece request nesnemizi belirleceğiz mediatR bize ilgili handlerı adresliyecek. Bunun detaylı örneğini birazdan konuşacağız.

MediatR’ın sağladığı en büyük avantajlardan biride “PipelineBehavior”. Vertical Slice’da bir request bir feature demiştik.(Örn : GetUserById)
Tüm aksiyonlardan için tek bir yerden validation, loglama, cacheleme yapmak istiyorum.

Bu davranışları tek bir yerden yönetilme imkanını “PipelineBehavior” ile yakalıyoruz. Bir request işlemi gerçekleşirken akışın arasına girip girdi ve çıktıya erişebilmemizi sağlıyor.

Ben örnek projemde sadece “ValidationBehavior” kullandım. Bir örneğini gördüğünüzde diğer davranışları siz zaten rahat bir şekilde yapabileceğinizi göreceksiniz.

ValidationBehaviour

IPipelineBehavior ara yüzünü implemente ederek generic TRequest nesnesini validate işlemine dokup olumsuz bir durumda TResponse ile geri dönüş yapabiliyoruz. Middleware gibide düşünebiliriz.

services.AddTransient(typeof(IPipelineBehavior<,>), typeof(ValidationBehaviour<,>));

Startup içerisinde tanımlamasını bu şekilde yapacağız. Birden fazla pipelineBehavior olabilir. Aynı şekilde kayıt edebiliriz ancak ekleme yaparken hangi davranışın daha önce çalışmasını istiyorsak ona göre sırayı belirmeliyiz.

Startup’da yapacağımız bir çok dependency kaydı var. Bunları tek bir yerde toplamak ve bir extension haline getirmek çok daha iyi olacaktır.

Dependency Service Register Extension

Böylece startup içerisinde sadece “services.AddDependencies” methodu tüm dependencylerimizi kayıt etmiş olacaktır.

CQRS Pattern

CQRS için bir çok yerde gördüğüm bu görevli çok beğeniyorum. Konuyu çok güzel özetliyor. CQRS kullanmanın bir çok avantajı var.

  • İlişkisel veri tabanlarında kayıt ekleme/düzenleme işlemleri sırasında ilgili satır locklanır. Bu süre zarfında gelen select sorgularında deadlock problemi yaşarız. CQRS ile bu sorunu çözmüş oluyoruz.
  • Command işlemi sırasında farklı bir ORM, Query işlemi sırasında farklı bir ORM kullanmakta özgürüz.
  • Yaptığımız gösterimlerin (dashboard, rapor v.b) trafik yoğunluğu ile beraber dar boğaz yaratıp kayıtlarımızda ekleme/düzenleme işlemi yaparken yavaşlık sorunları yaşabiliyoruz. CQRS ile bunuda çözmüş oluyoruz.

En büyük dezavantajı ise command işleminde sonra read yapılacak DB’nin replicasının sağlıklı bir şekilde beslenmesi. Burada yapılacak event patlatma yapısının doğru ve stabil bir şekilde çalıştığından emin olmalıyız.

Yukarıda bahsettiğim dependency kayıtlarında “AddContexts”de var. Command işlemleri için “SampleCommandContext”, Query işlemleri için ise “SampleQueryContext” oluşturdum. Bunun amacına uygun olarak iki farklı DB kullanmak doğru olacaktır tabii ancak örnekte ben tek DB kullandım. Dilerseniz sadece connectionString değiştirerek bu özelliği kullanabilirsiniz.

MediatR ve CQRS hakkında konuştuktan sonra şimdi bunların kullanıldığı Query ve Command featurelarını detaylıca inceleyelim. MediatR ile API seviyesinde nasıl kullanacağız bunları görelim.

Talep : User tablosundan “Id” değerini kontrol ederek varsa kullanıcının geriye “Id” ve “Email” değerlerini döndüren bir API end point isteniyor.

İlk olarak bu talep için bir isim bekliyoruz. Yani feature adı diyebiliriz, bunu da “GetUserById” olarak belirledik. Bu talep veri tabanında kayıt, düzenleme gibi bir aksiyon almayacağı için bunu “Query Feature” olarak “Features/Query” klasörü altında açıyoruz. “Query Feature” özetle “HttpGet” işlemine denk gelir diyebiliriz.

Feature içerisinde talep edilen request ve response classları özgürce belirliyorum. Farklı bir katmanda Dto ya da ViewModel objesi açmak için vakit kaybetmiyorum. Parametrelerin çok büyüdüğü durumlarda yine feature içerisinde ayrı classlar tanımlayabilirim. Bunda da özgürüm.

MediatR kütüphanesinin IRequest, IRequestHandler gibi interfacelerini kullanarak feature geliştirmelerimi tamamlıyorum. API üzerinden nasıl cağıracağımıza geçmeden önce bir diğer feature için neler yapacağız ona göz atalım.

Talep: User tablosuna kayıt için “Email, Password” bilgileri ile kayıt işlemi yapılması istenilen bir API end point talep ediliyor. Email sistemde kayıtlı ise tekrar kayıt olmasın, işlem başarılı olursada geriye yeni kayıt olan kullanıcının ID bilgisinin dönülmesi gerekiyor.

Talep için belirlediğimiz feature ismi “RegisterUser” bir http post işlemi olduğu için “Command Feature” olarak değerlendiriyoruz. “Features/Commands” altında “RegisterUser” adında feature açarak kodlarımızı yazıyoruz.

Command için Query’de yaptığımız gibi IRequest objesinden inputModelimizi oluşturuyoruz. Gelen isteği db seviyesinde sorgu atmadan request validation yaparken FluentValidation kütüphanesini kullandım. Böylece kayıt isteği geldiğinde geçersiz bir email talebi varsa db seviyesinde her hangi bir sorgu atmadan isteği sonlandırabiliyoruz.

Handle içerisinde kayıt işlemlerimizi tamamlıyoruz. Kayıt sırasında olası bir hata durumunda ExceptionManager’ı Corex içerisinde kullandım. Corex’de buna benzer bir çok yardımcı paket bulabilirsiniz.

Query ve Command için nasıl feature oluşturacağımızı gördük şimdi işin en basit bölümü kaldı API tarafında kullanmak bunun içinde dependency injection ile MediatR kütüphanesinin bize sağladığı “IMediator” interfacini kullanarak featurelarımızı cağıracağız.

Register örneğinde Command nesnesini direkt olarak bir Dto, ViewModel ya da InputModel gibi kullanabildiğimizi göstermek istedim. Query içinde dışarıdan Guid parametresi olarak instance alarak mediator.send yapmayı göstermek istedim.

Mediator’a query ve command nesnelerini göndermemiz yeterli oluyor. Direkt olarak ilgili handler cağırmamıza gerek kalmadığı için olası bir handler değişiminde özgür olmuş oluyoruz bu da çok büyük bir avantaj sağlıyor bizlere.

Vertical Slice Architecture’ı anlatırken bir yandan CQRS ve MediatR’da kullanmış olduk. Best Practice budur diyemem ancak güzel bir örnek olduğunu düşünüyorum. Şuan Vertical Slice ile edindiğim tecrübelere dayanarak şunu söyleyebilirim. Mikro bir proje(mikro servis) yapacaksam Vertical Slice kullanmayı tercih ederim. Monolit bir proje yapmak zorundaysam ve büyük bir proje ise n-tier architecture tercih ederim diye düşünüyorum.

İleride elde edeceğim tecrübelerime göre bu fikirler tabii değişebilir. Sizlerinde örnek projeyi inceleyip, olumlu/olumsuz eleştrilerinizle projeye katkıda bulursanız eğer çok sevinirim.

Görüşmek üzere..

--

--