Çok Katmanlı Mimari(N-Tier Architecture)

Samet Çınar
SabancıDx
Published in
6 min readFeb 13, 2021

Nedir bu kulağa hoş gelen herkesin dilinde olan çok katmanlı mimari?
Ne yapınca çok katmanlı mimariye uygun oluyoruz?
Geliştirebilir proje alt yapısı ne demek, değiştirmek yerine geliştirmek istiyorsak neler yapmalıyız?
Çok katmanlı mimarisiye uygun proje geliştirme konusunda yardımcı bir alt yapı projesinin bilgilendirmesini yazımın sonunda yapacağım.

Çok katmanlı mimari hakkında birçok yazı okudum, bir çoğunda yanlış ifade edilen kavramlar olduğunu düşünüyorum. Kendimce doğru olduğunu düşündüğüm yapıyı anlatmak istiyorum.

Herkesin BAL(Business Access Layer),DAL(Data Access Layer) ve PL(Presentation Layer) yapısını çok katmanlı mimari olarak anlattığını görüyorum. Yıllar önce bu tarzda yapılan çok işe yaradığı aşikar ancak ilerleyen teknoloji ile beraber projelerimizi sürekli yeni kütüphaneler, yeni çözümler ile geliştirme gereği duyuyoruz.

Bu yüzden katman olarak adlandırdığımız kod yapılarımızı mümkün mertebe parçalar halinde oluşturmalıyız. Büyük ve uzun soluklu projelerde yer alan geliştiriciler bana burada hak vereceklerdir. BAL adında açılan katmanda içerisinde DAL ile konuşarak tüm kodlarımızı yazarız. Bir zamandan sonra ne biz ne yaptığımızı hatırlarız, ne de başka birisi bizim ne yaptığımızı anlayabilir.

Açık konuşmak gerekirse BAL, DAL katmanı açarak çok katmanlı proje yaptığını düşünen bir kesim var, bu kadar basit değil diye düşünüyorum. O yüzden her iş kolunu ayrı parçalara bölmeliyiz.

Peki, bu kadar eleştiri yaptın o zaman nasıl yapmalıyız dediğinizi duyar gibiyim:) Tabii bu sorunun cevabı biraz yoruma açık, benim yaptığım en doğrusu diyemem, her zaman daha iyisi vardır diye düşünüyorum. Her zaman daha iyisini aramamız gerekiyor.. Bu yüzden yorumlarınız da çok değerli olacaktır.

Kod paylaşımı yapmadan örnek bir proje üzerinden anlatarak gitmek istiyorum.

Sample Project

Projemizin böyle bir domainde açılmasını tercih etmeliyiz. Yani birbirini referans eden katmanların sıralı olması önemli. Böylece yukarıdan aşağıya baktığımızda tüm yaşam döngüsünü görebiliriz.

Her bir katmanın kendi içerisinde bir alt yapı(infrastructure) yapısı olmasına özen göstermeliyiz. Bir alt yapıdan türetilmiş class library listesi diyebiliriz.

Projemizin ilk olarak kendine özel bir “Core” katmanı olmalı. Bunun adına “Common” da diyebilirsiniz. Burada projemizin appsettings, startup v.b alt yapısal ihtiyaçlarını barındırabiliriz.

Data katmanı entitylerimizi modellediğimiz ve veri tabanından veri getirecek repositorylerimizin barındırğı katmandır. Burada en çok dikkat edilmesi gereken yer repository yapımızın değişebileceğini ön görmektir.

ORM olarak EntityFramework kullanarak SQL’den veri çektiğimizi düşünelim. Yöneticimiz bize geldi ve artık SQL kullanmıyoruz, Postgresql’e geçiyoruz gerekli düzenlemeleri yapın dedi. Ne yapacağız, nerelere dokunmamız gerekiyor ? Ne kadar sürebilir nasıl ölçümleyeceğiz?

Zaman ve maliyet hesabı yaparken sadece yeni geçeceğim ORM yapısının kurduğumuz alt yapıyı implemente etmesi kadar süre harcamalıyız. Yani benim entitylerim var, bunlara bağlı repositoryler var. Bunların methodlarını ben interfaceler üzerinden yapıp projemde de sadece interface üzerinde çağırırsam yarın bir gün sadece referans değişikliği ile ORM yapımı değiştirebilirim.

Bu konu bir çoğumuzun hakim olduğu “Dependency Injection” ile mümkün, bir çoğumuz bunu kullanıyoruz. Peki bunu projemizde nasıl konumlandırmalıyız?

Data Layer

Data.Model.Inftrastructure’da entitylerimizi Data.Model.Repository.Inftrastructure’da ise bu entitylerin repository interfacelerinin bulunduğu bölümdür.
Örn : UserEntity için IUserEntityRepository oluşturarak User tablosuna atılabilecek sorgularımızı belirlediğimiz ara yüzdür.

Bu ara yüze bağlı olarak “Derived.EFSQL”i oluşturdum. Repository.Inftrastructure’da bulunan tüm ara yüzleri(interface) implemente ettim. Böylece dependency injection ile “Repository.Inftrastructure” içerisinde bulunan interfaceler proje içerisinde kullanılacak.

PostgreSQL geldiğinde ise “Repository.Derived.PostgreSQL” oluşturup, Repository.Inftratructure’da bulunan ara yüzleri implemente ettiğim sadece sunum katmanında değiştireceğim referans ile postgreSQL’e geçiş yapmış olacağım.

Cache Layer

Cache katmanında da yaklaşım aynı, data repository içerisinde yaptığımız gibi bir alt yapı oluşturuyoruz. Burada ara yüzü en doğru şekilde belirliyoruz. Yani yazacağımız interface ondan türeyen class librarylere tam hizmet etmeli buna dikkat ediyoruz. Implemente edilemeyen methodlar vs olursa hata yapmışız demektir.

Bu örnekte de benim “Cache.Inftrastructure” ile bir alt yapım var. ICacheManagment içerisinde örnek vermek gerekirse şöyle bir kod var ;

public interface ICacheManagement : IDisposable, ISingletonDependecy{T Get<T>(string key);List<T> GetList<T>(string key);bool IsSet(string key);bool Remove(string key);bool Set<T>(string key, T data, int cacheTime);}

Bu ara yüze bağlı olarak MemoryCacheManager oluşturdum.

public class MemoryCacheManagement : BaseMemoryCacheManager, ICacheManagment{//methodlarımızı baseMemoryCacheManager'dan implemente ediyorum.}

Yine başka bir cache yapısına geçmem gerekirse “ICacheManagement” implemente etmem yeterli olacaktır.

Sırasıyla gördüğünüz katmanlardan “Business” bölümüne kadar hepsi aynı yapıda ilerliyor. Özetle bir infrastructure var, bundan türeyen içerisinde kendi kütüphanesine bağlanıp işlemlerimizi yapan bir yapı var.

Şimdi bence en önemli yer olan business yani operasyonların olduğu ve bu operasyonlara yöneticilik eden class librarylerimizi oluşturmaya geldi sıra.

Business Layer

Burada artık biraz daha gündelik hayattan benzetmeler yaparak devam edeceğim. Business.Operation altında bulunan class librarylerin birer takım olduğunu düşünelim. Business.Manager ise bu takımların müdürü diyebiliriz.

Her bir takım kendi içerisinde aksiyonlarında gerekli araç ve gereçlerini üst katmanlardan temin eder ve işlerini yapar. Burada görevlendirme yaparken her takım üyesinin bir görev yapmasına özen gösteririz.

Business.Manager seviyesine gelene kadar tüm classların, methodların tek bir görevi olmalıdır.

Bulunan tüm takımların görevi müdürün taleplerini yerine getirmektir. Müdür diğer alt katmanları bilmek zorunda değil, o sadece altında bulunan takımları tanır. O data gelsin der DataOperation gider data katmanında repository ile konuşur datayı müdürüne getirir.

Müdür burada DataOperation datayı nasıl getirdi ? Sorusu ile ilgilenmez, getirdiği datadan bir hesaplama işlemi yapmak istiyorsa bunu BusinessOperation’a söyler hesaplamayı yapar müdürüne söyler.

Müdüre dosya yükleme talebi geldi ise FileOperation’a gider bu dosyayı yükle der, bu dosyası fiziksel bir sunucuda mı yoksa bir cloud sunucuda mı tutacağı konusu ile ilgilenmez. FileOperation cloudFile ile konuşur.

Müdür istemciden aldığı bir talebin sisteme uygun olup olmadığı görevini ValidationOperation’a verir. Bu takım gelen data transfer objesi olabilir ya da bir inputModel olabilir. Bunun server side validasyon işlemlerini yürütür herhangi bir uygunsuzluk varsa “ValidationOperationException” patlatır.

Her takım kendi içerisinde bir hata aldığı zaman ne exception patlaması gerektiğini tanımlamalı ve müdürüne bilgilendirmelidir. Çalışma sırasında olacak bir hata müdürün bilgisi dahilinde olmaz ise büyük bir bug var demektir. :)

Müdür belirli durumlarda mail ile bilgilendirme yapmak istiyorsa MailOperation ile iletişime geçer, MailOperation maili SMTP ile mi atıyor AzureSendGrid ile mi atıyor bununla ilgilenmez.

Peki bu takımlar birbirine ihtiyaç duyduğunda iletişimi kim kuracak ?

Örn : PaymentOperation ödeme alıyor ama ödeme alma aşamasında DataOperation’dan alması gereken banka bilgileri var, bunlar veri tabanından yazıyor. PaymentOperation direkt olarak DataOperation ile iletişime geçemez, bu bağımlılık yaratır. PaymentOperation müdürü ile konuşacak, “müdürüm bana banka bilgileri lazım” diyecek, müdürü de DataOperation ile görüşüp ona gerekli datayı verecek. Güzel bir senaryo oldu:)

Bunlara ek olarak ne olabilirdi ? Bu size kalmış, örneğin cache katmanı var ama sistemde kullanmadığım için “CacheOperation” yok bunu açabilirdim. Ya da sizin bu tarzda farklı takımlara görev vermeniz gerektiğini düşündüğünüz konular için ayrı takımlar oluşturabilirsiniz. OLUŞTURMALISINIZ :) Takım için organizasyon sizin yapacağınız düzene bağlı.

Operasyonlarımıza takım dedik, müdürümüz ise “Business.Manager”. Müdüre gelene kadar herkesin tek bir görevi vardı ancak müdür birden fazla görev yaparak bir method oluşturabilir.

Örn : Bir kayıt işlemi hayal edelim. Methodumuzun adı “UserRecord” olsun. Manager UserRecord methodu için API ile haberleşiyor, API’dan gelen Name,Surname bilgileri ile kayıt oluşturmak istiyor.

İlk olarak validation operation ile datanın uygunluğunu kontrol ediyor. Uygun olması durumunda data operation ile veri tabanına kayıt işlemini tamamlıyor. Tamamladıktan sonra mail operation ile kullanıcıya hoş geldiniz mailini gönderiyor v.b.

Business.Manager “Domain Driven Design” kurallarına bağlı olarak “Application Layer” diyebiliriz. Sunum katmanı(Prensetation Layer) ile use case ilişkisinde bulunan tek katmandır. Sunum katmanı sadece müdürü tanır, ona taleplerini iletilir o da takımlarına iletir.

Bundan sonrası artık sunum katmanı. API, MVC, Blazor v.s ihtiyacımıza göre buna karar vermeliyiz.

Projelerimizi geliştirirken kendimize yardımcı bir alt yapı oluşturmalıyız. Proje özelinde değişmeyecek aksiyonlar için her seferinde Amerika’yı yeniden keşfetmeye gerek yok. MemoryCacheManagement’dan kullandığımız “BaseMemoryCacheManagement” gibi bir çok yardımcı methodu bulunabileceği bir github projesi oluşturdum. Bu projeden örnek bir proje nasıl olur, bunu da farklı bir github projesi ile oluşturacağım.

GitHub URL : https://github.com/gsmtcnr/Corex

Dilerseniz projeyi indirip inceleyebilirsiniz.
Corex ile adım adım proje geliştirip yine buradan yazılar yazacağım aynı zamanda github projesi de oluşturacağım.

Yine görüşmek üzere..

--

--