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

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.

ASP.NET MVC Projesinde Dependency Injection Uygulanması

30 Haz

Daha önce hazırladığım Dependency Injection Tasarım Deseni başlıklı yazıda, yazılım tasarımında sınıfların birbirine olan bağımlılıklarını esnetmeyi ve bağımlılıkların sınıf dışından enjekte edilmesini incelemiştik. Bu yazımızda ise konunun bir örnek uygulaması niteliğinde olan ASP.NET MVC uygulamalarında Dependency Injection uygulamasını inceleyeceğiz.

Bu yazımızdaki örnek uygulamamızı geliştirirken, soyut sınıflar ile somut sınıflar arasındaki bağlantıyı kurarken ve somut nesne oluştururken IoC Container denen yardımcı kütüphanelerden faydalanacağız. Yine bu örnek uygulamada IoC Container kütüphanesi ile  somut nesnelerin kullanıcı(client) sınıf içerisinde “new” anahtar sözcüğü ile oluşturmak yerine, IoC Container tarafından oluşturulduğunu göreceğiz.

Örnek Uygulama

Örnek uygulamamızda ASP.NET MVC controller sınıfına Dependency Injection tarasım deseni ile bağımlılıkları dışarıdan soyut nesneler(interface veya abstract) yardımıyla vererek uygulamayı çalıştırmayı deneyeceğiz.

Öncelikle Controller sınıfımızı tanımlayarak işe başlayabiliriz.

public class HomeController : Controller
{
    private readonly IProductService productService;

    public HomeController(IProductService productService)
    {
        this.productService = productService;
    }
    public JsonResult Index()
    {
        IEnumerable<Product> products = productService.GetBestSellers();

        return Json(products, JsonRequestBehavior.AllowGet);
    }

}

Gördüğünüz üzere, Controller sınıfında hiç bir şekilde nesne oluşturma işlemi gerçekleştirilmiyor. Dışarıda oluşturulan ProductService nesneleri Controller sınıfı içerisine enjekte ediliyor. IProductService tipi bir interface’dir. Bu interface tipini uygulayan sınıflar HomeController içerisinde çalışabilir. IProductController tipindeki nesneler constructor metodu yardımıyla sınıf içerisine alınıyor.

ASP.NET MVC Controller sınıflarının varsayılan(default) halleri çalışma zamanında parametresiz constructor metodlarını ararlar. Bu tanımlama ASP.NET DefaultControllerFactory içerisinde belirlenmiştir. ASP.NET MVC open source olduğundan isteyen MVC kodlarını açıp inceleyebilir. Biz constructor seviyesinde injection işlemi yapacağımızdan DefaultControllerFactory sınıfının GetControllerInstence metodunu ezerek yani yeniden tanımlayarak istediğimiz formata getirmeliyiz.

Bu tanımlamayı yapmadan önce uygulamada kullanacağımız NInject kütüphanesinden bir iki cümle ile bahsedelim. Ninject kitüphanesi bir konteynır olarak çalışır. Soyut nesneler ile somut nesnelerin eşleştirildiği ve istendiğinde somut nesnelerin(new) oluşturulduğu kütüphanelerdir.

public class NinjectControllerFactory : DefaultControllerFactory
{
     private readonly IKernel kernel;

     public NinjectControllerFactory()
     {
         kernel = new StandardKernel(new NinjectBindingModule());
     }

     protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType)
     {
         return controllerType == null ? null : (IController)kernel.Get(controllerType);
     }
}

public class NinjectBindingModule : NinjectModule
{
     public override void Load()
     {
         Kernel.Bind<IProductService>().To<ProductService>();
     }
}

Burada tanımlanan kod parçasında anlatılmak istenen şey DefaultControllerFactory metodunun Controller Instance oluştururken karşılaşacağı parametresiz constructor metodu oluşturma hatasını engellemektir. Ninject aracılığı ile Controller tipine göre bir nesne oluşturuyor. Örneğin HomeController için controllerType HomeController olacaktır ve constructor metodunda IProductService interface tipini parametre olarak alacaktır. HomeController tipinde bir nesne oluştururken constructor metodda karşılaştığı parametreyi Kernel bildiği için hemen nesnesini oluşturarak Controller sınıfına vermektedir.

Artık Global.asax sınıfı içerisine bu yapıyı tanıtma işlemi kaldı. Onu da şu şeklide gerçekleştirebiliriz.

public class MvcApplication : System.Web.HttpApplication
{
     protected void Application_Start()
     {
        //diğer tanımlamalar...

        ControllerBuilder.Current.SetControllerFactory(new NinjectControllerFactory());
     }
}

Uygulamayı çalıştırdığımızda çıktı olarak şu şekilde bir JSON veri dönecek:

[{"Name":"Notebook"},{"Name":"PC"}]

Örnek uygulamanın kodlarını SkyDrive üzerinden paylaştım. Bir sonraki yazıda görüşmek üzere.

Kaynak Kodlar: Burada

Adapter Tasarım Deseni (Design Pattern)

1 May

Adapter tasarım deseni, kodun bağımlılığını azaltmak amacıyla uygulanan kalıplardan biridir. Özellikle kurumsal bazlı projelerde modüler yapıda geliştirme yapıldığı düşünüldüğünde uyumluluğun sağlanması, gerekli şartlardan saedce biridir. Proje üzerinde çalışan geliştiriciler farklı modülleri hazırlayıp ortak bir noktada yazılıma monte edebilmelidir.

Adapter Tasarım Deseni
Adapter Tasarım Deseni

Yukarıdaki şemada “Client” tarafındaki kod biriminin soyut olan (interface veya abstract) “Target” kod birimini tanıması ve “Target” tipinden türetilen diğer tipleri tanımaması modülerliğin ve test edilebilirliğin sağlanabilmesi için gerekli esnekliği bize sunmaktadır. “Adapter” tarafındaki kod birimleri, “Target” tipinden türetilmektedir. Bu noktadaki implementasyonları N sayıda geliştirici yapabilmelidir.

Adapter Tasarım Örnek
Adapter Tasarım Örnek

Bu şemada “Client” kod olarak bir rapor servisi (ReportService) sunulmaktadır. ReportService Servis tipimiz sadece soyut olan “IReportProducer” tipini tanır. IReportService tipinin uygulandığı WordReportAdapter tipini tanımaz. WordReportAdapter tipi sadece Microsoft.Office kütüphanesini kullanarak Word dökümanı üretir.

İki farklı geliştirici olduğunu düşünecek olursak birine Word dökümanı üreten adaptörü, diğerine de PDF dökümanı üreten adaptörü yazdırabiliriz. Geliştiricilerimiz de ReportService tipiyle ilgilenmezler. Sadece IreportProducer tipini tanırlar ve bu tipi implemente ederek PDF ve Word raporu üreten sınıfları geliştirirler.

Örnek uygulama

Örnek uygulama olarak yukardaki şemanın C# kodu ile yazılmasını ele alabiliriz.

public interface IReportProducer
{
    void CreateReport(Report report);
}
public class ReportService
{
      private readonly IReportProducer reportProducer;
      public ReportService(IReportProducer reportProducer)

          this.reportProducer = reportProducer;
      }
     public void CreateReport()
      {
          reportProducer.CreateReport(new Report());
      }
}
public class WordReportAdapter : IReportProducer
{
      public void CreateReport(Report report)
      {
          // TR: Office DLL nesneleri yardımıyla raporu oluştur.
          // EN: Get Office DLL objects and create Report
      }
}

Bu tasarım sayesinde ReportService sınıfı test edilebilir hale getirilmiş oldu. Mock nesneler oluşturulup tast işlemini gerçekleştirmek basitleşti.

Eğer Word döküman raporunu üreten kodu ReportService sınıfı içerisinde yapsaydık;

  • Test edilebilirlikten uzaklaşacaktık.
  • Birden fazla developer ile bir işi geliştiremeyecektik.
  • Rapor servis tipi somut nesnelere bağımlı olacaktı.
  • Yeni bir rapor üretici geliştirilmek istediğimizde servis sınıfını bozmak zorunda kalacaktık.

Adapter tasarım kalıbının uygulanması gayet basittir. Tek amacı ise birbirini tanımayan tipleri interface gibi soyut tipler kullanarak birbiri ile çalışabilir hale getimektir.

Kaynak Kod: https://skydrive.live.com/redir?resid=2884CE0681105B31!147

Specification Pattern (Şartname Tasarım Kalıbı)

3 Nis

Specification tasarım kalıbı, bir nesne içerisindeki mantıksal (boolean) yapıdaki iş kurallarının, dışarıdan almasını sağlamak amacıyla oluşturulmuştur. İş kuralları, birbirini takip eden zincirli bir yapıda olup daha karmaşık bir iş kuralını da ortaya çıkarabilir.

Kod Örneği

Desenin anlaşılabilmesi açısından bir kod örneği ile devam etmek istiyorum. Kod örneğimizi bir iş nesnesi üzerindeki mantıksal kuralları ele alarak geliştirmeye çalışacağız. Örneğimizde bir ürünü temsil eden tip üzerinden ilerleyeceğiz.

Ürün tipinde, stok durumunun yeterli olup olmadığını belirleyen bir mantıksal iş kuralını dışarıdan temin edecek şekilde modelleyerek ilerleyelim.

Altyapımızı oluştururken öncelikle şartname niteliğinde bir interface hazırlamalıyız.

public interface ISpecification<T>
{
    bool IsSatisfiedBy(T candidate);
}

Şartnamemizde, bu şartnameyi uygulayan sınıfların tipinden bir generic “T” parametresi verilmiştir. Bu sayede şartname tipi dışarıdan alınabilir şekilde genelleştirilmiştir.

ISpecification<T> tipine T parametresi yerine vereceğimiz ve iş nesnesi olarak kullanacağımız ürün tipini, adını Product olacak şekilde belirliyoruz.

public partial class Product
{
    public decimal Stock {get; set; }
}

Artık bu tipimize bir iş kuralı belirlememiz gerekmektedir. İş kuralımızı kısaca stok miktarının kritik olduğu durumu gözetecek şeklide belirleyebiliriz. Stok miktarı 50’nin altına düşerse şartımız çalışacaktır.

public class IsCriticalStock : ISpecification<Product>
{
     public bool IsSatisfiedBy(Product product)
     {
         return candidate.Stock < 50;
     }
}

Şartnameyi kabul eden bir tip olarak IsCriticalStok isimli sınıf oluşturduk. Artık iş kuralını bünyesinde barındıran bu sınıfımızı Product tipine tanıtabiliriz. Product tipimizi yeniden tasarlayacak olursak;

public partial class Product
{
       private readonly IsCriticalStock isCriticalStock;

       public Product()
       {
           isCriticalStock= new IsCriticalStock();
       }

       public decimal Stock {get; set; }

       public bool IsStockEnough()
       {
            return isCriticalStock.IsSatisfiedBy(this);
       }
}

Bu şekilde iş kuralımızı Product tipine geçirmiş oluruz. Kodun okunabilirliğini arttırmak için Product sınıfını parçalı şeklide “Partial” tanımlayabiliriz.

public partial class Product
{
      private readonly IsCriticalStock isCriticalStock;

      public Product()
      {
          isCriticalStock= new IsCriticalStock();
      }

      public decimal Stock {get; set; }
}

public partial class Product
{
      public bool IsStockEnough()
      {
        return isCriticalStock.IsSatisfiedBy(this);
      }
}

Bu örnek aracılığıyla Specification tasarım kalıbını en basit haliyle incelmiş olduk.

Abstract Factory Method Tasarım Deseni

20 Mar

Factory Method tasarım deseni, nesne oluşturma ihtiyacı doğrultusunda ortaya çıkmış bir tasarım desenidir. Bu tasarım deseni, oluşturulacak somut nesnenin türünü belirlemeye gerek duymadan nesne oluşturma işlemini temel almaktadır.

Resim-1
Resim-1

Factory deseninin temel amacı nesne oluşturma karmaşıklığını kullanıcıdan gizlemektir. Ayrıca kullanıcı, oluşturulacak nesnenin somut türünü belirlemek zorunda değildir. Bunun yerine somut nesnenin implemente edildiği soyut tür olan interface veya abstract class tipini bilmesi yeterlidir. Yani sorumluluk somut nesnelerden soyut nesnelere aktarılmaktadır. Genel olarak Factory sınıflar “static” erişim belirleyicili bir metod barındırırlar. Bu metod, kullanıcıya interface veya abstract class tipinde bir nesne döndürür. Bu sayede kullanıcılar, alt sınıfların oluşturulması sorumluluğundan dolayı oluşabilecek hatalardan da uzak tutulmuş olur.

Örnek Uygulama

Bu örnek uygulamamızda Grafik türüne göre Grafiğin temsilini yapan Sembollerin oluşturulmasını Factory Method yöntemiyle üretmeyi amaçlamaktayız.

Yukarıdaki şekildeki tasarım deseninin uygulanışını şu şeklide yapabiliriz.

Resim - 2
Resim – 2

Gerçek bir uygulamadan alarak göstereceğim bu örnekte bir REST servisinden gelen Graphic nesnesinin türüne göre sembol nesnesinin belirlenmesi işlemini Factory Method inceleyeceğiz. Tabi bu örnekteki konumuz Factory uygulaması olduğundan Servis işlemlerini temsili olarak göreceğiz. Bizi ilgilendiren sadece nesne üretimi olduğundan nesne üretim aşamasını inceleyeceğiz.

   public class Graphic
   {
      public Symbol Symbol { get; set; }
   }

Bize servis tarafından sunulan Graphic nesnesi Null olarak gelmektedir. Biz yazılım tarafında Graphic nesnesinin türüne göre Sembolünü üretmekle sorumluyuz.

public abstract class Symbol
{
     public abstract void draw();
}

Symbol sınıfının soyut bir tip olduğunu görüyoruz. Graphic sınıfında da bu soyut tip kullanılmıştır.

public class SimpleLineSymbol: Symbol
{
    public override void draw()
    {
       // Draw line
    }
}

public class SimpleFillSymbol: Symbol
{
     public override void draw()
     {
       // Draw polygon
     }
}

public class SimpleMarkerSymbol: Symbol
{
    public override void draw()
    {
       // Draw point
    }
}

Somut sembol sınıfları ise SimpleLineSymbol, SimpleFillSymbol, SimpleMarkerSymbol şeklinde oluşturulmuştur.

public class GraphicSymbolFactory
{
    public static Symbol GetSymbol(Graphic graphic)
    {
        if(graphic is Point)
           return new SimpleMarkerSymbol();
        if(graphic is Polygon)
           return new SimpleFillSymbol();
        if(graphic is Polyline)
           return new SimpleLineSymbol();

        throw new InvalidExpressionException("Unknown graphic type");
    }

}

GraphicSymbolFactory sınıfına eklediğimiz GetSymbol metodu, Graphic türüne göre somut olan Symbol tiplerini oluşturmaktadır. Dikkat edecek olursak kullanıcıya soyut olan bir Symbol tipi vermekteyiz. Yani içerde olup bitenden kullanıcılar haberdar değildir.

public class GraphicService
{
    private readonly IGraphicRestService service;

    public GraphicService(IGraphicRestService service)
    {
       this.service = service;
    }

    public Graphic GetGraphicsFromRESTService(string serviceUrl)
    {
       Graphic graphic = service.GetGraphic(serviceUrl);

       graphic.Symbol = GraphicSymbolFactory.GetSymbol(graphic);

       return graphic;
    }

}

GraphicService sınıfı içerisinde tanımlı GetGraphicsFromRESTService metodu, bir servis yardımıyla Graphic nesnesini elde etmektedir. Gelen Graphic nesnesinin sembolü ise kullanıcı tarafından Factory sınıfı yardımıyla belirlenir. Burada kullanıcı diye adlandırdığımız GraphicService sınıfıdır aslında. Çünkü API içerisinde hazırladığımız GraphicSymbolFactory tipini kullanan sınıftır. Kullanıcı sınıf aslında Symbol tipinden türetilen SimpleLineSymbol veya SimpleFillSymbol gibi tiplerden haberdar değildir. Bu somut tipleri oluşturmak zorunda da değildir. Bu karmaşık sorumluluğu Factory deseni ile bir sınıfa aktarmış olduk.

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

Antipattern Nedir?

2 Mar

Antipattern kavramını yazılım geliştirme sürecinde programcıların günü kurtarmak adına uyguladıkları kötü çözümler olarak adlandırabiliriz.

Antipattern denilen düzensiz işleyişlerin bilgi ve tecrübe eksikliğinden kaynaklandığını söyleyebiliriz. Bilgi ve tecrübe eksikliği yazılım geliştiricide veya takım yöneticisinde olabilir. Sonuçta kötü etkilenen, yazılım süreci olmaktadır.

Yazılım geliştirirken yapılan Antipattern hataları sonucunda biriken problemler, yazılım ilerledikçe çığırından çıkmaya başlar. Yazılıma eklenmesi istenen yeni özellikler ekibin başına dert olmaya başlar. Hatalar artar, sinirler bozulur ve neticede ortaya konulan ürünün müşteriyi yeterince tatmin etmediği anlaşılır. Müşteri isteklerini yerine getirmeyen yazılım ise başarısızdır.

Kodlama sırasında sık karşılaşılan bazı antipattern kavramlarını şu şekilde belirtebiliriz.

  • Analysis Paralysis: Proje analizine harcanan fazla zaman.
  • Overengineering: Bir problemi olduğundan daha zor olarak algılamak.
  • Smoke and Mirrros: Yanıltıcı araçlar kullanmak.
  • God Object: Çok fazla üye içeren nesneler.
  • Golden Hammer: Bir çözümü birçok probleme uygulamaya çalışmak.
  • Hard Coding: Koda gömülen ve değiştirilemeyen değişkenler.
  • Boat Anchor: Kullanılmayan ancak lazım olur diye tanımlanan kodlar.

Daha fazla antipattern için buradan faydalanabilirsiniz.

Bu örneklere baktığımda yazılım geliştirme sürecini bu sıkıntılardan kurtaracak olan can simidinin çevik süreçler olduğunu görüyorum.

Gerçek anlamda uygulanan çevik sürecin hem tasarım aşamasında uygulanan yöntemler hem kodlama aşamasında uygulanan Test Driven Development tekniği hem de yazılıma uygulanan doğru tasarım desenleri ile antipattern denen illetten kurtulmanın mümkün olacağını düşünüyorum.

Open Closed Principle OCP – Tasarım Prensibi

20 Ağu

İyi kodun üretilmesi, bazı kurallara dikkat ederek mümkün olabilmektedir. Bu kurallarda biri de kodun sürekli gelişebileceğinin göz önünde bulundurulmasıdır. Sonuçta biz bir inşaat mühendisi değil, yazılımcıyız. İnşaat mühendisinin yaptığı yapıyı değiştirmesi neredeyse imkansızdır. Ancak yazılım mühendisinin üretimini her zaman geliştirme ve değiştirme imkanı vardır. Ancak bazen öyle kodlar yazarız ki kodumuzu, içerisine beton dökülmüş gibi bir hale sokar, kodu tekrar düzenleme ve yenileme işlemini imkansızlaştırırız.

Kodlama sırasında birçok değişiklik, uygulamalara yeni özellikler eklerken ortaya çıkmaktadır. Kodda yapılandırma işleminin, kodumuzu en az seviyede etkilemesini amaçlamalıyız. Kodun en az seviyede etkilenmesini sağlamak için kodu üretirken bazı durumları göze alarak hareket etmemiz gerekir. Örneğin mevcut kod zaten bir test işleminden geçerek üretilmiş olabilir. Yeni eklemelerle mevcut test durumlarını yeşilden kırmızıya düşürmemek gerekir. Özetle kodumuz geliştirilmeye açık (Open), değiştirilmeye kapalı(Closed) olmalıdır.

Bu tasarım ilkesini bir örnek üzerinden anlatmaya çalışalım. Öncelikle, open closed tasarım ilkesinin kullanılması için bir sebebimizin olması gerekir. Yani durduk yere bu prensip ortaya çıkmış değil. Hatalı bir çözümün sonucunda ortaya çıkmış olmalı.


public class FileGenerator
{
    private readonly FileBase baseFile;
    public FileGenerator(FileBase bBase)
    {
          baseFile = bBase;
    }

    public string Create()
    {
      if (baseFile is PDF)
      {
         var item = baseFile as PDF;
         return item.Create();
      }

      if (baseFile is WORD)
      {
         var item = baseFile as WORD;
         return item.Create();
      }

      return null;
    }
}

public class FileBase
{
}

public class PDF: FileBase
{
   public string Create()
   {
       return "PDF File";
   }
}

public class WORD : FileBase
{
     public string Create()
     {
        return "WORD File";
     }
}

Bu örneğimizde FileBase tipindeki bir temel tipten türetilmiş WORD ve PDF tipleri oluşturulmuştur. Bir tane de dosya oluşturma işlemini gerçekleştirecek olan FileGenerator tipi oluşturulmuştur. FileGenerator sınıfının Create metodu, base tipine göre dosya üretimi yapmaktadır. Buraya kadar hesaplanan  düzenek çalışır vaziyettedir. Ancak bu düzenekte bazı sorunlar vardır. Bunlar:

  • FileGenerator tipi, dosya tipleriyle (WORD, PDF) birebir ilgilenmekte. Yani nesnelere direk eriştiğinden sıkı bağlılık sözkonusu.
  • Yeni bir dosya tipi eklendiğinde FileGenerator sınıfının Create metodunda değişiklik yapılmak zorunda. Oysa değişikliklere kapalı bir sistem olmalı. Ne kadar dosya tipi oluşturulursa oluşturulsun, FileGenerator sııfı etkilenmemeli. Yani gelişime açık sistem olmalıdır.
  • FileGenerator tipine ait create metodunun testleri her yeni ekleme

[Test]
public void FileGenerator_Generates_PDF()
{
    var file = new FileGenerator(new PDF());
    var created = file.Create();
    Assert.AreEqual("PDF file",created);
}

Yani yukarıdaki tes metodu, WORD için, PDF için çalışırken, FileBase tipinden türetilmiş EXCEL şeklinde yeni bir tip için çalışmayacaktır. Create metodunda değişiklik isteyecektir.

Bu gibi dezavantajları göz önünde bulundurarak yeni bir yapı tasarlayalım.


public class FileGenerator
{
    private readonly IFileBase fileBase;

    public FileGenerator(IFileBase fileBase)
    {
         this.fileBase = fileBase;
    }

    public string Create()
    {
         return fileBase.Create();
    }
}

public interface IFileBase
{
     string Create();
}

public class PDF : IFileBase
{
    public string Create()
    {
        return "PDF file";
    }
}

public class WORD : IFileBase
{
    public string Create()
    {
        return "WORD file";
    }
}

Bu tasarımda, FileGenerator ile dosya tipleri arasında IFileBase interface ile bir esneklik sağlanmıştır. Bu sayede FileGeerator tipinin sadece temel tip olan IFileBase tipinden haberi vardır. WORD, PDF gibi tipleri tanımaz. Bu yüzden yeni eklemeler(örn. EXCEL) FileGenerator sınıfında hiçbir etki yapmayacaktır.Yani değişikliğe kapalı (Cosed) şartını sağlamış olduk. İstediğimiz kadar yeni dosya tipi üretebiliriz. Böylece genişlemeye  de açık (Open) olma şartını sağlamış olduk.

Artık birim(metod) testler de değişikliklere dayanıklı hale gelmiş oldu. Yeni bir tip üretip testten geçirebiliriz.

Sürekli değişikliklere rahat cevap verebiliriz ve müşterilerden gelen yeni dosya tiplerine karşı hazırlıklıyız.

Tekrar görüşmek ümidiyle…

Repository Pattern Nedir?

7 Nis

Veri merkezli yazılımların iş katmanlarından veriye ulaşım işlemleri sırasında meydana gelen ve gözardı edilen bazı ayrıntılar, yazılımın ilerki aşamalarında önümüze bir çığ misali yığılıp kalmaktadır. Özellikle katmanlı mimaride geliştirilen yazılımlarda iş kuralları ve katmanlar, düzgün  oluşturulmadığı taktirde bir işi N defa yapmak zorunda kalabiliriz. Ancak nesneye yönelik (object oriented) yapıların ilkelerinden biri de, bir kuralı bir kez belirleyip N kez çalıştırmaktır. Örneğin veritabanındaki öğrenciler tablosuna öğrenci eklemeyi yapan bir nesne ve bir metod olmalıdır.

İş katmanında dikkat edilmesi gereken bazı kurallar vardır. Bunlar:

  • Kod tekrarlarından kaçınmak
  • Hata yakalamayı kolaylaştırmak
  • Kod yazımını ve okunuşunu kolaylaştırmak
  • Test edilebilir bir yapı kurgulamak

Repository pattern de bizi bu kurallara uymaya zorlamaktadır. Peki repository pattern nedir?

Veri merkezli uygulamalarda veriye erişimin ve yönetimin tek noktaya indirilmesini sağlayan bir tasarım desenidir. Veri merkezli uygulama olması şart değil elbette. Ama bu yazıdaki uygulama veriye erişim kurallarını belirleyen bir örnek üzerinden yürütülecektir.

Örneğimiz, bir ASP.NET MVC web uygulaması içerisinden alınmış repository uygulanışını göstermektedir. Verileri, tek noktadan taşıma işlemini yapacak olan repository yapısı da Entity Framework altyapısını kullanmaktadır. Repository pattern için kullanılan teknoloji çokta önemli değildir. MVC veya Entity Framewok gibi detaylara bu örnekte gerek yoktur, yeri gelmişken bahsetmeden geçemedim. İstersek kendi veri katmanımızı (data layer) hazırlayarak da yola devam edebiliriz.

ASP.NET MVC mimarisinin tanıtımı konusunda en etkili örnek olan açık kaynak kodlu Nerddinner uygulaması üzerinden işlemlerimizi yürütmeye çalışacağız. Neden hazır bir uygulama üzerinden gittim? Kendim de bir sınıf hazırlayıp burada kullanabilirdim pek tabi. Ancak, Nerddinner uygulamasını merak edip bakmanızı da istedim, çok faydalı olacağından eminim.

Nerddinner nedir? Hikaye olarak, insanların yemek yiyebilecek yerleri internet üzerinden kaydedebilecekleri veya daha önceden, başkaları tarafından kaydedilen yemek yenilebilecek yerleri görebilmelerini sağlamak şeklinde özetlenebilir. Bu kısmı da yeri gelmişken kısaca özetledikten sonra konumuza dönebiliriz.

Projede yemek yerleri, Dinner tablosunda tutulmaktadır. Entity framework tarafında da, veritabaındaki Dinner tablosu Dinner sınıfı tarafından temsil edilmektedir. Bu noktada Dinner tablosuna CRUD(Create, Update, Delete) işlemlerini gerçekleştirebilmek için bir arayüz (interface) tanımlanır. Bu arayüzü uygulayan(implement) eden sınıflar Dinner tablosuna CRUD işlemlerini yapmaya hazır demektir.

Hazırladığımız interface aşağıdaki gibidir.


public interface IDinnerRepository {

       IQueryable<Dinner> FindAllDinners();
       IQueryable<Dinner> FindDinnersByText(string q);

       Dinner GetDinner(int id);

       void Add(Dinner dinner);
       void Delete(Dinner dinner);

       void Save();
}

Bu arayüzü uygulayan bir sınıfı da aşağıdaki gibi gösterebiliriz.

public class DinnerRepository : NerdDinner.Models.IDinnerRepository
{

       NerdDinnerEntities db = new NerdDinnerEntities();

       Public IQueryable<Dinner> FindDinnersByText(string q)
       {
              return db.Dinners.Where(d => d.Title.Contains(q)
                     || d.Description.Contains(q)
                     || d.HostedBy.Contains(q));
       }

       public IQueryable<Dinner> FindAllDinners()
       {
              return db.Dinners;
       }

       public Dinner GetDinner(int id)
       {
             return db.Dinners.SingleOrDefault(d => d.DinnerID == id);
       }

       public void Add(Dinner dinner)
       {
             db.Dinners.AddObject(dinner);
       }

       public void Delete(Dinner dinner)
       {
            foreach (RSVP rsvp in dinner.RSVPs.ToList())
                db.RSVPs.DeleteObject(rsvp);

            db.Dinners.DeleteObject(dinner);
      }

      public void Save()
      {
            db.SaveChanges();
      }

}

Buradaki NerdDinnerEntities tipi Entity Framework tarafına ait, ObjectContext tipinden türemiş ve kullanıcı tarafından oluşturulan nesneleri alıp kaydetme, güncelleme veya silme işlemleri için oluşturulmuş, motor(engine) görevindeki sınıftır.

Save metodu içerisinde de zaten NerdDinnerEntities tipinin SaveChanges metodu çağrılmıştır ki bu metod, yapılan değişiklikleri veritabanına yansıtır.

Artık Dinner tipindeki bir nesnenin veritabaına kaydedilmesi, güncellenmesi veya silinmesi gibi işlemlerden sorumlu olan sınıf DinnerRepository sınıfıdır. Başka hiçbir yerde bu işlemler için kod yazmaya gerek yoktur. Bir süre sonra, sistemde tarihe göre arama işlemi yapılmak istendiğinde, ilgili metod IDinnerRepository arayüzünde belirlenerek, DinnerRepository sınıfına uygulandığında işlem tamamdır. Tek sorumluluk(Single Responsibility)  DinnerRepository sınıfına aittir. Bu sayede yazılım kurallarından “Tek Sorumluluk” ilkesine de uymuş olduk.

Repository pattern sayesinde test edilebilirlik te mümkün hale gelmektedir. Bu daha ayrıntılı bir konu olduğu için ileriki yazılarımızda işlemeye çalışacağız.

Repository patternle karşılaşmadan önce, kendi kendime desenler oluşturup, tek noktadan veri yönetimini sağlamaya çalışıyordum. Ancak bu desen bana daha düzgün geldi ve artık bu deseni iyice benimsedim. Test gücünü de gördükten sonra vazgeçilmezim oldu. Herkese de tavsiye ederim.