En Çok Kullanılan Web Uygulama Mimarileri

8 Ağu

Web uygulama mimarileri, uygulamanın nasıl geliştirildiğinden, nasıl yayına alındığına kadar planlanan adımlar dizisidir. Günümüzde bir çok uygulama, IIS, Tomcat veya Nginx gibi ortamlarda tek bir application domain altında tek bir birimden oluşmuş şekilde çalışabildiği gibi, birden fazla application domaine hatta birden fazla sunucuya dağıtılmış şekilde çalışabilen uygulamalar mevcuttur. Bununla birlikte, tek bir dağıtım birimi ile deploy edilip çalışan uygulamalar bile, mantıksal olarak birkaç katmana ayrılabilmektedir. Yazılım mühendisliğinin amaçlarından biri de çok sık değişen kod ile az değişen kodu birbirinden ayırmaktır.

Genel olarak kullanılan mimariler Monolitik, N-Katmanlı ve Clean architecture olarak adlandırılabilir.

Monolitik (Tek katmanlı) uygulama nedir?

Monolitik uygulamalar, kullanıcı arayüzü ve veri erişim kodunun tek bir programda birleştirildiği tek katmanlı bir uygulama biçimidir. Uygulamanın sunucu ortamlarında dağıtılması tek bir birim şeklindedir. Böyle bir uygulamanın yatay olarak ölçeklenmesi gerekiyorsa, uygulamanın tamamı birden çok sunucu veya sanal makine arasında çoğaltılır.

Monolitik uygulama mimarisi proje yapısı

Bu uygulama mimarisinde, geliştirilen uygulamanın tüm birimleri tek bir projede tutulur, tek bir derlemede derlenir ve tek bir birim olarak dağıtılır.

Projede iş birimlerinden birbirinden ayrılması, klasörleme mantığına göre yapılır. Örneğin MVC pattern uygulanıyorsa Model, View, Controller, Data gibi klasörlere dağıtılmış bir şekilde geliştirme yapılır. Bu düzenlemede, uygulama görünümü ile ilgili detaylar Views klasörüyle, veri erişim uygulama detayları ise Data klasöründe tutulan sınıflarla sınırlandırılmalıdır. İş mantığı, Models klasöründeki servis ve sınıflarda bulunmalıdır.

Uygulanışı her ne kadar basit olsa da bu mimarinin dezavantajları vardır.

  • Teknolojik araçlardan veritabanına yazma işlemlerine, arayüz tasarımına kadar her şey tek bir projededir. Böyle bir ortamda aradığını yerli yerinde bulmak zordur.
  • Projenin boyutu ve karmaşıklığı arttıkça dosya ve klasörlerin sayısı da artar.
  • Kullanıcı arabirimi (UI) iş parçacıkları (Models, Views, Controller) birden fazla klasöre dağılmıştır.
  • UI tarafında, filter, model binder, exception partial modeller eklendikçe klasörler büyümeye ve karmaşıklık artmaya devam eder.
  • Business mantığı Models ve Services klasörleri arasında dağılmış ve hangi klasörlerin hangi sınıflara bağlı olması gerektiğine dair net bir gösterge yoktur.
  • Bir takım halinde çalışma sırasında birden fazla kişinin aynı dosya üzerinde çalışması gerekir ve çakışmalar artar.

Bu durumda uygulanabilirlik azaldıkça, sorunları daha rahat ele alabilmek için uygulamalar, çok projeli yapılara dönüştürülürler.

N-Katmanlı mimari nedir?

Uygulamaların karmaşıklığı arttıkça, bu karmaşıklığı yönetmenin bir yolu, uygulamayı sorumluluklarına veya iş birimlerine göre ayırmaktır. Bu ayrım, seperation of concerns olarak bilinir. Bu yöntem ile geliştiriciler, bir parçanın nerede geliştirildiğini rahatça karar verebilir ve takım geliştirmesi yapıyorlar ise aradıklarını kolayca bulabilirler. Katmanlı mimarinin bir dizi avantajları vardır.

  • Uygulama kodu ve işlevsellikler katmanlara bölündüğünden, kodda yeniden kullanılabilirlik arttırılır. Uygulama da DRY ( Don’t Repeat Yourself) ilkesi sağlanmış olur.
  • Hangi katmanların birbiri ile iletişim kuracağı belirlenerek encapsulation sağlanmış olur. Bir katmanda değişiklik meydana geldiğinde sadece onunla çalışan katman etkilenmelidir. Bu sayede bir değişikliğin uygulamanın tümünü etkilemesi engellenmiş olur.
  • Katmanlar sayesinde uygulama işlevselliğini değiştirmek kolaylaşır. Örneğin bugün Oracle veritabanı ile çalışan sistem bir süre sonra Postgresql ile çalışma ihtiyacı duyduğunda, data interface ile Postgresql implementasyonu yapan yeni bir sınıf ile kodda çok fazla değişkliğe neden olmadan yeni bir altyapı eklenebilir.
  • Test edilebilirliği kolaylaştırır. Uygulamanın gerçek veri katmanına veya UI katmanına ait çalışan testler yazmak yerine, bu katmanlar test zamanında isteklere bilinen yanıtlar sağlayan sahte (Moq) uygulamalarla değiştirilebilir
Uygulama katmanları

Bu mimariye göre kullanıcılar uygulamaya User Interface üzerinden istekte bulunurlar. User Interface katmanı ise yalnızca Business Logic katmanı ile iletişim kurar. Business Logic katmanı da Data Access katmanından veri isteği yapar. User interface doğrudan Data Access katmanına erişemez. Business Logic katmanı da doğrudan veriye erişmemelidir, veri talebini Data Access katmanından yapmalıdır. Yani her katmanın kendine ait sorumluluğu bulunur.

N-Katmanlı uygulama mimarisi proje yapısı

Bu mimari her ne kadar tek projeli yapıyı çok projeli hale dönüştürse de bazı dezavantajları vardır.

  • Derleme zamanı bağımlılıkları (dependencies) yukarıdan aşağıya doğru çalışır. Yeni UI katmanı BLL katmanına, BLL katmanı da DLL katmanına bağlıdır. Bu, genellikle uygulamada en önemli mantığa sahip olan BLL’nin veri erişimi uygulama ayrıntılarına ve hatta bir veritabanının varlığına bağlı olduğu anlamına gelir. Böyle bir mimaride iş mantığını test etmek genellikle zordur ve bir test veritabanı gerektirir.
  • Deployment tek bir birim olarak yapılır. Bu uygulama enterprise amaçlar için birkaç projeye bölünmüş olsa da, tek bir birim olarak dağıtılır ve istemcileri onunla tek bir web uygulaması olarak etkileşime girerler.

Clean architecture nedir?

N katmanlı mimaride bahsedilen yukarıdan aşağı doğru bir derleme bağımlılığını ortadan kaldırmak için çeşitli çalışmalar yapılmıştır. Dependency Inversion ve Domain Driven Design gibi yaklaşımlar aslında benzer bir mimariye ulaşma eğilimindedirler. Bu mimari yıllar içinde birçok isimle anıldı. İlk isimlerden biri Hexagonal Architecture, ardından Ports-and-Adapters oldu. Son zamanlarda, Onion Architecture veya Clean Architecture olarak anıldı.

Clean Architecture mimarisi, Business Logic ve Application modelini uygulamanın merkezine (Application Core) koyar. Business Logic veri erişimine veya diğer infrastructure sorunlarına bağlı olması yerine, bu bağımlılık tersine çevrilir: infrastructure ve application ayrıntıları Application Core bağlıdır. Bu işlevselliği sağlamak için Application Core tarafında interface veya abstraction tanımlamaları yapılıp, bu soyut tiplerin implementasyonları da Infrastructure tarafında yapılarak sağlanır. Bu mimariyi görselleştirirken, soğana benzer bir dizi eş merkezli daire kullanmaktır.

Clean Architecture

Bu diyagramda, bağımlılıklar dıştan, en içteki daireye doğru akar. Application Core, adını bu diyagramın merkezindeki konumundan alır. Ve diyagramda, Application Core diğer uygulama katmanlarına bağımlılığı olmadığı görülür. Entity ve Interface tipleri tam merkezdedir. Hemen dışında, ancak yine de Application Core içerisinde, genellikle iç çemberde tanımlanan Interface tiplerini implemente eden domain servisleri bulunur. Application Core dışında, hem UI hem de Infrastructure katmanları, Application Core’a bağlıdır, ancak birbirine bağlı değildir.

Clean Architecture yatay mimari görünümü

Düz okların derleme zamanı bağımlılıklarını, kesikli ok ise yalnızca çalışma zamanı bağımlılığını temsil eder. Clean Architecture ile UI katmanı, derleme zamanında Application Core üzerinde tanımlanan arabirimlerle çalışır ve ideal olarak Infrastructure katmanında tanımlanan uygulama türlerini bilmemelidir. Ancak çalışma zamanında, bu implementasyon türleri uygulamanın yürütülmesi için gereklidir, bu nedenle mevcut olmaları ve bağımlılık ekleme yoluyla Application Core interface tiplerine bağlı olmaları gerekir.

Clean Architecture uygulanan ASP.NET Core uygulaması.

Clean Architecture Katman Yapısı

Katman yapısı oluşturulurken, katmanlara ait projelerin birbirine karışmaması adına öncelikle bir klasör modeli oluşturulabilir.

Daha sonra projeler bu düzene göre kolayca eklenebilir.

Clean Architecture proje yapısı

Presentation

Bu katmanda, kullanıcının uygulama ile iletişim kuracağı ara yüz uygulamaları oluşturulur. Örneğin bir Web Api veya bir desktop uygulama gibi düşünülebilir. Hiç bir katman, en üstte bulunan bu katmana bağımlı olamaz.

Infrastructure

Bu katmanda, uygulamanın dış dünya ile iletişim kurabileceği modüllerin somut implementasyonları yapılır. Örneğin Entity Framework veya NHibernate gibi framework kullanarak veritabanı işlemlerinin gerçekleştirilmesi. Ya da bir SMTP servisine erişerek mail gönderen işlemleri gerçekleştiren sınıfların oluşturulması gibi düşünülebilir.

Core

Bu katmanda Application ve Domain projeleri bulunur.

Domain projesi uygulamanın en soyut ve diğer katmanların hiç birini referans almayan çekirdektir. Entity, Value Objects, Exception ve Enumeration gibi tipleri burada bulunur. Örneğin bir Order entity tipinde, status ve tarih özelliğinin bulunması gibi iş kuralları burada belirlidir.

Application ise uygulama kuralarının belirlendiği projedir. Yani Entity tiplerinin nasıl kullanılacağı burada belirlenir. Application katmanı sadece Domain katmanına bağımlıdır ve oradaki bileşenleri kullanarak iş kuralları oluşturur. Örneğin işimiz ürün satmak ise, sipariş vermek bizim için bir kullanıcı senaryosu olabilir. Bu hizmeti kullanıcıya sunmak için öncelikler ürünün stokta olup olmadığını kontrol etmek ve ardından siparişi oluşturmak gerekir. Bunlar uygulamanın kurallarıdır. Ayrıca uygulamanın interface, abstract gibi business soyutlamaları Application projesinde bulunur. Fakat somut implementasyonları infrastructure katmanında bulunur. Örneğin IProductRepository interface Application projesinde ise, bunu implement eden ProductRepository class Infrastructure projesindedir.

Özet

Tek projeli monolitik yapılar, küçük çaplı ve dağıtık deployment gerektirmeyen proje çözümler için uygundur.

Katmanlı mimari sayesinde tek projeli yapılar birden fazla projeye bölünerek daha yönetilebilir ve takım geliştirmesine uygun hale getirilebilir. Ancak katmanlı mimarilerde core modüllerinin diğer modüllere bağımlılığı oluşmaktadır.

Clean Architecture mimarisi de bu bağımlılığı ortadan kaldırmaktadır. Bağımlılıklar, uygulamanın en önemli parçası olan Application Core katmanına doğru gelişmektedir. Bu sayede katmanlarda test edilebilirlik artar, uygulamanın merkez modülü olan Application Core, soyut bir özellik kazanarak üçüncü parti framework bağımlılıkları ortadan kalkar ve modülü test etmek için her hangi bir database ihtiyacı kalmaz. UI katmanının da aynı şekilde framework ve diğer katmanlar ile sıkı bağı kalmaz. Ancak deployment sırasında uygulamalar bu üç mimaride de bütün olarak deploy edilir.

Not: Uygulamaların önyüz, backend veya feature modüllerini birbirinden ayırarak her birinin ayrı bir servis halinde deploy edilebilmesini sağlayan mimariler, dağıtık mimariler olarak adlandırılır. Dağıtık deploy edilebilen servislerin dağıtık bir şekilde, farklı takımlar ile geliştirilmesi sağlanabilmektedir. Günümüzde dağıtık mimarilerden, özellikle e-ticaret sektöründe en çok tercih edilen mimari, mikro servis mimarisidir. Unutulmamalıdır ki her bir mikroservis özünde monolitik bir uygulamadır. Mikro servisler boyutu küçük olan servisler olarak anlaşılmamalıdır. Tek başına deploy edilebilen servisler olarak anlaşılmalıdır.

Sonuç

İyi mimariler bağımsız modüller yaratmak içindir. İyi yapılandırılmamış bir monolitik uygulama dahi geliştirilemiyorken, probleminize mikroservislerin çözüm olacağını düşünmemek yanlıştır.

Kaynak

https://docs.microsoft.com/en-us/dotnet/architecture/modern-web-apps-azure/common-web-application-architectures

ASP.NET Core geliştiriciler için yol haritası

5 Oca

2019 yılında, bir grup geliştirici, GitHub‘da ASP.NET Core geliştiriciler için bir yol haritası ortaya koymuşlar. Kaynağa buradan ulaşabilirsiniz.

Bu yazıda, her geliştirici için faydalı olabileceğini düşündüğüm bu yol haritasını sizlerle paylaşmak istedim.

Yol Haritası Boyunca Bilinmesi Gerekenler

  1. Öncelikle bilinmesi gerekenler.
    • C#
    • Entity Framework
    • ASP.NET Core
    • SQL Fundamentals
  2. Bilinmesi gereken genel geliştirme becerileri.
    • GIT sistemi öğrenilmeli, GitHub üzerinde yeni bir repository oluşturulabilmelidir, kod diğer geliştiricilerle paylaşılmalıdır.
    • HTTP(S) protokolü bilinmeli, http request metodları (GET, POST, PUT, PATCH, DELETE, OPTIONS) bilinmelidir.
    • Google kullanmaktan ve araştırmaktan korkmayın.
    • Dotnet CLI öğrenmelisiniz.
    • Veri yapıları ve algoritmalar hakkında bir kaç kitap okumalısınız.
  3. Dependency Injection (DI – Bağımlılıkların dışarıdan alınması)
  4. Veritabanları
  5. Cache Mekanizmaları
  6. Log Mekanizmaları
  7. Web site geliştirme şablonları (Template Engines)
  8. Gerçek zamanlı iletişim araçları (Realtime Communication)
  9. Nesne eşleştirme araçları (Object Mapping)
  10. API istemcileri (Clients)
  11. Bilinmesi faydalı olabilecek bilgiler
  12. Testler
  13. Görev zamanlayıcılar (Task scheduling)
  14. Mikro servisler (MicroServices)
  15. SOLID prensipleri
  16. Tasarım Kalıpları (Design-Patterns)

Yol Haritası Görseli

Kullanıcı Dostu Yazılım Nasıl Anlaşılır?

13 Haz

Yazılımda kullanıcı dostu (user friendly) kavramı arayüzle ilgili bir kavramdır. İçgüdüsel olarak, yazılımımız kullanıcının hoşuna giden rahat bir kullanıma sahipse kullanıcı dostudur denir. Ancak bu durumun ispatlanabilir ve bilimsel olarak ifade edilebilir olması gerekmektedir. Aksi halde yazılımınızın kullanıcı dostu olup olmadığı havada duran bir ifade olarak kalır.

Bir yazılımın kullanıcı dostu olup olmadığı, arayüz değerlendirmesi kabul testleri ile meydana çıkan bir kavramdır. Yazılımın analiz aşamasında, yazılım performansının değerlendirilebilmesi amacıyla objektif ve ölçülebilir hedeflerin belirlenmesi gereklidir. Değerlendirme yapılırken niyeti açık bir şekilde ortaya koyan kabul kriterleri oluşturulur ve bunlar genellikle aşağıdaki maddeleri içerir:

  • Öğrenme süresi
  • Hata oranı
  • Görevin tamamlanma hızı
  • Akılda kalıcılık

1- Öğrenme Süresi
Bir kullanıcının, sistemi öğrenme süresi yazılımın kullanılabilirliği açısından önemli bir ölçümdür. Karmaşık bir arayüze sahip yazılımın öğrenme süresi ve dolayısıyla müşteriye olan maliyeti yüksektir. Bu istenmeyen bir durumdur.

2- Hata oranı
Sistemin kullanımı sırasında meydana gelen toplam hata veya belli bir süre içerisinde oluşan hata sayısıdır. Çok fazla hata uyarısı vermek veya istenmeyen hataların(exception) oluşması , kullanıcı üzerinde hoş bir izlenim bırakmaz.

3- Görevin Tamamlanma Hızı
Görevin tamamlanma hızı yazılımın bir işi bitirme hızı yani yazılımın performans göstergesi değildir. Kullanıcının arayüzü kullanabilemsi ile ilgili gözlenen hızdır. Örneğin bir yeni bir kaydın eklenebilmesi kullanıcının ne kadar zamanını aldığını gösteren hızdır.

4- Akılda Kalıcılık
Zaman içinde arayüzün akılda kalıcılığı ile ilgilidir. Arayüzün kısa bir süre içinde unutulmaması gerekir.

Örnek bir kabul testi nasıl olmalıdır?

  • Bir saatlik eğitim sonunda, banka görevlisi yeni bir müşteri kaydını hatasız olarak yapabilmelidir.
  • Beş saatlik eğitim sonunda, banka görevlisi müşteriler arasında para transferini hatasız bir şekilde gerçekleştirebilmelidir.

Sistemin tasarımı ve kodlanması tamamlandıktan sonra, analiz aşamasında belirlenmiş olan hedeflere bakılarak kullanıcı kabul testleri gerçekleştirilir. Arayüzün testi sırasında, ölçülebilir kriterlerin incelenmesi büyük önem taşır. Böylece bilimsel temeli olmayan “kullanıcı dostu olup olmama” tartışmalarından kaçınılmış olur. Yani kullanıcı dostu (user friendly) diye tabir edilen ama tarif edilemeyen kriterin anlaşılabilir bir şekilde ortaya koyulmuş ve bu kriteri ölçebilen bir ölçek oluşturulmuş olur. Kabul testinin amacı, gereksinim dökümanlarında belirtilmiş olan şartların yerine getirilip getirilmediğini kontrol etmektir. Sistem bu testleri geçtikten sonra, seri üretime geçilir ve aktif olarak kullanımı sırasında da incelenmesi devam edecektir.

Application Repository Ninject Modülü

29 May

Bu yazımızda, veri merkezli(data centric) uygulamalarda alışılagelmiş bir yöntem olan Repository kullanımının, birden fazla veri kaynağının bulunduğu durumlarda nasıl yapılması gerektiği konusunu incelemeye çalışacağız.

Repository mantığında öncelikle tüm alt sınıfları hiyerarşik bir düzene sokmak adına tanımlanan bir interface vardır. Örnek olarak basit bir interface tanımlamasını şu şekilde ifade ediyor olalım.


public interface IRepository<T>
{
    void Add(T entity);
    IList<T> GetAll();
}

Bu interface altında veritabanına ekleme ve veritabanından veri getirme gibi iki temel işlem vardır. Eğer bir SQL veri tabanına veri yazmayı amaçlıyorsak bu işlemi gerçekleştiren somut sınıfın oluşturulması gerekmektedir.


 public class SqlRepository<T> : IRepository<T>
 {
   public void Add(T entity)
   {
       // Veritabanına ekle..
   }

   public IList<T> GetAll()
   {
       // Veritananından oku..
   }
 }

Buraya kadar işler normal akışında seyrediyor. Ancak SQL veri tabanına yazılamadığı durumlarda ne olacağı bir soru işareti. Ya da başka bir sebepten dolayı ilişkisel veri tabanına yazmak yerine kayıtları XML bir dosyaya yazmak gerektiğinde XML dosya ile çalışabilen somut bir repository oluşturulması gerekmektedir. Bu durumda yeni bir sınıfa ihtiyaç duyulmaktadır.


 public class XmlRepository<T> : IRepository<T>
 {
    public void Add(T entity)
    {
       // XML dosyaya ekle...
    }

    public IList<T> GetAll()
    {
       // XML dosyadan oku...
    }
 }

Ancak bu iki farklı Repository tipinin, çalışma zamanında seçilebilir ve değiştirilebilir olması gerekmektedir. Aksi taktirde kodun değiştirilebilir olması istenmeyen bir durumdur. Bu değişime ayak uydurabilmek adına, değişikliği otomatik olarak gerçekleştirebilecek bir sınıfa ihtiyaç duyulmaktadır. Bu sınıf, çalışma zamanında dependency injection mekanizması ile yüklenebilen bir modül halinde tasarlanabilir.


 public class ApplicationRepositorySelectionModule : NinjectModule
 {
    public override void Load()
    {
        switch (ApplicationConfiguration.StorageType)
        {
           case PersistenceStorageType.SqlRepository:
              Bind(typeof(IRepository<>)).To(typeof(SqlRepository<>));
              break;
          case PersistenceStorageType.XmlRepository:
              Bind(typeof(IRepository<>)).To(typeof(SqlRepository<>));
              break;
         default:
              throw new ApplicationException("Not supported storage type!");

       }
    }
 }

NinjectModule sınıfı, Ninject dependenjy injector aracına ait bir sınıftır. Yaptığı işlem ise  Interface türlerine karşılık hangi somut sınıfın oluşturulacağını belirlemektir. Zamanı geldiğinde ilgili nesneyi oluşturup programa sağlamak yine Ninject tarafından gerçekleştirilmektedir.

Modül içerisinde kullanılan PersistenceStorageType ve ApplicationConfiguration sınıfları ise şu şekildedir.


 public class ApplicationConfiguration
 {
     public static PersistenceStorageType StorageType { get; set; }
 }

 public enum PersistenceStorageType
 {
     SqlRepository = 0,
     XmlRepository = 1
 }

Bu şekilde birden fazla repository sınıfı ile konfigurasyonu sağlababilir şekilde çalışmak mümkün olmaktadır. Yazılımda çevikliği yani yazılımın değişime ayak uydurabilme yeteneğini sağlayabilmek açısından uygulanabilecek olan küçük bir detaydan bahsetmiş olduk.

Bir sonraki yazıda görüşmek dileğiyle.

Bir İki Cümleyle Design Patterns

17 Eyl

Factory Pattern
Nesne oluşturma işleminin bir iş kuralı dahilinde olduğu durumlarda kullanıcıyı kuralın dışında tutarak nesne oluşturmak için kullanılır. Kurallara uygun bir şekilde soyut tipleri implemente eden  somut nesneler oluşturularak kullanıcılara verilir.

Abstract Factory Design Pattern
Factory nesneleri oluşturmak için kullanılan bir desendir. Yani Abstract Factory deseni ile soyut bir factory nesnesi oluşturulur, bu factory nesnesinden de belirlenen kural dahilinde bir nesne oluşturulur. Küme alt küme mantığına benzer bir yapı söz konusudur.

Adapter Design Pattern
Birbiriyle uyumsuz iki farklı interface arasında anlaşma sağlayan bir köprü vazifesi görür. Bunu gerçek hayatta cep telefonu ile elektrik prizi arasında görev yapan şarj aletine benzetebiliriz.

Template Pattern
Bir algoritma için soyut tipte bir iskelet oluşturarak bu iskeletten somut süreç adımları oluşturmamızı sağlar. Algoritmanın adımlarını somut sınıflarda tanımlarız. İskelet görevi gören tip bir şablon niteliğindedir.

Singleton Design Pattern
Bir sınıfa ait sadece tek bir nesne oluşturmayı ve gerektiğinde ona ulaşabilmeyi garanti eder. Yani nesne oluşturma üzerine oluşturulmuş bir desendir.

Decorator Pattern
Nesnelere kompozisyon yoluyla yeni davranışlar eklememizi sağlar. Bu işlemi aynı temel sınıftan türeterek veya ortak bir interface uygulayıp nesneye enjekte ederek yapabilmek mümkündür.

State Pattern
Bir nesnenin iç durumunda meydana gelen değişikliklerin nesnenin davranışını değiştirmesine izin verir. İç durumdan kasıt interface tipleridir.

Strategy Pattern
Çalışma zamanında dinamik olarak algoritma değişikliğine olanak sağlayan bir desendir. Algoritmalar somut nesnelerde saklanır. Client kod ise algoritma sınıfının türetildiği abstract veya interface türünü tanır.

  • Not: State ve Strategy pattern diyagram olarak birbirine benzerdir. Ancak çalışma prensipleri açısından birbirlerinden farklıdırlar. State pattern için nesnenin NE olduğu önemlidir. Strategy için nesnenin NASIL çalıştığı önemlidir.

Specification Pattern
İş nesneleri içerisinde gömülü olan seçim kriterlerini başka nesneler ile paylaşamazsınız veya tekrar tekrar kullanamazsınız. Specification pettern, bu problemi ortadan kaldırır.

Composite Pattern
Nesnelere ağaç yapısında ya da hiyerarşik topluluklar halinde gruplanabilme yeteneği sunar. Örneğin bir kategorinin alt kategorisi olabileceği gibi onun da alt kategorisi olabilir.

Proxy Design Pattern
Bir sınıfın işlevselliği başka bir sınıf ile temsil edilir. Cache mekanizmaları oluşturulurken kaynak kullanımını azaltmak için kullanılabilir. Örneğin veri tabanından veri getiren bir nesne, aynı verilere ihtiyaç duyulduğunda tekrar veri tabanına gitmeye gerek duyulmadan kullanılabilir.

Builder Design Pattern
Kuramsal bir iş akışını işletmek için gerekli olan kompleks bir nesnenin basit nesneler ile adım adım oluşturulmasına olanak sağlar.

Bir Metodun Single Responsibility Principle Bakımından İncelenmesi

4 Oca

Bu yazımızda SOLID prensiplerinden ilki olan Single Respoinsibility Principle(Tek Sorumluluk İlkesi) üzerinde duracağız. Daha önceki yazılarımdan birinde bu prensibi giriş seviyesinde inceleme şansı bulmuştuk. Bu yazıda biraz daha farklı bir yöntemle prensibe yaklaşmak istedim. Bir örnek kod üzerinde yapılmış ihlalleri ortaya koyup, bu soruna SRP ile çözüm bularak ilerlemeye çalışacağız.

public class StringCalculator
{
    public int Add(string number)
    {
       if (number.Length==0)
       {
           return 0;
       }
       return int.Parse(number);
   }
}

Bu sınıfa ait Add() metoduna baktığımızda metodun, Single Responsibility Principle bakımından bazı problemleri olduğu görüyoruz. Bu metodun çok fazla bilgiye sahip olduğu ortada. Metodun sahip olduğu bilgiler:

  • Ne zaman “0” döndüreceğini biliyor.
  • Döndürülecek default değerin “0” olduğunu biliyor.
  • Sayıların dönüştürme(parse) detaylarını biliyor.
 public class StringCalculator
 {
    private const int DefaultValue = 0;

    public int Add(string number)
    {
        if (isNumberEmpty(number))
        {
           return DefaultValue;
        }
        return parseSingleNumber(number);
    }

    private static bool isNumberEmpty(string number)
    {
        return number.Length == 0;
    }

    private static int parseSingleNumber(string number)
    {
       return int.Parse(number);
    }
}

Add() metodunun bilmemesi gereken durumları metodun dışına çıkardık. DefaultValue değeri değişeceği zaman veya number parametresinin boş(empty) olduğu durum kontrolünün değişeceği zaman sadece ilgili private metoda bakmamız yeterli olacaktır.

Bu durumda Add() metodunun, sadece algoritmayı bildiğini görebiliriz. Uygulama detaylarına dair hiçbir şey ile ilgilenmiyor. Yani metodun tek sorumluluğu sadece algoritmayı takip etmektir.

Metodalarımıza baktığımızda onları kitap gibi okuyabilmeliyiz. Ne yaptığını ve niyetini anlayabilmeliyiz. Bu şekilde çalışırsak gereksiz yorum satırlarıyla kod açıklaması yapmak zorunda kalmayız.

Bir başka ipucunda görüşmek dileğiyle.

Yazılım Sistemlerinde Soyutlamalar

13 Kas

Bir arabaya bindiğimizde aracı hareket ettirebilmek için gerekli bilgilere sahipsek aracı çalıştırıp yolumuza gidebiliriz. Aracı hareket ettirebilmemiz için bize öğretilen kurallar dizisi, aracın hareketini sağlayan mekanik aksamlardan arındırılmıştır. Yani şoför koltuğuna oturduğumuzda motordan tekerlere iletilen gücün, motora giden yakıtın veya aküde ki elektrik dağılımının yönetimi ile ilgilenmeyiz. Bu süreçler mühendisler tarafından tasarlanmış ve sürücüden soyutlanmıştır. Sürücüye sadece sisteme gerekli komutları vermek kalmıştır.

Arabaların donanımsal özelliklerinde yapılan değişiklikler sürücüleri etkilemez . Örneğin benzinli bir araç LPG yakıta dönüştürüldüğünde sürücüler, yeniden araç kullanma eğitimi almak zorunda kalmazlar.

Soyutlama işlemi, karmaşık sistemlerin uygulama alanındaki detaylarını kullanıcılardan gizlemek için uygulanır. Ayrıca karmaşık sistemlerin basitleştirilmesi ve kolayca anlaşılması için benimsenmiş ilkelerden biridir.

Yazılım sistemlerinde soyutlamalar mimari düzeyde yapılabildiği gibi kod düzeyinde de yapılabilmektedir.

Bir yazılım sisteminde mimari düzeyde yapılan soyutlamalar, yazılımı mimari katmanlar şeklinde sorumluluklara ayırarak yapılır. Böylece bir katman diğeri ile birlikte çalışır, fakat biri diğerinin çalışma alanını etkilemez. Hem de Separation of Concerns diye adlandırılan tasarım prensibine de uyulmuş olur. (Concern kelimesini burada “katmanlar” veya “işlevler” olarak değerlendirebiliriz).

Mimari Soyutlamalar
Mimari Seviyede Soyutlama

 

Yukarıdaki resimden de anlaşılacağı üzere, bir kullanıcı sadece Presentation Layer ile etkileşir. Bütün isteklerini bu katman üzeriden talep eder. Diğer katmanlardan haberdar değildir. Bu katmanların her birini farklı takımların geliştirdiğini varsayacak olursak, geliştiricilerin de sadece sorumlu olduğu katman içerisinde sorumlu olduğunu söyleyebiliriz. Bu şekilde izolasyon sağlanmış olur. Yazılım sistemlerinde kod seviyesinde yapılan soyutlamalar, nesneye yönelik programlama teknikleri ve tasarım desenlerinin doğru bir şekilde uygulanarak kod birimlerinin sınıflar şeklinde sorumluluklara ayrılması ile gerçekleştirilir. Örneğin üst seviye sınıfların alt seviye sınıflara bağımlı olması kodun gelişime kapalı olmasına yol açar.

 

Somut Nesne Kullanımı
Somut Nesne Kullanımı

 

Bunun yerine üst seviye sınıfları alt seviye sınıflara soyut nesnelerle bağlarız. Bu sayede üst seviye sınıflar alt seviye sınıfları tanımazlar. Interface, Abstract Class gibi soyut nesneleri tanırlar.

Soyut Nesne Kullanımı
Soyut Nesne Kullanımı

 

Bu örnek, kod seviyesinde yapılan soyutlamalara verilebilecek örneklerden sadece biridir. Kod seviyesinde yapılan soyutlamalarla nesneler arasındaki ilişkilerin ve bağımlılıkların düzenlenmesi de sağlanmış olur. Programcı, iş birimlerini somut sınıflara kodlarken, somut sınıfları soyut tipler aracılığı ile kullandırırlar. Bu sayede kullanıcılar, işleyiş detaylarını bilmeden ihtiyaçlarını karşılayabilirler.

Soyutlama kavramı, yazılım geliştirme sırasında oluşan bazı Anti-Pattern durumlarını da bertaraf eder. Aceleci ve plansız yaklaşımlar ile her şeyin birbirine girdiği projeler Anti-Pattern durumları doğurur. Yazılımda Anti-Pattern durumları, bir gömleğin ilk düğmesinin yanlış iliklenip diğerlerinin de yanlış iliklenmesine benzetirim. Yani başlangıçta düzensiz ve plansız başlanan işlerin zamanla, birikmiş hatalar yığınına dönüşeceği açıktır.

Anti-Pattern durumlardan arınmış projeler dileğiyle, tekrar görüşmek üzere.

Dependency Inversion Principle DIP – Tasarım Prensibi

31 Ağu

Bu tasarım prensibi, uygulamalardaki alt ve üst sınıflar arasındaki bağımlılıklardan dolayı ortaya çıkmıştır. Alt sınıfları, ana işleri yapan sınıflar olarak düşünelim. Üst sınıflar da alt sınıflara bağlı olarak işlemler yapan sınıflar olarak düşünelim. Yani üst sınıflar bazı işlemleri gerçekleştirebilmek için alt sınıflara muhtaçtır. Doğal olarak üst sınıflar, alt sınıflardan oluşturulmuş nesneleri kullanarak süreci işleteceklerdir. Ancak üst sınıfların direk alt sınıf nesnelerine bağlı olması bir takım zorlukları beraberinde getirmektedir. Bu zorlukların başında sıkı bağ oluşumu gelmektedir. Sıkı bağlılık ise gelişimi ve test edilebilirliği olumsuz etkileyen bir durumdur.

Yazılım tasarımında sıkı bağlılığı ortadan kaldıran çözüm ise nesneleri direk kullanmak yerinde soyut nesneler yani arayüzler (interface) kullanmaktır. Yani üst sınıflar, nesnelerin kendisini tanımak yerine soyut nesneleri tanırlar.

Bir örnek üzerinden kötü ve iyi kod tasarımlarını inceleyelim.

Sıkı Bağlı Tasarım


public class DisplayServiceManager
{
     readonly AnalogDisplayService service = new AnalogDisplayService();

     public DisplayServiceManager(AnalogDisplayService service)
     {
         this.service = service;
     }

     public void StartService()
     {
         service.Start();
     }

     public void StopService()
     {
         service.Stop();
     }

}

public class AnalogDisplayService
{
     public void Start()
     {
          // Start analog display.
     }

     public void Stop()
     {
          // Stop analog display.
     }

}

Bu tasarımda örnek olarak görüntü yönetimini yapan DisplayServiceManager adında, üst sınıf olan bir servis yöneticisi oluşturulmuştur. Bu üst sınıf AnalogDisplayService adında bir alt sınıfı kullanmaktadır. Yukarıda belirttiğimiz gibi, üst sınıf alt sınıf nesnesini somut olarak kullanmıştır. Yani üst sınıf, servisleri tanımaktadır. Bu da alt sınıflara sıkı bağlılık oluşturmaktadır. Bu durumda DigitalDisplayService adında yeni bir ihtiyaca karşılık, yönetici üst sınıfın yapısı değişecek.

Yani şu anki durumumuz şu şekildedir:

Üst Sınıf + Alt Sınıf

Oysa istenen durum şu şekildedir:

Üst Sınıf + Soyut Nesne + Alt Sınıf

Bağımlılığa yeni bir boyut kazandıran tasarımı ise şu şeklide yapabiliriz.

Dependency Inversion Yöntemiyle Tasarımı


public class DisplayServiceManager
{
       private IDisplaySerivce service;

       public DisplayServiceManager(IDisplaySerivce service)
       {
            this.service = service;
       }

       public void StartService()
       {
            service.Start();
       }

       public void StopService()
       {
            service.Stop();
       }

}

public interface IDisplaySerivce
{
     void Start();
     void Stop();
}

public class AnalogDisplayService : IDisplaySerivce
{

     public void Start()
     {
        // Start analog display.
     }

     public void Stop()
     {
        // Stop analog display.
     }

}

Bu kod, Dependency Inversion prensibinin uygulandığı bir koddur. Sıkı bağlılığın IDisplayService arayüzü sayesinde ortadan kalktığını görmüş olduk. Yeni eklemeler için DisplayServiceManager sınıfında değişikliğe gerek kalmadı. Esnek bir yapıya sahip olduk.

Interface Segregation Principle ISP – Tasarım Prensibi

26 Ağu

Bu tekniği, arayüz ayırma prensibi olarak adlandırabiliriz. Uygulamalarımızda bağlı kalmaya çalıştığımız soyut nesnelerle çalışma ilkesi, bazı yanlış hamleler sonucu başımıza iş açabilir. Soyutluk kavramını da bazı kurallara göre işletmeliyiz. Bize soyut nesne sağlayan 3 tane metodu olan bir interface ürettiğimizi varsayalım. Bu interface den türeyen bir de sınıf oluşturalım. Benzer şekilde bir sınıf daha türetelim. İkinci sınıfımız interface nesnemizin tek bir metodunu kullanıyor ve diğer iki metodu işe yaramıyorsa, doğru yolda değiliz demektir.

Cümlelerden ziyade bir örnek üzerinden anlatmak daha isabetli olacaktır.

Örneğimizde aşağıdaki gibi IUser tipinden iki seviyeli LowLevelUser ve HighLevelUser kullanıcı tipleri oluşturuyoruz. LowLevelUser sadece programları update edebilir. HighLevelUser ise hesap oluşturup hesap silebilir. Ancak LowevelUser tipinin CreateAccount metodu tetiklendiğinde NotImplementedException hatası ile karşılaşırız.


public interface IUser
{
      void UpdateProgram(string programme);
      void CreateAccount(string accountName);
      void DeletaAccount(string accountName);
}

public class LowLevelUser : IUser
{
      public void UpdateProgram(string programme)
      {
         // Start update service
      }

      public void CreateAccount(string accountName)
      {
         throw new NotImplementedException();
      }

      public void DeletaAccount(string accountName)
      {
          throw new NotImplementedException();
      }

}

public class HighLevelUser : IUser
{
      public void UpdateProgram(string programme)
      {
          // Start update service
      }

      public void CreateAccount(string accountName)
      {
          // Start create service
      }

      public void DeletaAccount(string accountName)
      {
          // Start delete service
      }
}

IUser arayüzünü parçalayarak işimize yarar hale getirmeliyiz. Yani alt arayüzler oluşturmalıyız.

Bu ayrımı şu şeklide yapabiliriz:


public interface IUpdater
{
      void UpdateProgram(string programme);
}

public interface IAccountManager
{
      void CreateAccount(string accountName);
      void DeletaAccount(string accountName);
}

public class LowLevelUser : IUpdater
{
      public void UpdateProgram(string programme)
      {
          // Start update service
      }
}

public class HighLevelUser : IUpdater, IAccountManager
{
      public void UpdateProgram(string programme)
      {
         // Start update service
      }

      public void CreateAccount(string accountName)
      {
         // Start create service
      }

      public void DeletaAccount(string accountName)
      {
         // Start delete service
      }
}

LowevelUser seviyeli kullanıcı tipinin sadece update işlemlerine yetkili olduğu için UpdateProgram metodunu içeren IUpdater arayüzünden türetilmiştir. Aynı şekilde HighLevelUser ise hesap yönetimlerinden sorumlu olduğu için CreateAccount ve DeleteAccount metodlarını içeren IAccountManager arayüzünden türetilmiştir.

Bu sayede arayüz ayırım tasarım prensibinin amacına ulaşabilmiş oluyoruz.

Liskov’s Substitution Principle LSP – Tasarım Prensibi

23 Ağu

Bu ilkenin benimsediği ana fikir şu şekildedir: “Alt sınıflardan oluşan nesneler üst sınıfların nesneleriyle yer değiştirdiklerinde aynı davranışı sergilemelidir.” Yani kullanıcılar (client) tarafında alt sınıf ve üst sınıfın kullanımı bir fark göstermemelidir.  Cümlelerle ifade etmek anlaşılır olsa da, kavramı örneklerle güçlendirmekte fayda var.


public abstract class Employee
{
     public abstract string GetDepartment();
}

public class ITEmployee : Employee
{
     public override string GetDepartment()
     {
          return "Information Technology Department";
     }
}

public class HumanResource : Employee
{
      public override string GetDepartment()
      {
         return "Human Resources Department";
      }
}

Yukarıdaki örnekte Employee adında bir üst ve bu sınıftan türetilmiş ITEmployee ve HumanResource alt sınıfları gösterilmiştir. Bu yapıya göre, alt sınıflar üst sınıf gibi kullanılabilir. Nasıl olduğunu şu şeklide client tarafında gösterebilriz.

static void Main(string[] args)
{
     Employee employee1 = new HumanResource();
     Employee employee2= new ITEmployee();

     Console.WriteLine(employee1.GetDepartment());
     Console.WriteLine(employee2.GetDepartment());
}

Employee tipine ait nesne(employee1), HumanResource veya ITEmployee gibi kullanılabilmiştir. Yani alt sınıflar, üst sınıfın işini üstlenebilmektedir. Yani yer değiştirme işlemi başarıyla gerçekleştirilebilmektedir.

LSP temelde Open Closed prensibinin özel bir türü veya uzantısı gibidir. Çünkü OCP den olduğu gibi LSP de de genişlemeye açık bir yapı sözkonusudur.