Caner Tosuner

Leave your code better than you found it

Constructor Injection Hell (IoC)

Daha önceki IoC yazılarımızda nedir ne değildir den uzunca bahsedip hayatımıza ne gibi güzellikler getirdiğini anlatmıştık. Basitçe tekrar değinmek gerekirse uygulamadaki instance yönetiminden sorumlu ve bu instance'ları interface seviyesinde kullanıp runtime da container tarafından resolve edilip uygulamada kullanabiliyoruz.

Projenin İlk Günlerinde...

Kullanmak istediğimiz interface'leri aşağıdakine benzer bir şekilde constructor'a inject ediyoruz.

    public class FooController : ApiController
    {
        private readonly IUserService _userService;

        public FooController(IUserService userService)
        {
            _userService = userService;
        }
    }

1-2 Yıl Sonra...

Projenin ilk başlarında aslında ne de güzel duygularla geliştirme yaparken 1-2 sene sonra FooController'ın son durumunun aşağıdaki gibi bir hal aldığını görebiliyoruz.

    public class FooController : ApiController
    {
        private IUserService _userService;
        private ICustomerService _customerService;
        private IBankService _bankService;
        private IAccountService _accountService;
        private ITransferService _transferService;
        private IBranchService _branchService;
        private IEmployeeService _employeeService;
        private IMoneyService _moneyService;
        private IBasketService _basketService;
        private IAyService _ayService;
        private IVayService _vayService;
        private ILayService _layService;
        private IUserService _userService;
        private ICustomerService _customerService;
        private IBankService _bankService;
        private IAccountService _accountService;
        private ITransferService _transferService;
        private IBranchService _branchService;
        private IEmployeeService _employeeService;
        private IMoneyService _moneyService;
        private IBasketService _basketService;
        private IAyService _ayService;
        private IVayService _vayService;
        private ILayService _layService;
        private IUserService _userService;
        private ICustomerService _customerService;
        private IBankService _bankService;
        private IAccountService _accountService;
        private ITransferService _transferService;
        private IBranchService _branchService;
        private IEmployeeService _employeeService;
        private IMoneyService _moneyService;
        private IBasketService _basketService;
        private IAyService _ayService;
        private IVayService _vayService;
        private ILayService _layService;
        private IUserService _userService;
        private ICustomerService _customerService;
        private IBankService _bankService;
        private IAccountService _accountService;
        private ITransferService _transferService;
        private IBranchService _branchService;
        private IEmployeeService _employeeService;
        private IMoneyService _moneyService;
        private IBasketService _basketService;
        private IAyService _ayService;
        private IVayService _vayService;
        private ILayService _layService;

        public FooController(IUserService userService,
                             ICustomerService customerService,
                             IBankService bankService,
                             IAccountService accountService,
                             ITransferService transferService,
                             IBranchService branchService,
                             IEmployeeService employeeService,
                             IMoneyService moneyService,
                             IBasketService basketService,
                             IAyService ayService,
                             IVayService vayService,
                             ILayService layService,
                             IUserService userService,
                             ICustomerService customerService,
                             IBankService bankService,
                             IAccountService accountService,
                             ITransferService transferService,
                             IBranchService branchService,
                             IEmployeeService employeeService,
                             IMoneyService moneyService,
                             IBasketService basketService,
                             IAyService ayService,
                             IVayService vayService,
                             ILayService layService,
                             IUserService userService,
                             ICustomerService customerService,
                             IBankService bankService,
                             IAccountService accountService,
                             ITransferService transferService,
                             IBranchService branchService,
                             IEmployeeService employeeService,
                             IMoneyService moneyService,
                             IBasketService basketService,
                             IAyService ayService,
                             IVayService vayService,
                             ILayService layService,
                             IUserService userService,
                             ICustomerService customerService,
                             IBankService bankService,
                             IAccountService accountService,
                             ITransferService transferService,
                             IBranchService branchService,
                             IEmployeeService employeeService,
                             IMoneyService moneyService,
                             IBasketService basketService,
                             IAyService ayService,
                             IVayService vayService,
                             ILayService layService)
        {
            _userService = userService;
            _customerService = customerService;
            _bankService = bankService;
            _accountService = accountService;
            _transferService = transferService;
            _branchService = branchService;
            _employeeService = employeeService;
            _moneyService = moneyService;
            _basketService = basketService;
            _ayService = ayService;
            _vayService = vayService;
            _userService = userService;
            _customerService = customerService;
            _bankService = bankService;
            _accountService = accountService;
            _transferService = transferService;
            _branchService = branchService;
            _employeeService = employeeService;
            _moneyService = moneyService;
            _basketService = basketService;
            _ayService = ayService;
            _vayService = vayService;
            _userService = userService;
            _customerService = customerService;
            _bankService = bankService;
            _accountService = accountService;
            _transferService = transferService;
            _branchService = branchService;
            _employeeService = employeeService;
            _moneyService = moneyService;
            _basketService = basketService;
            _ayService = ayService;
            _vayService = vayService;
            _userService = userService;
            _customerService = customerService;
            _bankService = bankService;
            _accountService = accountService;
            _transferService = transferService;
            _branchService = branchService;
            _employeeService = employeeService;
            _moneyService = moneyService;
            _basketService = basketService;
            _ayService = ayService;
            _vayService = vayService;
        }
    }

Not : Yukarıda initialize edilen interface'ler örnek olarak kullanıldı. Kod bloğunu kendi projenize copy paste yaptığınızda bu interface'ler projede oluşturulmadığından hata alacaksınızdır. Sadece constructor'a inject edilen nesnelerin fazlalığından bahsetmek adına yukarıdaki gibi kullanım uygulanmıştır.

Kullanılan interface sayısı bir elin parmaklarıyla sınırlı olduğunda constructor injection uygulamak oldukça güzel duruyor ancak çok daha büyük bir projede aylar yıllar geçtikçe yukarıdaki gibi bir constructor injection hell dediğimiz durum ortaya çıkabiliyor.

Bu durumu sadece Controller üzerinde değilde constructor injection uyguladığımız her hangi bir yer Service class'ı, Repository olabilir.  

TDD uyguladığımızı da düşünürsek bu controller'ın birde test class'ları olacaktır. Aynı injection muhabbeti ordada önümüzde çıkacaktır.

Çözüm Olarak..

Aslında bu durumu bir sorun olarak da görmeyebiliriz de. Sonuçta "kod çalışıyor abi tıkır tıkır devam.." diyebiliriz ancak bunun bir sıkıntı olduğunu geliştirme yaparken anlıyorsunuz. Alt alta veya yan yana satırlarca kod ortaya çıktığında kodu okumak istemiyorsunuz ve bir süre sonra yeni bir interface enjecte etmeye kalktığınızda Vs kasmaya başladığını anlıyorsunuz.

Peki ne yapabiliriz bunun için ?

Projemizde container olarak Castle Windsor kullandığımızdan yola çıkarak windsor'ın görevi bize instance yönetimini sağlamaksa bizde Controller'larda kullanacağımız interface'leri implementasyonu olmayan bir IServiceFactory adında bir interface'de tanımlayıp container'a Typed Factory Facility servisini register edip sonrasında interface'imizi register edeceğiz.

    public interface IServiceFactory
    {
        IUserService CreateUserService();
        void Release(IUserService userService);

        ICustomerService CreateCustomerService();
        void Release(ICustomerService customerService);

        IBankService CreateBankService();
        void Release(IBankService bankService);

        IAccountService CreateAccountService();
        void Release(IAccountService accountService);

        ITransferService CreateTransferService();
        void Release(ITransferService transferService);

        IBranchService CreateBranchService();
        void Release(IBranchService branchService);

        IEmployeeService CreateEmployeeService();
        void Release(IEmployeeService employeeService);

        IMoneyService CreateMoneyService();
        void Release(IMoneyService moneyService);

        IBasketService CreateBasketService();
        void Release(IBasketService basketService);

        IAyService CreateAyService();
        void Release(IAyService ayService);

        IVayService CreateVayService();
        void Release(IVayService ayService);

        ILayService CreateLayService();
        void Release(ILayService layService);
    }

Bu interface bir dummyService interface'i ve tek görevi bize istediğimiz service'in instance'ını container'dan resolve ederek vermek.

Sırada son adım olarak Factory interface'ini register etmek. Yukarıda bahsettiğimiz gibi container'a Typed Factory Facility servisini register edip sonrasında IServiceFactory interface'ini aşağıdaki gibi register edeceğiz. 

Container.AddFacility<TypedFactoryFacility>();
Container.Register(Component.For<IServiceFactory>().AsFactory());

Geliştirmemiz hazır. Artık FooController'a gidip IServiceFactory interface'ini constructor injection yöntemiyle inject edebiliriz.

    public class FooController : ApiController
    {
        private IServiceFactory _serviceFactory;

        public FooController(IServiceFactory serviceFactory)
        {
            _serviceFactory = serviceFactory;
        }
    }

Görüldüğü üzre satırlarca uzayan kodlardan kurtulduk ve artık tek bir interface üzerinden ihtiyaç duyduğumuz service'in Create() metodunu çağırarak service içerisinde tanım olan metotları vs. kullanabiliriz.

Örnek olarak;

    public class FooController : ApiController
    {
        private IServiceFactory _serviceFactory;

        public FooController(IServiceFactory serviceFactory)
        {
            _serviceFactory = serviceFactory;
        }


        [HttpGet]
        public HttpResponseMessage GetCustomers()
        {
            var response = _serviceFactory.CreateCustomerService().GetAllCustomers;
            return Request.CreateResponse(response);
        }
    }

Geliştirmemiz bu kadar. Büyük ölçekte projelerde çalışanların büyük bir kısmı benzer bir sorunla karşılaşmıştır veya karşılaşacaktır. Çözüm olarak farklı alternatiflerde mevcut bu sadece onlardan bir tanesiydi. 

Typed Factory Facility ile ilgili daha geniş bilgiyi bu linkten bulabilirsiniz.

Castle Windsor Kullanarak Logging Interceptor Oluşturma

Daha önceki Aspect Oriented yazılarında interceptor'lardan bahsetmiştik ve biz developer'lar için bulunmaz bir nimet olduğunu söylemiştik. Server-Side bir projede olmazsa olmaz özelliklerin başında gelen Logging, Exception Handling, Caching vs gibi özellikleri çok basit küçük interceptor'lar yazarak uygulamamıza bu özellikleri kazandırabiliriz. Daha önceki yazılarımızda bu ihtiyaçları karşılayabilmek için .Net tarafında oldukça entegrasyonu basit olan Postsharp kütüphanesinden faydalanmıştık. Bugün ki yazımızda ise IoC container'lardan Castle Windsor'ı kullanarak uygulamamız için bir Logging intercepter'ı geliştireceğiz. 

Örneğimiz şu şekilde; Email göndermek için kullanılan basit bir Service projemiz olsun ve parametre olarak Address ve html olarak Content alsın. İlk olarak VS de EmailSender adında bir Api projesi oluşturalım ve projemizin referanslarına Nuget üzerinden Castle Windsor'ın paketlerini indirip kuralım.

Sonrasında SendEmailRequest adında request modelimizi tanımlayalım

    public class SendEmailRequest
    {
        public string Address { get; set; }
        public string Content { get; set; }
    }

Email gönderme işlemini yapacak olan service interface'imiz ve onun impl. class'ını aşağıdaki gibi oluşturalım

    public interface IEmailService
    {
        bool Send(SendEmailRequest reqModel);
    }
    public class EmailService : IEmailService
    {
        public bool Send(SendEmailRequest reqModel)
        {
            //todo return true dedik ancak bu kısımda email göndermek için kullandığınız kodları yazmalıyız. 
            return true;
        }
    }

Email gönderme servisimiz artık kullanıma hazır. Artık bu service'i Api ile dışarıya açma zamanı. Projemize CommunicationController adında bir controller ve bu controller içerisinde HttpPost kabul eden SendEmail adında bir endpoint tanımlayalım.

    public class CommunicationController : ApiController
    {
        private readonly IEmailService _emailService;

        public CommunicationController(IEmailService emailService)
        {
            _emailService = emailService;
        }

        [HttpPost]
        public HttpResponseMessage SendEmail(SendEmailRequest reqModel)
        {
            var result = _emailService.Send(reqModel);
            if (!result)
            {
                return Request.CreateResponse(HttpStatusCode.NotFound);
            }
            return Request.CreateResponse(result);
        }
    }

Yukarıda görüldüğü üzre kullanacağımız IEmailService'ini Castle kullanarak EmailService'ine inject edeceğiz ve controller seviyesinde bu service interface'ini kullanarak email gönderme işlemini yapacağız.

Şimdi sırada LoggingInterceptor'ımızı oluşturma var. Bu interceptor ile log kayıtlarına MethodName, request ise aldığı parametreler ve response için return edilen değer bilgilerini atacağız. 

    public class LoggingInterceptor : IInterceptor
    {
        public void Intercept(IInvocation invocation)
        {
            var serializer = new JavaScriptSerializer();
            var parametersJson = serializer.Serialize(invocation.Arguments);

            System.Diagnostics.Debug.WriteLine("Request of " + invocation.Method.Name + " is " + parametersJson);

            invocation.Proceed();

            var returnValueJson = serializer.Serialize(invocation.ReturnValue);

            System.Diagnostics.Debug.WriteLine("Response of " + invocation.Method.Name + " is: " + invocation.ReturnValue);
        }
    }

Logları şimdilik sadece Output Window'a yazdırdım ancak gerçek hayatta tabikide NLog vs gibi bir kütüphane kullanıyor olmamızda fayda var. 

Interceptor'ımızda hazır olduğuna göre artık IoC Initialize tarafına geçip gerekli register işlemlerimizi yapabiliriz. ServiceInstaller adında aşağıdaki gibi bir class oluşturalım ve içerisinde service'imizi ve interceptor'ımızı register edelim.

    public class ServiceInstaller : IWindsorInstaller
    {
        public void Install(IWindsorContainer container, IConfigurationStore store)
        {
            container.Register(Component.For(typeof(IEmailService))
                     .ImplementedBy(typeof(EmailService))
                     .Interceptors(typeof(LoggingInterceptor)));
        }
    }

EmailSender servisini ve interceptor'ı register eden installer'ı tanımladık. Şimdi ise Web Api projemizin controller'ını register eden installer'ı oluşturalım.

    public class WebApiControllerInstaller : IWindsorInstaller
    {
        public void Install(IWindsorContainer container, IConfigurationStore store)
        {
            container.Register(Classes.FromThisAssembly()
                .BasedOn<ApiController>()
                .LifestylePerWebRequest());
        }
    }

 

    public class ApiControllerActivator : IHttpControllerActivator
    {
        private readonly IWindsorContainer _container;

        public ApiControllerActivator(IWindsorContainer container)
        {
            _container = container;
        }

        public IHttpController Create(
            HttpRequestMessage request,
            HttpControllerDescriptor controllerDescriptor,
            Type controllerType)
        {
            var controller =
                (IHttpController)this._container.Resolve(controllerType);

            request.RegisterForDispose(
                new Release(
                    () => this._container.Release(controller)));

            return controller;
        }

        private class Release : IDisposable
        {
            private readonly Action _release;

            public Release(Action release)
            {
                _release = release;
            }

            public void Dispose()
            {
                _release();
            }
        }
    }

Son adım olarak ise oluşturduğumuz bu installer'ları container'a Install edeceğiz. Bunun için projemizde bulunan Global.asax içerisinde bulunan Application_Start metodu aşağıdaki gibi olacak.

        protected void Application_Start()
        {
            var container = new WindsorContainer();
            container.Install(new ServiceInstaller());
            container.Install(new WebApiControllerInstaller());
            GlobalConfiguration.Configuration.Services.Replace(
                typeof(IHttpControllerActivator),
                new ApiControllerActivator(container));
            GlobalConfiguration.Configure(WebApiConfig.Register);
        }

Hepsi bu kadardı :) 

Şimdi yazdığımız kodları test edelim. Postman kullanarak controller'da bulunan endpoint'e aşağıdaki gibi bir istek gönderip Output Window dan neler yazdırdığına bir bakalım.

 

Request sonrasında Interceptor araya girerek loglama işlemini aşağıdaki görselde olduğu gibi yapmakta.

Örneğimiz şimdilik burada bitiyor, sizlerde projeleriniz için bir çile haline gelebilme potansiyeli olan Loglama konusunu interceptor kullanarak son derece basit ve reusable hale getirebilirsiniz.

Sonraki yazılarımızda Interceptor kullanarak daha başka neler yapabiliriz fırsat buldukça inceleyeceğiz.

IoC Inversion of Control nedir ?

IoC den önce gerilere gidip Dependency Injection'ın tanımınada değinelim. Dependency Injection kısaca "bağımlılıkların  loose coupled yani gevşek bağlı bir şekilde dışarıdan enjecte edilmesi" şeklinde tanımlayabiliriz.

Inversion of Control (IoC) ise bir yazılım tasarım prensibidir ve basit tabiriyle nesnelerin uygulama boyunca ki yaşam döngüsünden sorumludur diyebiliriz. Uygulama içerisinde kullanılan objelerin instance'larının yönetimi sağlar ve bağımlılığı en aza indirgemeyi amaçlar. 

Container program içerisinde request edilen nesneleri abstraction'lara bağlı tutarak otomatik olarak oluşturan ve bağımlılıklarını inject eden bir framework diyebiliriz. Oluşturmuş olduğu bu nesneleri kendi içerisinde yönetimini yaparak tekrardan ihtiyaç duyulduğunda yeni bir instance oluşturmak yerine mevcut olan nesneyi atar.

IoC daha kolay test edilebilir loose coupling dediğimiz gevşek bağlı ve reusable bir yazılım desene oluşturmamızı sağlar.

IoC ilk başlarda implementasyonundan dolayı zor ve karmaşık gibi görünsede geliştirme yaptıkça ve sağladığı kolaylıkları fark ettikçe hayran kalınacak bir yazılım tasarım prensibidir. Basitçe işleyişini anlatmak gerekirse; soyut tiplerin hangi somut tipler tarafından register edildiği bilgisini tutar. Uygulama içerisinde container'dan abstract bir nesne talebinde bulursunuz ve size register bilgisinde tanımlı olan concrete type'ın instance'ını oluşturup verir. Bir tür object factory olarak düşünebilirsiniz.

IoC için kullanılabilecek çeşitli kütüphaneler bulunmakta. Bunlardan en popüler olanlarını ;

şeklinde sıralayabiliriz. Bu kütüphaneleri kullanmayıp kendi IoC infrastructure'ınızıda yaratabilirsiniz ancak instance yönetimi dışında bu kütüphanelerin sağladığı aspect oriented özellikleri de hayli önemli bir diğer özelliktir. 

Framework ler arasından en performanslı olan hangisi sorusunu soracak olursak internette araştırırken bir benchmark testine denk geldim ve aşağıdaki koşullar sağlanarak yapılan benchmark sonucuna göz atalım.

Test Verisi

  • 8 levels of depth for the dependency tree
  • 100 types per level
  • Between 0 and 8 dependencies for each type (excluding level 0)
  • 1 in 5 types are registered as singleton (20%)

 

Sonuç

Container 2012 Version 2012 Time Elapsed (Sec) 2014 Version Time Elapsed (Sec)
Ninject 3.0.1.10 31.29 3.2.2.0 30.84
StructureMap 2.6.2 1.98 3.1.4.143 1.95
Autofac 2.6.3.862 5.56 3.5.2 5.19
Castle Windsor 3.1.0 5.47 3.3.0 5.59
Unity 2.1.505.2 7.76 3.5.1404.0 3.71
SimpleInjector 1.5.0.12199 34.43 2.5.2 48.01
Dynamo 3.0.0.1 Fail 3.0.2 Fail
Hiro 1.0.2 Fail 1.0.2 Fail

 

Görüldüğü üzre StructureMap bütün 800 tipi register ve resolve etmede en hızlı olan ancak en çok kullanılan olarak bakacak olursak Castle Windsor galip geliyor. Unity'nin de son sürümüyle birlikte bugün itibariyle en hızlı olan IoC framework'ü olduğu söylenmekte.

IoC ile ilgili bugünlük bu kadar diyelim gelecek yazılarımızda seçtiğimiz bir IoC framework'ünü kullanarak örnek projeler geliştiriyor olacağız.

Kısaca Agile Nedir Neler Gerektirir

Agile aşağı Agile yukarı... Herkes bir agile'dır gidiyor ama malesef çoğu kişi için agile bir kaç cümle veya kelimeden ibaret.

Bir çok Yönetici & Patron için çabucak at koşturur gibi işi yapıp bitirme,

Bir çok çalışan için ise agile jira'da bulunan bug'ları çözmekten ve scrum yapmaktan ibaret..

Malesef Türkiye'de biraz gerilerde ancak güzel haberlerde gelmiyor değil, öyle ki bazı kurumsal bankalar dahi yazılım departmanı olmadığı halde kendi iç birimlerinde agile'ı test etmeye başladığı söyleniyor ve Türkiye'de bulunan çok büyük bir bankanın da bütün birimleriyle agile'a %100 geçtiğiyle ilgili geçenlerde bir haber okumuştum. Tabi bunlar agile doğru anlaşıldığı zaman güzel haberler oluyor.

Sektörde bulunan yazılım firmalarında ki bir çok development takımı kendi içlerinde agile yaptıklarını söylüyorlar ancak işin aslını konuşmaya çalıştığınızda anlıyorsunuz ki sadece agile'a ait bir kaç işi yaptıkları ortaya çıkıyor.

Scrum için ise aslında yapılan şey iş yoğunluğa göre bazı sabahlar proje müdürünün ekibi toplayıp sadece kendisinin konuşması...

 

Peki Agile olmak neleri gerektirir ?

Daha önce okuduğum ve bir çok kişininde referans alarak uygulamaya çalıştığı "What Do We Know About Scientific Software Development’s Agile Practices ?" makalesinde belirtilen 35 Agile Best Practice'i baz alarak takım olarak ne yapıp ne yapmıyorsunuz, ne kadar agile'sınız yorumlayabilirsiniz. 

  1. Önceliklendirmeler projede en yetkili kişi olan product owner tarafından (ekibin fikrini alarak) yapılmalı,
  2. Development sürecindeki sorunlar scrum master tarafından scrum'larda çözüm aranmalı,
  3. Spring backlog oluşturmak için Spring planlama toplantıları yapılmalı,
  4. Efor verirken Planning Poker oyunu gibi eğlenceli bir seçiminiz olmalı,
  5. Kısa ve Orta vadede koşan sprint'leriniz olmalı,
  6. Product owner ve ekip arasında sürekli bir communication olmalı,
  7. Mevcut konuları konuşmak için kısa daily meeting'ler düzenlenmeli,
  8. Self-organization yani kendiliğinden organize olabilen bir ekip uluşturulabilmeli,
  9. Grafikler ile sprint süreci gözlemlenmeli,
  10. Sprint sonlarında review toplantıları düzenlenmeli,
  11. Retrospective yaparak geçmiş sprintle ilgili değerlendirmelerde bulunulmalı,
  12. Short & Fast product release'ler çıkılmalı,
  13. User storie'ler yazılmalı,
  14. Takıma dedike bir şekilde çalışabileceği açık çalışma alanları yaratılmalı,
  15. Sürdürülebilir bir çalışma ortamı sunulmalı,
  16. Proje ivmesi ölçülebilmeli,
  17. Ekipte bulunanlar her konuda fikrini söyleyebilmeli,
  18. Müşteriye ihtiyaç duyulduğunda hemen ulaşılabiliyor olmalı,
  19. Ekip olarak kod standartları belirlenmeli,
  20. İlk önce unit-test'ler yazılmalı,
  21. Prod'a alınan kod'un bir pair-programming ürünü olduğu unutulmamalı,
  22. Geliştirmeler branch'lere ayrılarak yapılmalı ve test sonuçlarına göre merge işlemi yapılmalı,
  23. Sürekli olarak yeni şeyler entegre edilebilecek altyapıya sahip bir altyapı,
  24. Repository yönetimini iyi yapıp release günü geldiğinde sorunsuz bir şekilde release çıkılabilmeli,
  25. Collective bir biçimde developer'lar atayın bir sorumluluğu tek bir kişiye yüklemekten kaçının,
  26. Basit bir tasarıma desenine sahip olmalı,
  27. Bir sistem metforu seçilmeli,
  28. Design&Development için Class-responsibility-collaboration kartlarını kullanmaya çalışın,
  29. Riski azaltmak için çözümler üretilmeli,
  30. İhtiyaç duyulmayan hiç bir fonksiyonalite önceden entegre edilmemeli,
  31. Ne zaman-Nerde olduğuna bakılmaksızın refactor edilebilmeli,
  32. Bütün kod'un unit-test'i yazılmış olmalı,
  33. Proje prod'a alınmadan önce bütün unit-test'lerden geçmeli,
  34. Yeni bir bug bulunduğunda testler anında oluşturulmalı,
  35. Acceptence-test'ler sıklıkla uygulanmalı ve skor yayınlanmalı.

 

Agile hakkında söylenecek oldukca fazla şey bulunuyor ancak kısa bir özetle yukarıda ki rule'ları sıralamış olduk. İyi bir agile-team oluştururken bu kurallardan faydalanmak çöğunlukla iyi olacaktır ama belirttiğim gibi liste genişletilebilir de.

Caching With Postsharp

Cache bir proje için olmazsa olmazlardan biridir diyebiliriz ve ihtiyaç duyulduğunda çok can kurtarır. Öyleki çok fazla değişmesini düşünmediğiniz verileri cache de saklayarak reqeust response time konusunda projenize oldukça fazla performans kazandırabilirsiniz. Daha önceki web api için cache ve aspect oriented - postsharp konularından bahsetmiştik. WCF Service projenize Postsharp ile basit bir server-side cache yapısı kurabilirsin.

İlk adım olarak cache de tutacağımız CacheModel adlı model'i tanımlayalım.

public class CacheModel
{
    public DateTime CacheTimeOutInfo { get; set; }//cache'in ne zaman timeout olacağını belirten time bilgisi
    public object CacheData { get; set; }//cache'de tutulacak object
}

Şimdi ise CacheManager'a implement edeceğimiz ICache interface'i tanımlayalım ve içerisine cache metotlarımızı yazalım.

public interface ICache
{
   bool Contains(string key);//key varmı yokmu diye control ettiğimiz metot
   void Add(string key, CacheModel cacheData);//cache key'i ile birlikte cache model'i alıp cache'e ekleyen metot
   object Get(string key);//key parametresi alarak cache'de ki data yı return eden metot
   void Remove(string key);//key parametresine göre mevcut cache'i silen metot
   void RemoveAll();//bütün cache'i silen metot
}

Aşağıda yazacağımız class'ta ise üstte yazdığımız ICache'i implement eden cache yönetimini yapacağımız CacheManager class'ını oluşturacağızz.

    public class CacheManager : ICache
    {
        public bool Contains(string key)
        {
            var isExist = HttpRuntime.Cache[key];
            if (isExist != null)
            {
                var obj = GetCacheModel(key);
                if (obj.CacheTimeOutInfo > DateTime.Now)
                    return true;
                Remove(key);
            }
            return false;
        }

        public void Add(string key, CacheModel cacheData)
        {
            if (Contains(key))
                Remove(key);
            HttpRuntime.Cache[key] = cacheData;
        }

        public object Get(string key)
        {
            var obj = GetCacheModel(key);
            return obj.CacheData;
        }

        private CacheModel GetCacheModel(string key)
        {
            return (CacheModel)HttpRuntime.Cache[key];
        }

        public void Remove(string key)
        {
            HttpRuntime.Cache.Remove(key);
        }

        public void RemoveAll()
        {
            var enumerator = HttpRuntime.Cache.GetEnumerator();
            while (enumerator.MoveNext())
            {
                HttpContext.Current.Cache.Remove((string)enumerator.Key);
            }
        }
    }

Sırada Postsharp kullanarak oluşturacağmız CacheAspect adında interceptor'ı yazama var. Bunun için nuget'ten postsharp'ı indiriyoruz ve aşağıdaki gibi CacheAspect adındaki interceptor'ımızı oluşturuyoruz.

    [Serializable]
    public class CacheAspect : OnMethodBoundaryAspect
    {
        //Metottan alacağımız cache süresi
        public int MethodCacheDurationInMinute = 0;

        public override void OnEntry(MethodExecutionArgs args)
        {
                 //cache key oluşturma
                var cacheKey = CreateMD5(GenerateCacheKey(args));

                var cacheManager = new CacheManager();

                var isCacheKeyExist = cacheManager.Contains(cacheKey);

                if (isCacheKeyExist)
                { 
                    //eğer request geldiğinde cache key cache de varsa client'a cahe de bulunan veriyi dön
                    args.ReturnValue = cacheManager.Get(cacheKey);
                    args.FlowBehavior = FlowBehavior.Return;
                }
                else
                {
                    //cache de yoksa normal akışta ilerlemesine devam eder
                    args.MethodExecutionTag = cacheKey;
                }
        }

        public override void OnSuccess(MethodExecutionArgs args)
        {
                //başarılı bir şekilde metottan çıktıysa cache'e at

                    var cacheKey = (string)args.MethodExecutionTag;

                    var cacheManager = new CacheManager();
                    var cacheModel = new CacheModel
                    {
                        CacheData = args.ReturnValue,
                        CacheTimeOutInfo = DateTime.Now.AddMinutes(MethodCacheDurationInMinute)
                    };
                    cacheManager.Add(cacheKey, cacheModel);
        }

        private static string GenerateCacheKey(MethodExecutionArgs args)
        {
            var generatedKey = string.Empty;

            if (args.Method.DeclaringType != null)
                generatedKey = args.Method.DeclaringType.Name + "-" + args.Method.Name + (args.Arguments.Any() ? "-" + args.Arguments.Aggregate((first, second) => first + "-" + second) : "");
            generatedKey += SerializeObjectToJson(args.Arguments);

            return generatedKey;
        }

        public static string CreateMD5(string input)
        {
            using (var md5 = System.Security.Cryptography.MD5.Create())
            {
                var inputBytes = Encoding.ASCII.GetBytes(input);
                var hashBytes = md5.ComputeHash(inputBytes);

                var sb = new StringBuilder();
                foreach (var t in hashBytes)
                {
                    sb.Append(t.ToString("X2"));
                }
                return sb.ToString();
            }
        }
    }

Artık sıra cache'i projemizde kullanmaya geldi. yazdığımız interceptor'ı projede hangi metodu cache'e atmak istiyorsak metodun başına attribute olarak ekleyip kaç dakikalık cache'de tutacağını belirteceğiz.

        [CacheAspect(MethodCacheDurationInMinute = 120)] //120 dakikalığına cachelemesini söylüyoruz
        public List<FooModel> Foo(FooRequest reqModel)
        {
            var fooManager=new FooManager();
            return fooManager.GetFooList();
        }

 

Kullanım olarak oldukç basit ve projede hangi metotta istersek aynı yukarıda olduğu gibi attribute olarak projemize ekleyebiliriz. Yazının başında da belirttiğim gibi cache bir çok zaman hayat kurtarır ve yabana atılmaması gereken bir konudur.

 

Builder Design Pattern

Design pattern'leri spagetti kodlara veya tekrar eden kod bloklarına çözümler olarak ortaya çıkmışlardı. Builder Design Pattern creational patterns kategorisi altında bulunan patternlerden birisidir ve kısaca basit objeler kullanarak complex objeler inşa etmede kullanılır diyebiliriz. 

 

Builder'ı DTO yani data transfer object class'larını örnek olarak düşünürsek, bu objeler birden fazla objenin birleşmesinden oluşurlar ve genelde complex objelerdir. Builder pattern'nini de base'de bir obje var ve bu objeye ait alt property'leri oluştururken her bir özellik için ayrı build metotları yazmamız gerekir. Builder design pattern'i bu gibi durumlarda kullanabilecegimiz bir pattern olarak dusunebiliriz. Başka bir deyişle; runtime da yaratılması gereken complex objelerinizin olduğu bir projeniz varsa bu pattern'i kullanmak güzel bir seçim olabilir veya adım adım obje yaratma işlemlerini yönetmek istediğiniz bir durum var ise yine bu pattern'i kullanabiliriz.

Builder pattern'i 4 ana yapıdan oluşur;

  • Product  : Build edilen obje,
  • Builder   : Product'ı build eden abstract yapı,
  • Concrete Builder : Builder nesnesinin abstract yapıyı kullanarak aynı yapıdaki farklı ürünlerin build edilmesini sağlayan yapı,
  • Director  : Kendisine verilen Builder'ı property'lerini kullanarak ürünü inşa eden yapı.

Örnek bir proje üzerinden Builder Design Pattern'i implement etmeye çalışalım. Apple fabrikasında Iphone modelleri üretimi yapan bir senaryo yazalım ve bunu pattern'imizi uygulayarak yapmaya çalışalım.

Öncelikle ürün class'ımızı oluşturalım. Iphone'un farklı modellerini (6, 6S, SE, vs.) ürettiğimizi düşünelim ve constractor seviyesinde hangi modeli ürettiğimiz bilgisini verelim.

    //Product'ımız   
    public class Iphone
    {
        string phoneType;
        public Iphone(string phoneType)
        {
            this.phoneType = phoneType;
        }
    }

Şimdi ise Builder gibi davranan abstract class'ımızı oluşturalım. Bu class üretimde kullanacağımız abstract üyeler içerecektir. Icerisinde BuildOS ve BuildDevice adında iki abstract metot ve hangi iphone modelini üreteceği bilgisinin olacağı Iphone tipinde olan obje olacaktır.

    //abstract Builder nesnemiz   
    public abstract class IphoneBuilder
    {
        public abstract void BuildOS();

        public abstract void BuildDevice();

        Iphone IphoneType { get; }
    }

Sırada Builder class'larını geliştirme var. 6sBuilder ve SeBuilder adında iki tane iphone modeli için geliştirme yapalım.

SeBuilder

    // SE modeli için kullanacağımız Concrete Builder class'ımız
    public class SeBuilder : IphoneBuilder
    {
        Iphone device;

        public SeBuilder()
        {
            device = new Iphone("Se");
        }

        public override void BuildOS()
        {
            //TODO
        }

        public override void BuildDevice()
        {
            //TODO
        }

        public Iphone IphoneType
        {
            get { return device; }
        }
    }

_6sBuilder

   // 6S modeli için kullanacağımız Concrete Builder class'ımız
   public class _6SBuilder : IphoneBuilder
    {
        Iphone device;

        public _6SBuilder()
        {
            device = new Iphone("6S");
        }

        public override void BuildOS()
        {
            //TODO
        }

        public override void BuildDevice()
        {
            //TODO
        }

        public Iphone IphoneType
        {
            get { return device; }
        }
    }

Şimdi sırada üretim var. Manufacturer adında üretici class'ımızı oluşturalım.

   //Director class'ımız
   public class Manufacturer
    {
        public void Build(IphoneBuilder iphoneBuilder)
        {
            iphoneBuilder.BuildDevice();

            iphoneBuilder.BuildOS();
        }
    }

Sıra yaptığımız implementasyonu kullanmaya geldi. 

	public static void Main()
	{
		 Manufacturer manufacturer = new Manufacturer();

         _6SBuilder _6s = new _6SBuilder();

         manufacturer.Build(_6s);
	}

Main fonksiyon içerisinde Manufacturer'ı kullanarak _6s modelini build ettik. Builder pattern'nini elimizden geldiğince projemize entegre ettik ve diğer iphone ürünleri için de aynı akışı kullanarak ürünlerimizi Build edebiliriz.

Builder pattern'i factory ve abstract factory pattern'leri ile birçok yönden benzerlikler göstermektedir. Hangisini kullanmalıyım diye tereddütte kaldığınız durumlarda, complex objeler yaratıyorsanız Builder'ı, tek bir obje yaratmak istiyorsanız da factory veya abstract factory design pattern'i tercih edebilirsiniz.

Quartz .Net Nedir Nasıl Kullanılır ?

Quartz.Net, geliştirdiğiniz web tabanlı projelerde scheduled task yani zamana dayalı planlı görevler çalıştırmak istediğinizde çok kolay bir şekilde implement edip kullanabileceğiniz open source bir kütüphanedir. Aslında bir çok kişi bu tarz işlemleri yapmak için bir console uygulaması geliştirir ve windows server'da task schedular'a bu uygulamayı ne zaman çalıştıracağını söyler ve iş biter. Ancak gerek sunucuya erişimin kısıtlı olduğu durumlarda, gerekse ikinci bir uygulama geliştirmenin yol açtığı efor düşündüğünde quartz.net daha avantajlı gibi görünüyor.

Kullanım olarak iki aşamadan oluşuyor diyebiliriz,

  1. Çalışacak olan Job'ı belirleme,
  2. Hangi zaman aralıklarında çalışacağı bilgisi set edilir.

Quartz 3 ana bileşenden oluşur, job, trigger, schedular. Job yapılacak olan iştir. Trigger job'ın ne zaman, ne şekilde tetikleneceği emrini veren & tetikleyen yapıdır. Job ve Trigger ikisi birlikte schedular'a register olurlar ve kısaca çalışma şekli olarak; job schedular üzerinden trigger tarafından çalıştırılır diyebiliriz.

Örnek bir case üzerinden ilerleyelim; bir banka web service projemiz olsun ve müşterilere her gün saat 13:30 da push notification yollayan bir kampanyalar job'ı geliştirelim.

1. Quartz.Net Kurulumu

Kurulum için projenize sağ tıklayıp Manage Nuget Packages dedikten sonra aşağıdaki gibi nuget'ten projeyi install edebiliriz.

 

2. Job'ı Oluşturma

Bunun için ilk olarak IJob interface'inden implement olan CampaignPushJob adında bir class oluşturalım. IJob ile birlikte class'ımız Execute adında bir metoda sahip olur ve job çalışırken aslında bu metot içerisindeki akış çalışır diyebiliriz.

    public class CampaignPushJob : IJob
    {
        public void Execute(IJobExecutionContext context)
        {
            var userRepository = new UserRepository();
            var userList = userRepository.GetPNUserList();

            var pushNotText = "Size özel konut kredisi teklifleri.";

            var pnService = new PnService();
            pnService.SendPush(pushNotText, userList);
        }
    }

Örnekte PnService adında bir service'imiz olduğunu varsayalım ve SendPush metodu ile kullanıcılara notification gönderiliyor olsun.

3. Schedular Oluşturma

Bu adımda CampaignPushJobScheduler adında içerisinde ne zaman ve neyi çalıştıracağı bilgisini verdiğimiz schedular'ımızı yazıyoruz. JobBuilder.Create<CampaignPushJob>().Build() satırında da görüldüğü gibi bizden bir adet IJob interface'ini implement etmiş class istemekte. Bizde bir üstte yazdığımız CampaignPushJob'ını buraya set ediyoruz. Ne zaman çalıştıracağı bilgisini ise TriggerBuilder'ı initialize ederken set edeceğiz. Örneğimizde her gün saat 13:30 da çalışmasını istediğimiz bir job çalıştırmak istediğimiz için TriggerBuilder içerisinde aşağıdaki gibi bir tanımlama yaptık.

    public class CampaignPushJobScheduler
    {
        public static void Start()
        {
            IScheduler scheduler = StdSchedulerFactory.GetDefaultScheduler();
            scheduler.Start();

            IJobDetail job = JobBuilder.Create<CampaignPushJob>().Build();

            ITrigger trigger = TriggerBuilder.Create()
                .WithDailyTimeIntervalSchedule
                  (s =>
                     s
                    .OnEveryDay() //hergün çalışacağı bilgisi
                    .StartingDailyAt(TimeOfDay.HourAndMinuteOfDay(13, 30)) //hergün hangi saatte çalışacağı bilgisi
                  )
                .Build();

            scheduler.ScheduleJob(job, trigger);
        }
    }

4. Schedular Start Etme

Bu adıma kadar job, schedular ve trigger bilgilerimizi oluşturduk ve artık uygulama ayağa kalktıktan sonra schedular'ı start etmemiz gerekiyor. Geliştirmekte olduğumuz proje bir Asp.Net projesi olduğundan bu işlemi kolayca Global.asax dosyası içerisinde bulunan Application_Start metodunda yapabiliriz. Tek yapmamız gereken CampaignPushJobScheduler'ın içerisinde bulunan Start metodunu çağırmak.

    public class Global : HttpApplication
    {
        void Application_Start(object sender, EventArgs e)
        {
            CampaignPushJobScheduler.Start();
        }
    }

Quartz.Net implement etme işlemimiz bu kadar. Artık web projemiz bir kere ayağa kalktıktan sonra set ettiğimiz değerler doğrultusunda çalışacaktır.

Quartz schedular işlemler için .Net tarafında kullanılabilecek en yaygın kütüphanelerden bir tanesidir. Örnekte her ne kadar az kullanmış olsak da trigger time özelliğiyle ilgili kullanıcılara bir çok seçenek sunmakta. Bunlarla ilgili ayrıntılı bilgiyi bu linkte bulabilirsiniz.

SOLID Yazılım Süreçlerini Yavaşlatıyor Mu ?

Geçtiğimiz haftalarda  SOLID yazı serisini sonlandırdık ve sırasıyla

konularından bahsettik.

İlk defa SOLID'i araştırırken bir çok blog yazlım forum sitesi vs. dolaştım ve yapılan yorumları okuma fırsatım olmuştu. Bu yorumlardan en çok yaygın olanı kabaca şöyle bir şey "ya tamamda ne gerek var bunca şeye ?.. SOLID hem beni hemde yazdığım kodu yavaşlatıyor..." vs. şeklinde yorumlar yapılmıştı. Doğrusu en başlarda bende buna benzer cümleler kurmadım değil :)

SOLID prensiplerinin asıl amacı Separation of Concerns veya türkçe deyimiyle farklı kavramların, işlerin birbirinden ayrılması ilkesini High Cohesion & Low Coupling yoluyla geliştirmek. Prensipleri anlattığımız ilk yazıda şuna benzer bir cümle kullanmıştım "asıl amaç sonradan kolay müdahale edilebilir, yeni modüllerin kolay entegre edilebildiği core code'a çok fazla dokunmadan yazılımlar geliştirmek..." ve aslında bu cümle 5 temel prensiple bizlere anlatılıyor. İlk bakışta bu prensiplerin uygulaması zahmetli gibi geliyor olabilir ancak bunu ileriye dönük iyi bir yatırım olarak görmeliyiz ve yazılım geliştirme sürecini while(true) {develop}şeklinde sonsuz bir döngü olarak bakmalıyız.

SOLID prensiplerine uyalım derken bazı istenmeyen sonuçlarortaya çıkarmamızda mümkün;

  • Çok fazla abstraction ve interface,
  • İstenmeyen Concrete class'lar,
  • Gerektiğidnen fazla layer oluşabilme itimali,
  • Kalıtım ağacıdnaki derinliği gereknden fazla artırma,
  • Yo-yo problemi,
  • vs...

SOLID prensiplerinin gelişi güzel uygulanması bu ve benzeri durumların olmasına neden oalbilir ancak geliştirme yaparken arada bir geriye gelip geniş açıdan bakmakta fayda var çünkü bazen oop nin dibine vurmaya çalışırken farkında olmadan saçmalamışta olabiliriz :) . Tabi burda da devreye iyi deneyime sahip, çeşitli projelerde görev almış vizyon sahibi bir takım liderine gerek var ki code-review sırasında nokta atışı yaparak yanlış veya gereksiz olan yerleri saptayıp düzeltebilme fırsatınız olsun.

SOLID yazılım süreçlerini yavaşlatıyor mu ? sorusuna geri dönecek olursak, yukarıda da bahsettiğimiz üzre çok separation of concern'ü düşünerek yapacağımız işleri çoklu katmanalra ayırarak böyle bir duruma sebebiyet verme ihtimalimiz azda olsa var. Diğer bir deyişle tek bir metot çağrısı yaparak çözebileceğimiz bir sorunu birden fazla instance alarak metot çağrısı yapmaya zorlandığımız durumlar ortaya çıkabilir ve bu da geliştirme süresini uzatabilir. Ancak SOLID prensiplerini hangi projeye uygulanması gerektiğine karar vermekte bir o kadar önemli çünkü small diyebileceğimiz projeler için dibine kadar SOLID kastırmak çokta şart değildir hani. Bunun yerine enterprise veya işlevselliği çok olan büüyk projerlerde SOLID prensiplerini uygulamaya çalışmalıyız ve unutmamalıyız ki SOLID'e uygun geliştirmeler yaparak günü kurtarmıyoruz bunu ileriye dönük bir yatırım olarak görmeliyiz ve asıl faydalarını projenin ilerleyen fazlarında görüyor olacağız.

Ben kendi projelerimde nasıl ilerliyorum diye soracak olursak, mümkün olduğunda separation of concern'e odaklanmaya çalışıyorum ve ona uygun ilerleyecek sade-temiz bir design pattern seçmeye çalışırım. Çok fazla abstraction'dan dolayı performans sorunları olacağını düşündüğüm yerler varsa da yaygın kullanılan bir runtime performans tool'u kullanarak testler yapıp sorunu saptamaya odaklanırım.

 

Sonuç olarak SOLID yazılım süreçlerini yavaşlatıyor mu sorusuna bence en uygun cevap, SOLID'i ne kadar doğru zamanda doğru yerde kullandığınızla ilgili. Eğer prensiplere tamamiyle hakimseniz ve doğru projede uyguladığınızı düşünüyorsanız kesinlikle yazılım süreçlerini yavaşlatmıyor aksine hızlandırıyor. Şuan için farkında olmasanız bile ileriye dönük size birçok katkı sağlayacağı noktalar olacaktır. Eğer çok gereksiz bir projede hiç ihtiyaç yokken SOLID'in dibine vurmakta tavsiye edilen bir durum olmayacaktır, çok büyük bir ihtimalle geliştirme sırasında gereksiz zaman kaybına yol açacaktır.

Dependency Inversion Principle

SOLID prensipleri yazı serisinde son prensip olan SOLID'in "D" si Dependency Inversion prensibine gelmiş bulunuyoruz. Türkçe anlamını her ne kadar beğenmiyor olsam da atalarımız "Bağlılığı Tersine Çevirme Prensibi" olarak çevirmişler.

Bu prensip bizlere OOP yaparken şu 2 kurala uymamız gerektiğini söylüyor;

  • Üst seviye (High-Level) sınıflar alt seviye (Low-Level) sınıflara bağlı olmamalıdır, aralarındaki ilişki abstraction veya interface kullanarak sağlanmalıdır,
  • Abstraction detaylara bağlı olmamalıdır, aksine detaylar abstraction'lara bağlı olmalıdır.

Birçok projede malesef üst seviye sınıflar alt seviye sınıflara bağlıdır ve bu sınıflarda bir değişiklik yapmak istediğimizde başımıza onlarca iş açabilmektedir çünkü alt sınıfta yapılan bu değişiklik üstü sınıfıda etkileyebilir ve üst sınıfların etkilenmesi de projenizdeki bütün yapının etkilenmesi neden olabilir. Bu durum aynı zamanda reusability yani tekrar kullanılabilirlik durumuna engeller. İşte bu karmaşayı ortadan kaldırmak için Dependency Inversion prensibi ortaya çıkmıştır ve üsttede belirttiğim gibi modulleri birbirinden soyutlamamız gerekir. SOLID Nedir makalesinde verdiğim örnekte olduğu gibi gözlüğünüz var ve camlarını değiştirmek istediniz gittiniz gözlükçüye adam dediki "Bu camların değişmesi için gözlüğünde değişmesi gerek..." saçma dimi :) işte bazen bu prensibe uymazsak gözlük örneğinde olduğu gibi enteresan sorunlar başımıza gelebilir. 

Örnek bir case üzerinden ilerleyelim. LogManager adında bir class'ımız olsun ve bu class'ın Log() isminde bir metodu ve bu metod çağrıldığında FileLogger() objesine ait olan Log() metodu çağrılsın ve FileLog işlemini yapsın.

	public class FileLogger  
	{
		public string Message { get; set; }
		public void Log()
		{
			//File Log
		}
	}

	public class DBLogger  
	{
		public string Message { get; set; }		
		public void Log()
		{
			//Database Log
		}
	}

	public class LogManager  
	{
		private FileLogger _file;
		private DBLogger _db;
		
		public LogManager()
		{
			_file = new FileLogger();
			_db = new DBLogger();
		}
	
		public void Log()
		{
			_file.Log();
			_db.Log();
		}
	}

Yukarıda görüldüğü gibi LogManager üst seviye class'ımız ve tam da Dependency Inversion prensibine ters olarak FileLogger ve DBLogger class'larına yani alt seviye class'lara bağlı. DIP bize bu gibi alt-üst seviye sınıf ilişkilerini abstraction veya interface'ler kullanarak kurmamızı söylüyor ancak durum şuan bunun tam tersi ve yarın bir gün yöneticiniz geldi dedi ki "bundan sonra uygulama Log'ları EventLog'a da yazdırılacak". Bunun için gidip aynen File-DB Logger class'larında olduğu gibi EventLogger adında bir class tanımlayıp LogManager() içerisinde aynı işlemleri yapmak heralde istediğimiz bir çözüm değildir ki bu LogManger class'ına extra bir nesneye daha bağlı hale getirir. Hedefimiz LogManager class'ını olabildiğince nesne bağımsız hale getirmek.

 

Bunun için ilk olarak ILogger adında bir interface tanımlayalım ve FileLogger & DBLogger class'larını bu interface'den implement edelim.

	public interface ILogger  
	{
		void Log();
	}

	public class FileLogger : ILogger
	{
		public string Message { get; set; }
		public void Log()
		{
			//File Log
		}
	}

	public class DBLogger : ILogger
	{
		public string Message { get; set; }
		
		public void Log()
		{
			//Database Log
		}
	}

Ve son olarak da LogManager class'ını sadece ILogger interface'ine bağlı hale getirmek kalıyor. Böylelikle ILogger'den implement olmuş bütün class'lar LogManager tarafından kullanılabilecektir.

	public class LogManager 
	{
		private ILogger _logger;
		public LogManager(ILogger logger)
		{
			_logger = logger;
		}
	
		public void Log()
		{
			_logger.Log();
		}
	}

Bu refactoring işlemlerini yaptıktan sonra artık LogManager class'ı Dependency Inversion prensibine uygun hale gelmiştir yani alt seviye sınıflara bağlı değildir aradaki ilişki interface kullanarak sağlanmıştır. 

LogManager class'ının kullanım şekli ise aşağıdaki gibidir.

	public static void Main()
	{
		var dbLogger = new DBLogger();
		dbLogger.Message = "Test 123";
		
		var manager = new LogManager(dbLogger);
		manager.Log();
	}

Özetle; yaptığımız refactoring işlemiyle birlikte DIP'nin söylediği gibi high-level ve low-level sınıfları abstraction'lara bağlı hale getirdik.

Bu yazımızla beraber SOLID prensiplerinin sonuna gelmiş bulunuyoruz. Umarım faydalı bir yazı serisi olmuştur, soru, yorum veya eleştirilere açığımdır :) hope to keep in touch

Interface Segregation Principle

SOLID prensipleri yazı dizisinde sırada SOLID'in "I" olan Interface Segregation (ISP) var. Bu prensip bize kısaca şunu söylüyor; "Nesneler asla ihtiyacı olmayan property/metot vs içeren interface'leri implement etmeye zorlanmamalıdır  !".

Terminolojide bu interface'ler "fat" yada "polluted" interfaces diye adlandırılır.  ISP uygulanmadığında bir den fazla sorumluluğu olan nesneler ortaya çıkar ki bu da aslında SOLID'in "S" si olan "Single Responsibility" prensibine aykırı bir şeydir. Bu sınıflar yüklenen çoklu sorumluluklardan dolayı zaman içerisinde yönetilemez hale gelirler ve projemiz patates olur çıkar. Bu tarz case'ler le karşılaşıldığında interface içerisinde bulunan kullanılmaması gereken özellikleri içeren yeni interface'ler tanımlanır ve ihtiyaca göre nesneler tarafından implement edilir. Eğer projeniz bu prensibi ihlal ediyorsa Adapter Design Pattern avantajlarını kullanarak da ilerleyebilirsiniz. İlgili nesne interface'i implement edip hiç kullanmayacağı metotlara sahip olduğunda class'ınız içerisinde "NotImplementedException" yazan metotlar olacaktır ve buda OOP açısından hiç istenen bir şey değildir.

ISP'yi örnek bir case üzerinde anlatmaya çalışalım. Bir tane FileLog ve DbLog yapan işlemleri yapan bir proje geliştireceğiz ve içerisinde ILog adında bir interface tanımlayalım ve bu interface'inde Log(), OpenConn(), CloseConn() gibi metodları olsun.

public interface ILog
{ 
    void Log(string message);

    void OpenConnection();

    void CloseConnection();
}

 

İlk olarak DBLogger sınıfını yazalım

public class DBLogger : ILog
{		
        public void Log(string message)
        {
            //Code to log data to a database
        }

        public void OpenConnection()
        {
            //Opens database connection
        }

        public void CloseConnection()
        {
           //Closes the database connection
        }
}

 

Şimdi de FileLogger class'ını yazalım. 

public class FileLogger : ILog
    {
        public void Log(string message)
        {
            //Code to log to a file           
        }
    }

FileLog işlemi yaparken Db de olduğu gibi bir Connection açıp kapama işlemi yok ve bu metotları FileLogger'da kullanmak istemiyoruz sadece ILog interface'inde tanımlı olan Log(string message) metodunu kullanmak istiyoruz. Projeyi derlediğinizde şöyle bir hata alırsınız "FileLogger class doesn't implement the interface members ILog.OpenConnection() and ILog.OpenConnection()" .  Peki hatayı görüp eksik interface'in eksik üyelerini implement edelim. Bu sefer class'ımız aşağıdaki gibi olacaktır.

public class FileLogger : ILog
    {
        public void Log(string message)
        {
             //Code to log to a file           
        }

        public void CloseConnection()
        {
            throw new NotImplementedException();
        }

        public void OpenConnection()
        {
            throw new NotImplementedException();
        }
    }

E şimdi ne oldu ? Patates ! Hiç ihtiyacımız olmasa da FileLogger class'ına 2 tane gereksiz metot kazandırdık. İşte ISP burada devreye giriyor. Yapmamız gereken şey yeni interface veya interface'ler tanımlayarak FileLogger ve DbLogger class'larının sadece ihtiyacı olan metotları içeren interface'i implemente etmesini sağlamak.

Yeni interface'ler aşağıdaki gibi olacaktır.

    public interface ILog
    {
        void Log(string message);
    }

    public interface IDBLog: ILog
    {
        void OpenConnection();

        void CloseConnection();
    }

    public interface IFileLog: ILog
    {
        void CheckFileSize();

        void GenerateFileName();
    }

 

Bu interface'leri implement eden DbLogger ve FileLogger class'larımızda aşağıdaki gibidir.

 public class FileLogger : IFileLog
    {
        public void CheckFileSize()
        {
            //Code to check log file size
        }

        public void GenerateFileName()
        {
            //Code to generate a new file name
        }
		
        public void Log(string message)
        {
            //Code to log data to the log file
        }
    }

    public class DBLogger : IDBLog
    {
        public void Log(string message)
        {
            //Code to log data to the database
        }

        public void OpenConnection()
        {
            //Code to open database connection
        }

         public void CloseConnection()
         {
            //Code to close database connection
         }
    }

Interface Segregation çok önemli bir prensiptir. Özellikle Adapter Design Pattern ile haşır neşir olan arkadaşlar için dahada fazla öneme sahiptir. İçerisinde 60-70 tane üyesi bulunan interface'ler yazmaktan da çekinin bu gibi durumlarda bir yolunu bulup interface üyelerini ayırıp gruplayıp yeni interface'ler türetmeye çalışmamız daa doğru olacaktır.