Specification Tasarım Deseni
You can access the English version via this link.
Specification tasarım deseni, objelerimizin belli özelliklere sahip olup olmadığını kontrol edebilmemizi sağlayan tasarım desenidir. Bu tasarım deseni aracılığıyla kontrolleri hem tekrar kullanabilmemizin hem de kontrolleri kombinleyerek daha karmaşık durumları basitçe sorgulayabilmemizin önü açılmış olur.
Bu yazıda specification tasarım desenini hangi durumlarda kullanabileceğimizden bahsedeceğiz. Ardından specification sınıflarımızı nasıl oluşturabileceğimizden, bu sınıfları mantıksal operatörlerle nasıl birbirlerine bağlayabileceğimizden bahsedeceğiz. Son olarak da oluşturduğumuz specification sınıflarını kullanarak, Entity Framework aracılığı ile veri tabanından verilerimizi nasıl sorgulayabileceğimize bakacağız.
İmplementasyon kısmında görülebilen kodların tamamına ve örnek projeye bu github linki üzerinden erişebilirsiniz.
Tasarım desenin implementasyonuna Spectacular adıyla NuGet üzerinden erişebilirsiniz.
Specification Tasarım Deseni Ne Zaman Kullanılmalıdır?
Birden fazla faktör bu tasarım desenini kullanmamıza sebep olabilir. Bunlardan biri olarak kontrol kurallarının kodun içinde tekrar etmesinden kaçınmak istediğimiz zamanları sayabiliriz. Örneğin bir kişinin ehliyet alıp alamayacağının kontrolünü kodunuzun içerisinde farklı yerlerde tekrarladınız. Şansa bakın ki yeni bir yasa çıktı ve ehliyet alma yaşında değişiklik yapıldı. Specification tasarım kalıbını kullanmadığımız durumda kodun içinde gezip kontrol yaptığımız yerleri bulmamız ve değiştirmemiz gerekecekti. Arayıp bulamadığız kod parçaları da uygulamamızın bug’lı bir şekilde çalışmasına sebep olacaktır.
Specification tasarım desenini kullanmak için bir başka sebep olarak objeye müdahale edemediğimiz durumları sayabiliriz. NuGet paketler aracılığıyla veya kullandığımız framework aracılığıyla ulaştığımız sınıfları kullandığımız zaman bu gibi durumlar yaşanabilmektedir. Örneğin IdentityServer NuGet paketini indirip ApplicationUser sınıfını doğrudan kullandığınızı ve kullanıcının yetişkin olup olmadığına dair bir kontrol yapmaya ihtiyacınızın olduğunu varsayalım. Bu durumda ApplicationUser sınıfı bizim değişiklik yapabileceğimiz bir sınıf olmadığı için IsAdult (Yetişkin mi) adında bir metod ekleyemeyeceğiz. IsAdultSpecification adında bir sınıf oluşturarak kontrol yapımızı bu sınıf içerisinde enkapsüle edebiliriz.
Bir başka ihtiyaç anı da obje üzerinde çok fazla sorgu tipinin var olması olabilir. Yine User örneğinden devam edelim. “Yetişkin mi?”, Evli mi?”, “Çocuğu var mı?”, “Çalışıyor mu?”, “Spor yapıyor mu?”, “Vejeteryan mı?”, “Ehliyeti var mı?” gibi bir çok soruya cevap verecek şekilde sınıfınızı hazırladığınızı ve uzun bir sınıfınız olduğunu düşünelim. Uygulamanız geliştikçe yani yeni sorgular gerektikçe bu sınıf uzamaya devam edecektir. Specification tasarım desenini kullanarak sınıfımızın okunamayacak kadar uzun olmasının önüne geçebiliriz.
Specification Tasarım Deseni İmplementasyonu
Bu tasarım desenini implemente ederken ilk olarak ISpecification arayüzünden başlayabiliriz. Arayüzü implemente eden sınıflar T tipinden bir objeyi parametre olarak kabul edecek ve objenin talep edilen özelliklere sahip olup olmadığını kontrol edebilecektir.
Aldığı tarih verisinin 21. yüzyıla ait olup olmadığını kontrol edecek bir specification sınıfı hazırlayalım. Bu sınıfa baktığımız zaman küçük bir kod parçası olduğunu ve yazmanın kolay olduğunu görebiliriz. Buna karşın tekrar tekrar yazmak içinse uzun bir kod bloğu olduğunun farkına varmamız gerekmektedir.
Specification tasarım kalıbının güzel yanlarından biri tekrar kullanılabilir olmasıdır. Elimizde olan bir kullanıcı objesinin doğum tarihini parametre olarak kullanıdığımız durumda kişinin 21. yüzyıl içinde doğup doğmadığını anlayabiliriz. Aşağıdaki örnekte ise film servisi üzerinden rastgele bir film elde edip bu filmin 21. yüzyıl içerisinde yayınlanıp yayınlanmadığını kontrol etmekteyiz.
Specification Sınıflarıyla Veri Sorgulamak
Specification tasarım deseninin temel implementasyon ve kullanım senaryosunu görmüş olduk. Bu noktadan sonra specification tasarım desenini kullanarak veri tabanı gibi dış kaynaklardan nasıl veri sorgulama işlemi yapabileceğimizi göreceğiz.
İlk olarak örnek modelimize bir göz atalım. Boy ve yaş bilgilerini tuttuğumuz basit bir kullanıcı sınıfımız olsun.
Şimdi specification sınıflarının türetileceği soyut sınıfı hazırlayarak işe başlayalım. Bu sınıf ISpecification<T> arayüzümüzü implemente edecek. Bunu yaparken bize Expression tipinden sorgu kriterine erişim hakkı da sağlayacak.
Artık ExpressionSpecification sınıfını kullanarak X yaşından daha küçük olan kişileri bulmamızı sağlayacak YoungerThanSpecification sınıfını hazırlayabiliriz.
Specification sınıfının kullanımının göze hoş gelecek şekilde olması için extension metodlar yazabiliriz. Aşağıda üç farklı extension metod görebiliriz. Bunlardan ilki tek bir objeye soru sorabilmemize olanak tanıyacaktır. İkinci metodu bellekte yer alan liste veya dizi gibi kitleleri filtrelemek için kullanacağız. Sonuncusunu ise dış veri kaynakları üzerinde filtreleme yaparken kullanacağız. IQueryable aracılığı ile sorgularımızı oluşturmazsak Entity Framework verileri önce belleğe alıp sonra filtreleme yapmaya çalışacaktır. Bu da hiç istemeyeceğimiz bir durumdur.
Tasarladığımız extension metodlar aracılığı ile filtreleme yapma ihtiyacımızı karşılayabiliriz. Örnek bir senaryo olarak yaş kriterine göre kullanıcılarımızı filtrelediğimiz aşağıdaki gibi bir endpoint’i düşünebiliriz.
Entity Framework’e ait DbContext objesine erişimi kısıtladığımız, özel IRepository arayüzlerine sahip olduğumuz durumlar da olabilir. Bu durumda kurallarınızı birleştirmek yukarıdaki gibi IQueryable objesi üzerinde yapılamayacaktır. Bu duruma karşın geliştireceğimiz çözüm için IUserRepository arayüzü ile ilk adımımızı atabiliriz.
Repository arayüzümüzde tek bir specification kabul ettiğimizi farketmişsinizdir. Bu durumda birden fazla specification sınıfını bir araya getirdiğimiz dinamik bir sınıfa ihtiyaç duyacağız. Bu sınıfı oluşturarak işe başlayalım. Yeni sınıfımızın yapacağı işlem aslında birleşim ile oluşturulmuş yeni expression objemizi saklamaktır.
İki ExpressionSpecification objesi alarak bu sınıfların sahip oldukları kriterleri birleştirecek metodun imzasını oluşturarak sürecimize devam edelim.
IExpressionSpecificationOperator arayüzünü implemente ederek Ve operasyonunu hazırladığımız aşağıdaki gibi bir sınıf oluşturabiliriz. “Objenin X ve Y özelliğine sahip olmasını istiyorum” gibi bir talebi karşılamak için bu mantıksal operatörü kullanıyor olacağız.
Artık specification sınıflarımızı birleştirebiliriz ve iş kurallarımızı insanların daha kolay okuyacağı bir şekilde peşi sıra kullanabiliriz.
Örnek kodların tamamına bu link aracılığı ile ulaşabilirsiniz. Örnek projede birleştirdiğimiz filtrelere göre veri tabanında çalıştırılan sorguları konsol ekranından takip edebilirsiniz.
Umarım yararlı bir yazı olmuştur. Diğer yazılarıma göz atmak için bu linke tıklayabilirsiniz.