ElasticSearch Nedir ? Windows Üzerinde Kurulumu

Elasticsearch, java dilinde open-source olarak geliştirilen, dağıtık mimariye uygun, kolay ölçeklenebilir, enterprise düzeyde bir big-data arama motorudur. Sahip olduğu Http protokolü üzerinde çalışan Restful Api ile CRUD işlemlerini oldukça hızlı bir şekilde yapabilmemize olanak sağlar. 

ElasticSearch veya diğer search engine'lerin geliştirilmesine asıl sebep olan şey big-data dır. Her an her saniye milyonlarca satır veri üretiminden bahsediyoruz ve toplanmış olan bu verileri analiz etmek istediğimizde bu işlemi database seviyesinde yapıyorsak yani SQL'e bağımlıysak hız konusunda geride kalıyoruz. ElasticSearch core kısmında yer alan çeşitli algoritmalarıyla text-search işlemini oldukça kısa sürede hızlı bir şekilde yapabilmektedir.

Kurulum

Yukarıda bahsettiğimiz üzre elasticsearch Java tabanlı bir kütüphane olduğundan windows üzerinde kurulum yapmadan önce pc'nizde en az Java 8 versiyonu kurulu olmak şartıyla JRE ve JDK yüklü olmak zorundadır.

Elasticsearch elasticsearch.org adresinde ZIP and TAR.GZ gibi değişik formatlarda kurulum paketi sunmaktadır. Ancak ben temiz bir kurulum yapmanız adına MSI formatında olan paketi indirmeyi tercih edicem. İndirme işlemi bittiğinde exe'yi çalıştıralım ve bu adreste belirtildiği şekilde veya aşağıdaki görselde de olduğu gibi gerekli konfigurasyonları yaparak kurulumu tamamlayalım.

Kurulum sırasında path, memory-size gibi çeşitli konfigurasyonlar yapabilirsiniz. Eğer kurulumu Install as a service seçeneği ile yaptıysanız elasticsearch service olarak arka planda pc niz açık olduğu sürece çalışacaktır. Service'i görüntülemek için Windows Search kısmına "Services" yazdığınızda çıkan icon'a tıklayalım ve aşağıdaki gibi service listesinde elasticsearch'ü görelim.

Son olarak ES'ün çalışıp çalışmadığını browser üzerinden de kontrol edebiliriz. Browser'ın adres kısmına http://localhost:9200/ yazarak aşağıdaki gibi mevcut pc'niz de kurulu olan ES ile ilgili bilgilere ulaşabilirsiniz.

{
  "name" : "DESKTOP-GRKHT7E",
  "cluster_name" : "elasticsearch",
  "cluster_uuid" : "saiTqKiQRr6m_GQ03BCH0Q",
  "version" : {
    "number" : "5.5.0",
    "build_hash" : "260387d",
    "build_date" : "2017-06-30T23:16:05.735Z",
    "build_snapshot" : false,
    "lucene_version" : "6.6.0"
  },
  "tagline" : "You Know, for Search"
}

Mimarisi

Elasticsearch'ü database üzerinden anlamaya çalışacak olursak;

SQL de bulunan Database ES'de Index'e denk gelmektedir. Tablo ise Tip yani ES'e Index yaparken vereceğimiz modellerimize denk gelmektedir. Tabloya kaydettiğimiz her bir row ise ES de Document olarak adlandırılır. Tabloda bulunan Column'lar Field yani Tip olarak verdiğimiz model de bulunan property veya field'lar dır. Schema ise Mapping olarak adlandırılır.

Aslında Code-First yaklaşımına aşina olan arkadaşlar yukarıdaki görsele baktıklarında ES'ünde bir nevi code-first mantığıyla çalıştığını görebilirler.

ES ile ilgili bu yazımızda sona geldik ancak bir sonraki yazımızda yapmış olduğumuz bu kurulum üzerinden bir .Net projesi geliştirerek ES Client'ların dan biri olan NEST'i kullanarak örnekler vermeye devam edeceğiz.

Nhibernate IPreInsertEventListener ve IPreUpdateEventListener Kullanımı

Server-side bir projede geliştirme yapıyorsanız ve db de bolca CRUD işlemleri için query'ler çalıştırmanız gerekiyorsa sizden db de kaydedilen o row için sizden insert veya update anında bazı bilgileri otomatik bir şekilde o row için kaydetmeniz istenebilir. Örnek olarak; CreatedDate veya update edilen değer için ModifiedDate gibi alanlar tutmanız muhakkak istenir istenmese dahi bu bilgileri ilgili colum'lar da tutmak muhakkak bir gün işinize yarayacaktır.

Eğer CRUD işlemlerini Ado.Net kullanarak yapıyorsanız query'nin sonuna bu değerleri ekleyebilir yada stored-procedure kullanıyorsanız da bu işlemleri sp içerisinde de yapabiliriz.

Bu yazımızda bu ve benzeri işlemleri Fluent-Nhibernate kullanarak nasıl yapabiliriz konusuna değineceğiz. 

Her defasında yeni kayıt geldi modeli initialize ederken CreatedDate alanına DateTime.Now set et, yada her update işlemi geldiğinde ModifiedDate alanına DateTime.Now alanını set et. Pek de hoş durmuyor sanki. 50'ye yakın db de tablonuz olduğunu düşünün her bir entity için gidip bu işlemleri heralde yapmak istemeyiz .

Eğer proejenizde NHibernate'i kullanıyorsanız Nhibernate bu işlemler için bizlere aşağıdaki interface'leri sunmakta.

  • IPreInsertEventListener
  • IPreUpdateEventListener

IPreInsertEventListener; adında da anlaşılacağı üzre entity'niz insert edilirken bir interceptor gibi araya girmemizi sağlayan ve insert query execution'dan OnPreInsert adındaki metoduna invoke edilerek entity'niz üzerinde işlemler yapmanızı sağlar.

IPreUpdateEventListener; ise bir update listener'ı dır ve içerisinde implement edebildiğimiz OnPreUpdate  metodu çağrılır update query'sinin execution'dan önce call edilerek yine entity üzerinde değişiklikler yapabilmemizi sağlar.

Örnek olarak bir BaseModel'miz olsun ve projemizde bulunan her bir entity için tablolarda ortak bulunan alanları bu class içerisinde tanımlayabiliriz.

    public abstract class BaseModel
    {
        public virtual Guid Id { get; set; }
        public virtual DateTime? CreatedDate { get; set; }
        public virtual DateTime? ModifiedDate { get; set; }
    }

Db de bulunan tablolarımıza karşılık gelen entity'lerimiz ise yukarıda tanımladığımız BaseModel class'ından inherit olacaklar. Örnek olarak Customer adında aşağıdaki gibi bir entity tanımlayalım.

    public class Customer : BaseModel
    {
        public virtual string FirstName { get; set; }
        public virtual string LastName { get; set; }
        public virtual string Email { get; set; }
    }

Şimdi ise CustomerMap class'ını oluşturacaz ancak öncesinde BaseModel içerisinde bulunan proeprty'ler için BaseMapping adında bir class tanımlayalım. Customer ve diğer db modellerimizde bu BaseMapping'i kullanarak map işlemlerini yapacağız. Bunu yapmamızdaki amaç her bir entity için ayrı ayrı gidip BaseModel içerisinde bulunan alanların map'ini yapmamak. 

    public class BaseMapping<T> : ClassMap<T> where T : BaseModel
    {
        public BaseMapping()
        {
            Id(x => x.Id);
            Map(x => x.CreatedDate);
            Map(x => x.ModifiedDate);
        }
    }

 

Artık BaseMapping 'i kullanarak CustomerMap class'ını oluşturabiliriz. 

    public class CustomerMap : BaseMapping<Customer>
    {
        public CustomerMap()
        {
            Map(x => x.FirstName);
            Map(x => x.LastName);
            Map(x => x.Email);
        }
    }

Sırada Listener class'ını oluşturmak var. Aşağıda NHInsertUpdateListener adında bir class tanımlayalım. Yazının başında bahsettiğimiz her tablomuzda bulunan CreatedDate ve ModifiedDate tarih alanlarını NHInsertUpdateListener içerisinde set edeceğiz.

    public class NHInsertUpdateListener : IPreInsertEventListener, IPreUpdateEventListener
    {
        public bool OnPreUpdate(PreUpdateEvent @event)
        {
            var audit = @event.Entity as BaseModel;
            if (audit == null)
                return false;

            var time = DateTime.Now;

            Set(@event.Persister, @event.State, "CreatedDate", time);

            audit.CreatedDate = time;

            return false;
        }

        public bool OnPreInsert(PreInsertEvent @event)
        {
            var audit = @event.Entity as BaseModel;
            if (audit == null)
                return false;


            var time = DateTime.Now;

            Set(@event.Persister, @event.State, "ModifiedDate", time);

            audit.ModifiedDate = time;

            return false;
        }

        private void Set(IEntityPersister persister, object[] state, string propertyName, object value)
        {
            var index = Array.IndexOf(persister.PropertyNames, propertyName);
            if (index == -1)
                return;
            state[index] = value;
        }
    }

Artık son adım olarak FluentNHibernate'i ayağa kaldırmak var. Nh configuration'ı aşağıdaki gibi tanımlayabiliriz.

Fluently.Configure()
               .Database(MsSqlConfiguration.MsSql2012.ConnectionString(c => c.FromAppSetting("dbConnectionString")).ShowSql())
               .Mappings(m => m.FluentMappings.AddFromAssemblyOf<CustomerMap>())
               .ExposeConfiguration(cfg => new SchemaUpdate(cfg).Execute(false, true))
               .ExposeConfiguration(cfg =>
               {
                   cfg.SetProperty(
                      NHibernate.Cfg.Environment.CurrentSessionContextClass,
                       "web");
                   cfg.AppendListeners(ListenerType.PreUpdate, new IPreUpdateEventListener[] { new NHInsertUpdateListener() });
               })
               .BuildSessionFactory();

 

Sırasıyla yazmak gerekirse neler yaptık;

  1. Vs da bir tane proje oluşturduk. (Console veya Api),
  2. FluentNHibernate paketini nuget'ten indirip kurduk,
  3. Bir db miz olduğunu ve connection string bilgisinin web config'de tanımlı olduğunu varsaydık,
  4. Tablolarda ortak kullanılan propert'leri BaseModel adında ki class da topladık,
  5. Daha sonra BaseMapping adında bir mapping tanımlaması yaparak entity içerisindeki property'leri map ettik,
  6. CustomerMap class'ını oluşturarak mapping işlemini tanımladık,
  7. NHInsertUpdateListener'ı oluşturduk ve CreatedDate - ModifiedDate alanları için değerleri set ettik.
  8. Fluent Nhibernate konfigurasyonunu oluşturduk.

 Listener'lar biraz az bilinen bir özellik gibi görünse de oldukça faydalıdırlar. Örnekte olduğu gibi benzer case'lerde kullanarak bizleri satırlarca tekrar eden kodlardan uzaklaştırır.

StructureMap Nedir ? WebApi ile Kullanımı

Daha önceki IoC container yazılarında Ninject ve Windsor 'dan bahsetmiştik. Bu yazımızda ise 2016 benchmark'larına göre en hızlı IoC container olduğu söylenen StructureMap'i WebApi üzerinde örnek proje ile inceleyeceğiz. 

StructureMap ilk release'i .Net framework 1.1 için 2004 yılında çıkmış ve 12 yıldır hayatımızda olan en eski IoC/DI tool'u dur. Uygulama genelindeki instance yönetiminden sorumlu olup bağımlılıkları enjecte edebilmemizi sağlar.

Yazımızda StructureMap kullanarak basit bir infrastructure tasarlamaya çalışacağız. 

İlk olarak Vs'da bir Web Api projesi oluşturalım.

Sonrasında projemize nuget üzerinden StructureMap.WebApi2 paketini install edelim.

Kurulum işlemi tamamlandıktan sonra solution'da DependencyResolution adında auto generate olan bir klasör ve hem bu klasör içerisinde hemde App_Start klasörü içerisinde StructureMap konfigurasyonlarını yapabilmemizi sağlayacak olan class'ları göreceğiz. Şimdilik bu klasörü es geçelim projemizi hazır hale getirdikten sonra gerekli register işlemlerini yaparız. 

Örnek case'imiz şu şekilde olsun; UserController adında bir controller oluşturalım ve bu controller içerisinde tanımlı IUserService intercase'ini contructor injection yöntemi ile inject edelim. GetUserFullNames adında end-point ile geriye List of string dönelim.

    public class UserController : ApiController
    {
        private readonly IUserService _userService;
        public UserController(IUserService userService)
        {
            _userService = userService;
        }

        [HttpGet]
        public HttpResponseMessage GetUserFullNames()
        {
            var response = _userService.GetUserFullNames();

            return Request.CreateResponse(response);
        }
    }

Controller içerisinde kullandığımız IUserService interface'i ve onun implemantasyonunu aşağıdaki gibi oluşturalım.

public interface IUserService
{
    List<string> GetUserFullNames();
}
 
public class UserService : IUserService
{
    public List<string> GetUserFullNames()
    {
        return new List<string>
                              { "Olcay Şahan",
                                "Anderson Talisca",
                                "Oğuzhan Özyakup",
                                "Ricardo Quaresma",
                                "Cenk Tosun" };
    }
}

Yukarıda da söylediğimiz gibi UserController içerisindeki GetUserFullNames metodu HttpGet isteği alarak geriye UserService içerisinde bulunan GetUserFullNames metodunun return ettiği List of string'i dönecektir.

Örneğimiz hazır. Şimdi ise geriye son 2 adım kaldı.

1-) StructureMap ile ilgili container konfigurasyonlarını yapmak. DependencyResolution klasörü içerisinde bulunan DefaultRegistry adlı class'a gidip aşağıdaki gibi UserService'i container'a register edeceğiz.

    public class DefaultRegistry : Registry {
        #region Constructors and Destructors

        public DefaultRegistry() {
            Scan(
                scan => {
                    scan.TheCallingAssembly();
                    scan.WithDefaultConventions();
                });
            For<IUserService>().Use<UserService>();
        }

        #endregion
    }

2-) Son olarak App_Start/WebApiConfig.cs class'ına gidip DI container'ı start etmemiz gerekiyor. 

    public static class WebApiConfig
    {
        public static void Register(HttpConfiguration config)
        {
            // Web API configuration and services

            // Web API routes
            config.MapHttpAttributeRoutes();

            config.Routes.MapHttpRoute(
                name: "DefaultApi",
                routeTemplate: "api/{controller}/{id}",
                defaults: new { id = RouteParameter.Optional }
            );
            
            //structureMap start
            StructuremapWebApi.Start();
        }
    }

Hem örneğimiz hemde container ile ilgili konfigurasyonlarımız hazır. Artık projemizi run edip yazdığımız kodları test edebiliriz. Bunun için projemizi run edelim ve yazmış olduğumuz http-Get end-point'ini browser da çağıralım.

Yukarıda görüldüğü gibi UserController da bulunan end-point'e call yaptık ve bize container'da bulunan IUserService'ine ait olan implementasyonu resolve edip UserService içerisinde bulunan GetUserFullNames metodunu execute edip geriye user listesini return etti

 StructureMap yazımız şimdilik bu kadar. İlerleyen günlerde daha farklı DI Container yazılarına devam edeceğiz.

Optimistic Lock Nedir ? Pessimistic Lock Nedir ? Data concurrency

Db'si olan ve son kullanıcı tarafından CRUD işlemlerinin bolca yapılabildiği bir proje geliştiriyorsanız veri tutarlılığı sizin için oldukça önemli bir hal almak durumundadır. Kayıtlı olan veriyi son kullanıcıya ulaştırabilip en güncel veri üzerinden transaction'ları işleyebilmek ve stale yada dirty data olarak da adlandırılan bayat veriyi handle edip kullanıcının erişmesini engellemek oldukça önemlidir. 

Transactional operasyonlar Concurrency'yi sağlayabilmek adına genelde üzerinde işlem yapılan veriye lock işlemi uygulanarak gerçekleştirilirler. Bu lock işlemi için 2 farklı yaklaşım vardır. Pessimistik Lock ve Optimistic Lock.

Pessimistic Lock

O an işlem gerçekleşirken üzerinde çalışılan kayıt lock'lanır ki o anda başka birisi o kayıt üzerinde değişiklik yapmasın. Bu işlem session bazlı olur ve transaction başlarken açılan session sonlandırılıncaya veya rollback yapılıncaya kadar işlem yapılan row db de lock'lanır. Örnek olarak bir bankacılık uygulaması düşünün ve bir hesaba aynı anda hem para çekme hemde para yatırma işlemi geldi. İlk para yatırma işlemini yapan thread öncelikli düşündüğümüzde bu transaction'ı gerçekleştiren session o account'u işlem sonlanıncaya kadar lock'lar ve para çekme işlemini bekletir. Transaction sonlandıktan sonra diğer thread'in gerçekleştireceği para çekme işleminin session'nını açarak güncel veri üzerinden işlemlerin gerçekleşmesine olanak sağlar. Bunu yapmasındaki amaç güncel veri üzerinden transaction'ı geçirip oluşabilecek kayıpları engellemektir. Ancak pessimistic lock'ın deadlock'lara sebep olabileceğini de unutmayalım.

 

Optimistic Lock

Optimistic Lock ise, adından da anlaşılacağı üzre "iyimser" birden fazla işlemin birbirini etkilemeden gerçekleşeceğini ve kimsenin kimse üzerinde bir lock koymayacağını söyler. Diğer bir deyişle farklı thread'ler de aynı row üzerinde işlem yapılırken herhangi bir lock işlemi olmadan update edilmek istenen verinin bayat olup olmadığını o verinin kayıtlı olduğu tabloda yer alan versiyon numarası olarak da adlandırılan bir column'da bulunan değeri kontrol eder ve eğer versiyon eşleşmiyorsa yani veri bayat ise işlem geri çekilir.

Peki bayat(stale) data ne demek ?

Örnek üzerinden anlatacak olursak; bir internet sitesinde kayıtlı bulunan adres bilginizi güncellemek istiyorsunuz. Aynı anda 2 farklı bilgisayardan bilgileri güncelle sayfasını açtınız ve adresiniz o an "Samsun" olarak kayıtlı yani 2 ekranda da "Samsun" yazıyor. İlk bilgisayarda bulunan kişi adres bilgisini "Ankara" olarak değiştirdi ve güncelle butonuna basıp bilgiyi güncelledi.

İkinci ekranda bulunan kişi ise ekranda halen "Samsun" yazılı iken adres bilgisini "İstanbul" olarak değiştirdi ve güncelle butonuna basıp bilgiyi güncelledi. Ekranda yazan "Samsun" kaydı artık bizim için bayat bir kayıttır ve birinci kullanıcı değişikliği "Samsun" => "Ankara" yaptığını düşünürken ikinci kişi bu değişikliği "Samsun" => "İstanbul" yaptığını düşünüyor. Halbuki gerçekte olan ikinci kişi adres bilgisi ekranda "Ankara" iken => "İstanbul" olarak değiştirmiş oldu.

Ne oldu ? Pek de istemediğimiz bir case oluştu. İkinci kullanıcı Samsun olan kaydı İstanbul yaptığını düşünürken aslında Ankara olan kaydı İstanbul yaptı. Yani stale olan kaydı güncellemiş oldu.

Optimistic Lock ile ikinci kullanıcının stale olan veriyi update etmesine şu şekilde engel olabiliriz; Eğer Ado.Net kullanıyorsanız ve db de bulunan her bir row icin birer versiyon

numarasi vb gibi kaydinizvar ise query nizde where koşuluna o row için güncel olarak bulunan version bilgisini ekleyerek kontrol sağlayabiliriz veya Entity Framework yada NHibernate gibi ORM tool'larından birini kullanıyorsanız bu işlemi size bırakmadan güncellenmek istenen row'a ait versiyon numarasını select işleminde memory de tutuyor ve o veri için update transaction'ı execute edilirken bu versiyon numarası db de karşılaştırıyor. Eğer o versiyon numarası db de bulunan ile aynı ise versiyon numarasını 1 artırıp execution'a izin veriyor değilse hata fırlatıyor. Hata mesajı olarak kullanılan ORM türüne göre "The record you attempted to edit was modified by another user after you got the original value" gibi bir message return ediyor.

 

Hem optimistic hemde pessimistic lock konuları çok fazla önemsemediğimiz anlar olsa da oldukça önemli konulardır. Sonraki yazılarımızda Nhibernate veya Entity Framework kullanarak nasıl bir Optimistic Lock yapısı implement edebiliriz inceleyeceğiz.

Factory Method Pattern Nedir Nasıl Kullanılır

Design pattern'lar geliştirme yaparken tekrar eden problemlere denenip onaylanıp çözüm olarak sunulan kalıplardır. İyi bir tasarım deseni; yazmış olduğumuz kodları temiz, okunabilir kılıp sizden sonra gelecek olan kişilere daha kolay adapte olmayı sağlamalıdır.

Bu yazıda object oriented programming'in en çok tercih edilen design pattern'lerin den biri olan Creational pattern'ler grubundan Factory Pattern'i nedir ne değildir nasıl implemente edilir örnek proje ile inceleyeceğiz.

Factory Pattern

Gang of Four patternleri günümüz dünyasında en sıkı şekilde takip edilip en çok kullanılan ünlü tasarım desenleridir. Factory pattern'de bu 4 lü den biridir. Kısaca tanımı ; aynı abstract sınıf veya interface'den türeyen nesnelerin üretiminden sorumlu yapıdır. Bu pattern ile nesne yaratılma işini inheritance yoluyla client-side'dan ayırıp sub-classes'lara vermek amaçlanır.

Geliştirmekte olduğunuz uygulamaya yeni bir feature eklerken en az dokunuş ile client'ı bu duruma hiç sokmadan yapabilmek amaçlanır ve factory pattern de bu amaca yönelik olarak önerilen en önemli pattern'lerden birisidir.

Factory pattern 2 alt kategoriye ayrılabilir.

  1. Factory Method 
  2. Abstract Factory

Factory Method

Aynı interface'i veya abstract sınıfı implement etmiş etmiş factory nesnelerinin üretiminden sorumlu pattern dir.

Örnek bir case üzerinden ilerleyelim. Araç üretimi yapan bir fabrikamız olsun. Bu fabrika car, truck ve motorcycle üretebiliyor olsun. İlk olarak factory nesnelerimizin kullanacağı IVehicleFactory interface'ini ve car, truck, motorcycle nesnelerini oluşturalım.

    public interface IVehicle
    {
        void DisplayInfo();
    }

    public class Car : IVehicle
    {
        public void DisplayInfo()
        {
            Console.WriteLine("Car produced");
        }
    }

    public class Truck : IVehicle
    {
        public void DisplayInfo()
        {
            Console.WriteLine("Truck produced");
        }
    }

    public class Motorcycle : IVehicle
    {
        public void DisplayInfo()
        {
            Console.WriteLine("Motorcycle produced");
        }
    }

Yukarıdaki gibi nesneleri ve arayüzleri oluşturduktan sonra ismi VehicleFactory olan ve içerisinde geriye IVehicle döndüren ProduceVehicle adında bir sınıf tanımlayacağız. ProduceVehicle metodu VehicleType adında bir bir enum parametre olarak alacak. Bu enum'ı kullanarak factory metoduna üretmesini istediğimiz tip bilgisini geçeceğiz.

    public enum VehicleType
    {
        Car = 1,
        Truck = 2,
        Motorcycle = 3
    }

    public interface IVehicleFactory
    {
        IVehicle ProduceVehicle(VehicleType type);
    }

    public class VehicleFactory : IVehicleFactory
    {
        public IVehicle ProduceVehicle(VehicleType type)
        {
            IVehicle vehicle = null;
            switch (type)
            {
                case VehicleType.Car:
                    vehicle = new Car();
                    break;
                case VehicleType.Truck:
                    vehicle = new Truck();
                    break;
                case VehicleType.Motorcycle:
                    vehicle = new Motorcycle();
                    break;
            }
            return vehicle;
        }
    }


Factory metotlarımız da hazır artık üretime başlayabiliriz. Client dediğimiz kısım aslında tam da aşağıdaki kod parçaları oluyor Program.cs içerisinde aşağıdaki gibi üretmek istediğimiz türdeki aracı factory'e söyleyip üretebiliriz.

    class Program
    {
        static void Main(string[] args)
        {
            var vehicleFactory = new VehicleFactory();

            IVehicle vehicleCar = vehicleFactory.ProduceVehicle(VehicleType.Car);
            vehicleCar.DisplayInfo();

            IVehicle vehicleMotorcycle= vehicleFactory.ProduceVehicle(VehicleType.Motorcycle);
            vehicleMotorcycle.DisplayInfo();
        }
    }

Yazımızın başında da bahsettiğimiz gibi yapılabilecek değişikliklerden client'ı etkilemeden yapabilmek birinci önceliğimizdir. Bu örneğimizde araba üretimi yapmak için IVehicle interface'ini implement eden Car nesnesini kullandık ancak ilerde bir gün yine IVehicle interface'ini implement eden XCar adında bir nesne oluşturup üretim yaparken bu nesnyi kullanabiliriz ve bu durum client açısından hiç bir değişikliğe gidilmeden yapılabilmektedir.

Castle Windsor ile Exception Handling Interceptor (Dynamic Proxy)

Daha önceki IoC ve AOP yazılarında  çeşitli konulara değinerek örnek projeler üzerinde anlatımlarda bulunduk. Bu yazımızda da Castle Windsor'dan yararlanarak projelerimizde sıklıkla kullanacağımız bir ExceptionAspect veya Interceptor oluşturacağız. 

Interceptor'lar veya Dynamic Proxies Aspect Oriented Programming'in bir implementasyonudur. Bu bize oluşturduğu proxy ile metodu intercept ederek kendi kodlarımızın arasına inject etmemizi sağlar.

IL kodlarına baktığımızda aşağıdaki resimde olduğu gibi geliştirmiş olduğumuz kodları try catch finally blokları arasına alınır.

 

 

 

 

 

 

 

Daha önceki Castle Windsor Kullanarak Cache Interceptor Oluşturma yazısındaki örneğimiz üzerinden ilerleyelim. O yazıda il kodu alarak geriye ilçeleri dönen bir case üzerinden caching işlemi yapan aspect geliştirmiştik. Bu yazımızda da aynı örnek üzerinden ilerleyerek projemiz için bir ExceptionHandling aspect oluşturalım. 

İlk olarak ExceptionHandlingInterceptor adında aspect'imizi oluşturalım.

public class ExceptionHandlingInterceptor : IInterceptor
{
    public void Intercept(IInvocation invocation)
    {
        try
        {
            invocation.Proceed();      
        }
        catch (Exception ex)
        {
            LogManager.Log("Exception in : " + invocation.Method.Name + " method." + ex);

            invocation.ReturnValue = new BaseResponse { IsSuccess = false };
        }
    }
}

Yukarıdaki kodu incelediğimizde özetle şunu söylüyor ;

  • Invoke edilecek metodu try-catch-finally bloğu arasına al
  • Invoke edilen metot içerisinde hata alırsan catch bloğuna gir ve önce alınan hatayı log'a yaz
  • Sonrasında BaseResponse modelini initialize ederek client'a clear bir response model dön.

Tabi ki de çok daha farklı ayrıntıları log'a yazmanız gerekir ancak sample olduğundan şimdilik bunları yazalım.

İkinci adım olarak yazmış olduğumuz Exception aspect'ini container'a register etmek var.

Castle Windsor Kullanarak Cache Interceptor Oluşturma örneğinde ServiceInstaller class'ını olduğu gibi alıp ExceptionHandlingInterceptor'ını register işlemimizi yapalım.

public class ServiceInstaller : IWindsorInstaller
{
    public void Install(IWindsorContainer container, IConfigurationStore store)
    {
        container.Register(Component.For<CacheInterceptor>().LifestyleSingleton());
        container.Register(Component.For<ExceptionHandlingInterceptor>().LifestyleSingleton());

        container.Register(Component.For(typeof(ILocationService))
                 .ImplementedBy(typeof(LocationService))
                 .Interceptors(typeof(CacheInterceptor)
                 .Interceptors(typeof(ExceptionHandlingInterceptor))));
    }
}

Yukarıda ki register işlemi şunu söylüyor; "ILocationService interface'inin implementasyonu LocationService class'ı dır. Bu implementasyona ait 2 adet Interceptor var "CacheInterceptor" ve "ExceptionHandlingInterceptor" adında ve bu interceptor'lar LifeStyleSingleton olarak container'a register edilmişlerdir."

Uygulamamız hazır diyebiliriz. LocationService içerisinde bulunan GetDistrictsByCityCode adlı metodumuzda test etmek için aşağıdaki gibi kendimiz bir exception throw edelim.

public class LocationService : ILocationService
{
    public GetDistrictsByCityCodeResponse GetDistrictsByCityCode(int cityCode)
    {
        throw new NullReferenceException();
    }
}

Projemizi run edip ilgili endpoint'e istekte bulunduğumuzda exception fırlatılıp Interceptor içerisinde bulunan catch bloğuna düşecektir ve loglama işlemini yaptıktan sonra client'a BaseResponse modelini dönecektir.

 

Peki bunları yaparak ne sağladık ? 

  1. Exception Handling işini eski usül projede yüzlerce try-catch bloğu oluşturmak yerine AOP'in faydalarından yararlanarak tek bir yerden yönetebilir duruma getirdik.
  2. Çok daha reusable olan ve başka yerlerde de kullanabileceğimiz bir yapı tasarladık.
  3. Uygulama exception fırlattığında client'a saçma sapan StackTrace mesajı yerine her response da aldığı BaseResponse modelini dönerek response'larımızı daha tutarlı bir hale getirdik.

Postsharp Kullanarak Metot Execution Sürelerini Hesaplama

Daha önceki aspect oriented yazılarımızda ne aspect oriented'ın ne olduğunu ve nasıl kullanıldığını çeşitli örneklerle anlatmıştık ve AOP yaklaşımı ile yapılabileceklerin sınırının olmadığından bahsetmiştik. Bu yazımızda da özellikle server-side geliştirme yapanların sıkça ihtiyaç duyduğu metot execution süresinin AOP yaklaşımı ile nasıl yapıldığından bahsedeceğiz. 

Yazılımda performans bildiğiniz üzre en önemli konuların başında gelir ve büyük çapta 1 den fazla client'ın consume ettiği bir projeniz var ise metot execution sürelerini bir yerlerde logluyor olmak bizler için şu gibi durumlarda hayat kurtarıcı bir rol üstlenir; client der ki "Abi sayfa 8 saniyede açılıyor bizde bir sıkıntı yok service'den geç geliyor....vs.", server-side'da geliştirme yapan arkadaşta der ki "Yok abi ben 0.2 saniyede response dönmüşüm bu bizden değil...vs.". Bu gibi durumlarda eğer siz service olarak projenizden eminseniz bu durumu kanıtlamak için en güzel çözüm metot execution sürelerini hesaplayıp delil olarak ilgili mercilere sunmak. Ya da bu case'i de geçelim bizler developerlar olarak kendimize "arkadaş bi proje yaptık ama işler nasıl gidiyor, yazılımsal olarak performansı yerinde mi.." vs. gibi soruları sorup cevaplarını da biliyor olmamız gerekir.

Bunun için AOP çözümlerinden biri olan Postsharp'dan yararlanacağız. Case şu şekilde olacak; Bir Api projemiz olsun ve MoneyTransfer adında eft-havale-virman işlemlerini yapan end-point geliştirelim ve bu end-point'e gelen request-response'ların execution sürelerini LoggingAspect kullanarak loglayalım.

*PostSharp kurulumu vs bilgileri için şu yazıyı inceleyebilirsiniz.

    [Serializable]
    public class LoggingAspect : OnMethodBoundaryAspect
    {
        private readonly Stopwatch timer = new Stopwatch();  

        public override void OnEntry(MethodExecutionArgs args)
        {
            timer.Start();
        }

        public override void OnExit(MethodExecutionArgs args)
        {
           var executionTime = timer.ElapsedMilliseconds;
            timer.Stop();
            var logMessage = "Method : " args.Method.Name +  " Execution Time : " + executionTime + " Millisecond";
            Console.Write(logMessage );
        }
    }

Sırada MoneyTransfer işlemi yapan metodu yazalım ve bu metot parametre olarak TransferRequest objesi alsın ve response olarak TransferResponse adında bir model dönsün.

        [LoggingAspect]
        public TransferResponse MoneyTransfer(TransferRequest request)
        {
            // back-end kodlarınız

            var resp = new TransferResponse
            {
                IsSuccess = true,
                Message = "Transfer İşleminiz Gerçekleşti."
            };
            return resp;
        }

        public class TransferRequest
        {
            public decimal Amount { get; set; }
            public string SenderIBAN { get; set; }
            public string ReceiverIBAN { get; set; }
        }

        public class TransferResponse
        {
            public bool IsSuccess { get; set; }
            public string Message { get; set; }
        }
    }

Hepsi bu kadar artık yazdığımız kodları test edebiliriz. TransferMoney metoduna request yolladığımızda metot scope'ları içerisindeki kodları run etmeden aspect'imiz içerisinde bulunan OnEntry metoduna girecektir ve burada timer'ı start ediyoruz. Sonrasında metot scope'ları içerisinde bulunan kodları çalıştırıp metottan çıktıktan sonra OnExit metoduna girip timer'ı stop edip devamında execution süresini millisecond cinsinden Console'a yazacaktır.

Metot execution süresi yukarıda da belirttiğim gibi önemli bir konudur ve ne nerde ne kadar süre harcayıp client'a gidiyor bunu ölçeklendirebiliyor olmak gerekir.

Not: Çok uzun süren execution sürelerinin çok büyük bir ihtimalle networksel sorunlardan dolayı kaynaklanabileceği ihtimalini de unutmamak gerekir.

Castle Windsor Kullanarak Cache Interceptor Oluşturma

Daha önceki yazılarımızda IoC nedir ve Castle Windsor Kullanarak Cache Interceptor Oluşturma konularına değinmiştik. Bu yazımızda ise yine Windsor kullanarak projelerimizde sıkça kullandığımız bir özellik olan Caching işlemini basitçe yapabilen bir interceptor oluşturacağız.

Bir Api projesi oluşturalım ve projemizde cache ile ilgili işlemlerin yönetildiği ICache isminde aşağıdaki gibi bir interface'imiz olsun ve arka planda kullandığınız herhangi bir cache yapısı olabilir (Redis, memory cache, vs.) bu interface'in implementasyonunu kullandığınız cache yapısına göre yapmış varsayalım.

public interface ICache : IDisposable
{
    object Get(string key);
 
    void Set(string key, object obj, DateTime expireDate);
 
    void Delete(string key);
 
    bool Exists(string key);
}

Şimdi ise projemize Castle'ı nuget'ten indirip kuralım ve sonrasında aşağıdaki gibi CacAttribute adında bir attribute tanımlayalım. Bu attribute'ü kullanarak cache'e atmak istediğimiz metotları bir nevi flag'lemiş olacağız.

    [AttributeUsage(AttributeTargets.Method)]
    public class CacheAttribute : Attribute
    {
        public int CacheDurationInSecond { get; private set; }

        public CacheAttribute(int cacheDurationInSecond = 300)
        {
            CacheDurationInSecond = cacheDurationInSecond;
        }
    }

Örnek olarak projemizde LocationController adında bir controller ve içerisinde GetDistrictsByCityCode adında int cityCode parametresine göre geriye o şehirde bulunan ilçelerin listesini dönen bir end-point tanımlayalım. Projemizde kullanacağımız modellerimizi aşağıdaki gibi oluşturalım.

        public class BaseResponse
        {
            public bool IsSuccess { get; set; }
        }

        public class GetDistrictsByCityCodeResponse : BaseResponse
        {
            public List<string> CityList { get; set; }
        }

Controller'ın kullanacağı ILocationService ve onun implementasyonunu aşağıdaki gibi tanımlayalım.

        public interface ILocationService
        {
            [Cache(300)]
            GetDistrictsByCityCodeResponse GetDistrictsByCityCode(int cityCode);
        }

        public class LocationService : ILocationService
        {
            public GetDistrictsByCityCodeResponse GetDistrictsByCityCode(int cityCode)
            {
                return new GetDistrictsByCityCodeResponse
                {
                    CityList = new List<string> {
                        "Alaçam","Asarcık","Atakum",
                        "Ayvacık", "Bafra", "Canik",
                        "Çarşamba","Havza", "İlkadım",
                        "Kavak","Ladik", "Ondokuzmayıs",
                        "Salıpazarı","Tekkeköy", "Terme",
                        "Vezirköprü", "Yakakent" },
                    IsSuccess = true
                };
            }
        }

Yukarıda görüldüğü üzre GetDistrictsByCityCode metodu üzerinde CacheAttribute'ünü tanımladık ve cache süresi olarak 300 saniye yani 5 dk set ettik. Şimdi ise LocationController'ı oluşturalım ve controller içerisinde tanımlayacağımız end-point ILocationService'i kullanarak GetDistrictsByCityCode metoduna ilgili isteği yapacak. ILocationService'i controller'da kullanabilmek içinde controller'ın constructor'ında seviyesinde ILocationService'i inject edeceğiz.

        public class LocationController : ApiController
        {
            private readonly ILocationService _locationService;
            public LocationController(ILocationService locationService)
            {
                _locationService = locationService;
            }
            [HttpGet]
            public HttpResponseMessage GetDistrictsByCityCode(int cityCode)
            {
                var response = _locationService.GetDistrictsByCityCode(cityCode);
                return Request.CreateResponse(response);
            }
        }

Metodumuz geriye BaseResponse'dan türemiş olan GetDistrictsByCityCodeResponse objesini dönecek ve bu objede bulunan IsSuccess parametresini kontrol ederek modeli cache'e atıp atmamaya karar vereceğiz. Bunu yapmamızın sebebi hatalı response'ları cache atmayı önlemek. LocationService içerisindeki metotta dummy olarak geriye Samsun ilinin ilçelerini return eden bir response oluşturduk ve projemizi deneme amaçlı bunu return edeceğiz.

Sırada cache işlemlerinin yapıldığı aspect'imizi tanımlama var. Aşağıda olduğu gibi CacheInterceptor adında aspect'imizi tanımlayalım.

    public class CacheInterceptor : IInterceptor
    {
        private readonly ICache _cacheProvider;
        private readonly object _lockObject = new object();
        public CacheInterceptor(ICache cacheProvider)
        {
            _cacheProvider = cacheProvider;
        }

        public void Intercept(IInvocation invocation)
        {
            //metot için tanımlı cache flag'i var mı kontrolü yapıldığı yer
            var cacheAttribute = invocation.Method.GetAttribute<CacheAttribute>();
            if (cacheAttribute == null)//eğer o metot cache işlemi uygulanmayacak bir metot ise process normal sürecinde devam ediyor
            {
                invocation.Proceed();
                return;
            }

            lock (_lockObject)
            {
               //eğer o metot cache işlemlerinin yapılması gereken bir metot ise ilk olarak dynamic olarak aşağıdaki gibi bir cacheKey oluşturuyoruz
                var cacheKey = string.Concat(invocation.TargetType.FullName, ".", invocation.Method.Name, "(", JsonConvert.SerializeObject(invocation.Arguments), ")");
                //bu key ile tanımlı bir cache objesi var mı kontrol ediyoruz
                var cachedObj = _cacheProvider.Get(cacheKey);
                if (cachedObj != null)//eğer var ise o objeyi alıp client'a return ediyoruz
                {
                    invocation.ReturnValue = cachedObj;
                }
                else
                {
                    //yok ise metottan çıktıktan sonra return edilen response'u 
                    invocation.Proceed();
                    var returnValue = invocation.ReturnValue;
                    var response = returnValue as BaseResponse;
                    if (response != null)
                    {
                        //eğer o response bizim beklediğimiz gibi BaseResponse'dan türeyen bir model ise o modeli alıyoruz ve  başarılı bir şekilde client'a dönülen bir response ise cache'e atıyoruz
                        if (response.IsSuccess)
                        {
                            var cacheExpireDate = DateTime.Now.AddSeconds(cacheAttribute.CacheDurationInSecond);
                            _cacheProvider.Set(cacheKey, invocation.ReturnValue, cacheExpireDate);
                        }
                    }
                }
            }
        }
    }

Yukarıdaki akışı anlatmak gerekirse;

  • İlk olarak ilgili metot üzerinde bir cache flag'i var mı yok mu ona bakıyoruz. Eğer metot üzerinde tanımlı CacheAttribute'ü var ise o metot bizim için cacheinterceptor tarafından cache işlemlerine tabi tutulur demek. Yoksa hiç cache'i karıştırmadan process devam eder.
  • CacheAttribute varsa sonraki adımda metot namespace + metot ismi + metot parametrelerinden oluşan bir dynamic cacheKey oluşturuluyor ve bu cacheKey ile cache provider'ınızda (redis, memory cache, vs.) daha önce tanımlanmış olup halen geçerliliği olan bir cache objesi var mı bu kontrol yapılıyor.
  • Eğer varsa cache de bulunan obje return ediliyor ve GetDistrictsByCityCode metoduna girmeden client'a cevap dönülüyor.
  • Eğer cache'de bulunan bir obje yoksa GetDistrictsByCityCode metoduna girip ilgili işlemleri yaptıktan sonra metottan çıkıyor. Sonrasında metodun return ettiği model BaseResponse'dan türeyen bir model ise ve IsSuccess'i true ise obje cache'e atılıyor ve daha sonra client'a dönülüyor.

Geriye son adım olarak da ilgili bağımlılıkları tanımlayıp CacheInterceptor'ını install etmek kalıyor.  Aşağıdaki gibi ilk olarak interceptor'ı install ediyoruz ve sonrasında kullanmak istediğimiz LocationService için register ediyoruz.

        public class ServiceInstaller : IWindsorInstaller
        {
            public void Install(IWindsorContainer container, IConfigurationStore store)
            {
                container.Register(Component.For<CacheInterceptor>().LifestyleSingleton());

                container.Register(Component.For(typeof(ILocationService))
                         .ImplementedBy(typeof(LocationService))
                         .Interceptors(typeof(CacheInterceptor)));
            }
        }

Installer'ı container'a tanımlamak için projemizde bulunan Global.asax içerisindeki Application_Start metodu içerisinde container'ı aşağıdaki gibi ayağa kaldırıyoruz.

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);
}

Yukarıda bulunan WebApiControllerInstaller ve ApiControllerActivator daha önceki yazımızda  oluşturduğumuz installer ile birebir aynıdır. 

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();
        }
    }
}

Projemiz hazır ve artık yazmış olduğumuz end-point'i test edebiliriz. GetDistrictsByCityCode metoduna ilk gelen request'ten sonra return edilen response cache'e atılacaktır ve CacheAttribute tanımlarken verdiğiniz cacheDuration süresi içerisindeki ikinci request'i yaptığınızda GetDistrictsByCityCode scope'ları arasındaki işlemleri yapmadan ilgili response oluşturulan cacheKey ile bulup cache'den döndüğünü göreceksinizdir. 

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.

Local Functions C# 7

Local Functions bizlere method scope'ları içerisinde function'lar tanımlamamızı sağlar ve o fonction'ı tıpkı o scope aralığında tanımlanmış bir değişken gibi kullanabiliriz. 

Local Functions C# 7.0 ile gelen oldukça özel tabir edilen feature'lar dan biridir. Local Function tanımladığımız yer bir method, property veya bir constructor olabilir ve IL kodlarına baktığımızda local function'lar compiler tarafından private metotlara dönüştürülürler.

Geliştirme yaparken bir UpdateModel adında bir metodunuzun olduğunu varsayalım ve akış gereği bu metodun içerisinde küçük operasyonel işler yapan kod parçacıkları olduğunu düşünelim. Bu kod parçacıkları sadece o metot tarafından kullanılacağından o metot dışında ayrı bir metot olarak yazma gereği duymayız. Ancak o metoda baktığımızda da belki 200 satırdan fazla koddan oluşmuş ve sürekli kendi içerisinde tekrar eden kodlar içeren bir metot haline gelmiş olabilir. Local function tam da bunun gibi durumlarda karşımıza çıkıyor. O küçük kod parçacıklarını UpdateModel metodu içerisinden ayrı küçük birer metot olarak tanımlayabiliyoruz.

Örnek olarak aşağıdaki gibi küçük bir toplama işlemi yapalım.

public class Program
{
	public static void Main()
	{ 
		int Topla(int x, int y, int z)
        {
           return x + y + z;
		}

		Console.WriteLine(Topla(3, 4, 6));
	}
}

Projeyi çalıştırdığınızda 3,4 ve 6 nın toplamı olan 13 değerini ekranda görüyor olacaksınız.

Local function'lar aynı zamanda tanımlandığı metot içerisindeki değişkenlere de access özelliği taşır. Örnek olarak aşağıdaki gibi bir local function tanımlayabiliriz.

	public static void Main()
	{ 
		int Topla(int x, int y, int z)
        {
           return x + y + z + t;
		}

		int t = 8;
		
		Console.WriteLine(Topla(3, 4, 6));
	}

Local Function'lar kısaca bu şekilde. Küçük bir feature gibi görünse de doğru yerde doğru zamanda oldukça faydalı olacakları kesindir :)