Caner Tosuner

Leave your code better than you found it

Chain of Responsibility Pattern Nedir

Chain of Responsibility pattern behavioral patterns gurubuna ait olan ve özünde birbirini takip eden iş dizisine ait process'leri redirect ve handle etmek yada istekte bulunan-confirm eden süreçleri için çözüm olarak ortaya çıkmış bir tasarım desendir.

Yukarıda tanım yaparken birbirini takip eden iş dizesinden kasıt birbirlerine Loosly Coupled bir şekilde zincir gibi bağlı olan süreçleri process etmek için kullanabileceğimiz bir pattern dir.

Bir örnek ile ele alacak olursak; veznede çalışan bir kişi için günlük nakit para çekim miktarı 40 bin TL olan bir banka düşünelim ve bu bankaya gelen bir müşteri veznede bulunan kişiden 240 bin TL para çekmek istediğini söyledi. Banka kuralları gereği bu işlemin sırasıyla veznedar, yönetici, müdür ve bölge sorumlusu tarafından sırasıyla onaylaması gerekmekte. Bakacak olduğumuzda zincir şeklinde birbirine bağlı olan bir onay yapısı bulunmakta. 

Akış olarak özetleyecek olursak;

  1.  Müşteri 480 bin TL lik para çekme isteğini veznedar'a iletir.
  2.  Veznedar bu isteği alır ve kontrol eder eğer onaylayabileceği bir tutar ise onaylar, onaylayabileceği bir tutar değilse yöneticisine gönderir,
  3.  Yönetici isteği alır  onaylayabileceği bir tutar değilse müdüre iletir,
  4.  Müdür kontrol eder eğer onaylayabileceği bir tutar değilse bölge sorumlusunun onayına gönderir,
  5.  Bölge sorumlusu onaylar ve para müşteriye verilir.

Yukarıda bahsettiğimiz bu örneğimizi Chain of Responsibility pattern uygulayarak geliştirelim.

İlk olarak Withdraw adında domain model tanımlayalım.

   public class Withdraw
    {
        public string CustomerId { get; }
        public decimal Amount { get; }
        public string CurrencyType { get; }
        public string SoruceAccountId { get; }

        public Withdraw(string customerId, decimal amount, string currencyType, string soruceAccountId)
        {
            CustomerId = customerId;
            Amount = amount;
            CurrencyType = currencyType;
            SoruceAccountId = soruceAccountId;
        }
    }

Sornasında abstract bir Employee sınıfı tanımlayalım ve içerisinde aşağıdaki gibi property'lerinı yazalım.

    public abstract class Employee
    {
        protected Employee NextApprover;

        public void SetNextApprover(Employee supervisor)
        {
            this.NextApprover = supervisor;
        }

        public abstract void ProcessRequest(Withdraw req);
    }

Yukarıda bulunan NextApprover isimli property o sınıfa ait kişinin yöneticisi olarak atanan kişidir ve ProcessRequest metodunda ilgili condition'ı yazıp sırasıyla NextApprover'ları call edeceğiz.

Veznedar, Yonetici, Mudur ve BolgeSorumlusu sınıfları yukarıda tanımladığımız Employee sınıfından inherit olacak şekilde aşağıdaki gibi oluşturalım. 

    public class Veznedar : Employee
    {
        public override void ProcessRequest(Withdraw req)
        {
            if (req.Amount <= 40000)
            {
                Console.WriteLine("{0} tarafından para çekme işlemi onaylandı #{1}",
                    this.GetType().Name, req.Amount);
            }
            else if (NextApprover != null)
            {
                Console.WriteLine("{0} TL işlem tutarı {1} max. limitini aştığından işlem yöneticiye gönderildi.", req.Amount, this.GetType().Name);

                NextApprover.ProcessRequest(req);
            }
        }
    }

    public class Yonetici : Employee
    {
        public override void ProcessRequest(Withdraw req)
        {
            if (req.Amount <= 70000)
            {
                Console.WriteLine("{0} tarafından para çekme işlemi onaylandı #{1} TL",
                    this.GetType().Name, req.Amount);
            }
            else if (NextApprover != null)
            {
                Console.WriteLine("{0} TL işlem tutarı {1} max. limitini aştığından işlem yöneticiye gönderildi.", req.Amount, this.GetType().Name);

                NextApprover.ProcessRequest(req);
            }
        }
    }

    public class Mudur : Employee
    {
        public override void ProcessRequest(Withdraw req)
        {
            if (req.Amount <= 150000)
            {
                Console.WriteLine("{0} tarafından para çekme işlemi onaylandı #{1} TL",
                    this.GetType().Name, req.Amount);
            }
            else if (NextApprover != null)
            {
                Console.WriteLine("{0} TL işlem tutarı {1} max. limitini aştığından işlem yöneticiye gönderildi.", req.Amount, this.GetType().Name);

                NextApprover.ProcessRequest(req);
            }
        }
    }

    public class BolgeSorumlusu : Employee
    {
        public override void ProcessRequest(Withdraw req)
        {
            if (req.Amount <= 750000)
            {
                Console.WriteLine("{0} tarafından para çekme işlemi onaylandı #{1} TL",
                    this.GetType().Name, req.Amount);
            }
            else
            {
                throw new Exception(
                    $"Limit banka günlük işlem limitini aştığından para çekme işlemi #{req.Amount} TL onaylanmadı.");
            }
        }
    }

Son olarak ise domain modeli initialize edip chain'i oluşturup process metodunu call edelim.

    static void Main(string[] args)
    {
        var withdraw = new Withdraw("a6e193dc-cdbb-4f09-af1a-dea307a9ed15", 480000, "TRY", "TR681223154132432141412");
 
        Employee veznedar = new Veznedar();
        Employee yonetici = new Yonetici();
        Employee mudur = new Mudur();
        Employee bolgeSorumlusu = new BolgeSorumlusu();
 
        veznedar.SetNextApprover(yonetici);
        yonetici.SetNextApprover(mudur);
        mudur.SetNextApprover(bolgeSorumlusu);
 
 
        withdraw.Process(veznedar);
 
        Console.ReadKey();
    }

Yukarıdaki gibi 480000 TL lik bir işlem için istekte bulunduğumuzda console çıktısı aşağıdaki gibi olacaktır.

İşlem sırasıyla veznedar, yönetici, müdür bölge sorumlusunun önünde düşecektir. Son olarak ise bölge sorumlusunun onaylayabileceği bir tutar olduğundan onay verip banka müşterisine ödeme işlemini yapacaktır.

Chain of Responsibility pattern bir chain halinde process edilmesi gereken operasyonlar için rahatlıkla kullanabileceğimiz bir pattern dir. Yazılım dünyasında kullanım olarak diğer desing pattern'lar arasında %30-%40 lık bir orana sahip olduğu iddia edilir ve sıkça kullanılmaktadır.

NULL Object Pattern Nedir ?

NULL Object Pattern Gang of Four’s Design Patterns kitabında anlatılmış olup behavioral design pattern'ler den biridir. Bu pattern'in amacı uygulama içeresinde null objeler return etmek yerine ilgili tipin yerine geçen ve expected value'nun null objesi olarak kabul edilen tipi geriye dönmektir diğer bir değişle null yerine daha tutarlı nesneler dönmektir. Bu nesne asıl return edilmesi gereken nesnenin null değeri olarak kabul edilirken onunla aynı özelliklere sahip değildir, çok daha az bilgi içermektedir. NULL Object Pattern , süreli olarak null kontrolü yaparak hem server-side hemde client-side için boilerplate code yazmaya engel olmak amacıyla ortaya çıkmış bir pattern dir.

Platform yada dil farketmeksizin geliştirme yaparken sürekli olarak nullreferenceexception aldığımız durumlar olmuştur bu durumdan kurtulmak adına obj null mı değil mi diye bir sürü if/else kontrolleri yaparız. Bu pattern'i kullanarak biraz sonraki örnekte yapacağımız gibi boilerplate code'lar yazmaktan nasıl kurtulabiliriz bunu inceleyeceğiz.

Örneğimizi 2 şekilde ele alalım. İlk olarak geriye null değer return ederek çoğunlukla nasıl geliştirme yapıyoruz o case'i ele alalım, sonrasında ise NULL Object Pattern kullanarak nasıl geliştirebiliriz onu inceleyelim.

Öncelikle Customer adında bir nesnemiz var ve repository kullanarak geriye bu nesneyi return edelim. 

    public class Customer
    {
        public int Id { get; set; }
        public string FirstName { get; set; }
        public string LastNae { get; set; }
        public int NumberOfChildren { get; set; }
        public string GetFullName()
        {
            return FirstName + " " + LastName;
        }
    }

Service katmanında generic bir repository yapımız varmış gibi varsayalım ve repository üzerinden GetCustomerByFirstName adında bir metot tanımlayalım.

public class CustomerService
    {
        public Customer GetCustomerByFirstName(string firstName)
        {
            return _customerRepository.List(c => c.FirstName == firstName).FirstOrDefault();
        }
    }

Sonrasında yukarıda tanımladığımız metodu call yaparak geriye customer objesini dönelim ve bazı değerleri ekrana yazdıralım.

   var customerService = new CustomerService();
   var customer = customerService.GetCustomerByFirstName("tosuner");
   Console.WriteLine("FullName : " + customer.GetFullName() + "\nNumber Of Childreen:" + customer.NumberOfChildren);

Yukarıdaki gibi customer'ın null geldiği durumda exception thrown 'system.nullreferenceexception' hatasını çoktan aldık gibi yani memory'de değeri assing edilmemiş bir yere erişmeye çalışıyoruz. Peki çözüm olarak ne yapabilirdik, ilk akla gelen aşağıdaki gibi bir kontrol olacaktır.

    var customerService = new CustomerService();
    var customer = customerService.GetCustomerByFirstName("tosuner");
    if (customer != null)
    {
        Console.WriteLine("FullName : " + customer.GetFullName() + "\nNumber Of Childreen:" + customer.NumberOfChildren);
    }
    else
    {
        Console.WriteLine("Name : Customer Not Found !" + "\nNumber Of Childreen: 0");
    }

Yukarıdaki gibi bir çözüme gittiğimizde customer objesini get ettiğimiz bir sürü yer olduğunu düşünün ve her yerde sürekli olarak null kontrolü yapıp sonrasında console'a değerleri yazıyor oluruz. Aslında bu şu deme değil;"null kontrolü yapma arkadaş !" kesinlikle bu değil tabikide ihtiyaç duyulan yerlerde bu kontrol yapılmalı hatta birçok case'de null ise throw new CustomBusinessException() vs şeklinde exception'da throw edeceğimiz durumlar olabilir. Demek istediğim yukarıdaki gibi client'a bu kontrolü olabildiğince bırakmamak.

NULL Object Pattern uygulayarak nasıl bir çözüm getirirdik ona bakalım. İlk olarak AbstractCustomer adında base sınıfımızı oluşturalım.

    public abstract class AbstractCustomer
    {
        public abstract int Id { get; set; }
        public abstract string FirstName { get; set; }
        public abstract string LastName { get; set; }
        public abstract int NumberOfChildren { get; set; }
        public abstract string GetFullName();
    }

Sonrasında Customer objesini bu sınıftan türetelim.

    public class Customer : AbstractCustomer
    {
        public override string FirstName { get; set; }
        public override string LastName { get; set; }
        public override int NumberOfChildren { get; set; }
        public override int Id { get; set; }

        public override string GetFullName()
        {
            return FirstName + " " + LastName;
        }
    }

Şimdi ise bu pattern'in getirdiği çözüm olarak geriye null value dönmeyip asıl return edilmek istenen sınıf yerine onun null olduğunu belirten bir sınıf geriye dönelim. Bu sınıfa da NullCustomer adını verelim.

    public class NullCustomer : AbstractCustomer
    {
        public override string FirstName { get; set; }
        public override string LastName { get; set; }
        public override int NumberOfChildren { get; set; }
        public override int Id { get; set; }

        public override string GetFullName()
        {
            return "Customer Not Found !";
        }
    }

Sonrasında service katmanını aşağıdaki gibi düzenleyelim.

    public class CustomerService
    {
        public AbstractCustomer GetCustomerByFirstName(string firstName)
        {
            return _customerRepository.Where(c => c.FirstName == firstName).FirstOrDefault().GetValue();
        }
    }
    public static class CustomerExtensions
    {
        public static AbstractCustomer GetValue(this AbstractCustomer customer)
        {
            return customer == null ? new NullCustomer() : customer;
        }
    }

Yukarıdaki kod bloğunda görüldüğü üzre repository null değer dönmek yerine yeni bir NullCustomer sınıfı return edecektir.

Son adım olarak da cient tarafında yazılacak kod ise yazımızın ilk başında yazdığımız kod bloğu ile aynı.

   var customerService = new CustomerService();
   var customer = customerService.GetCustomerByFirstName("tosuner");
   Console.WriteLine("FullName : " + customer.GetFullName() + "\nNumber Of Childreen:" + customer.NumberOfChildren);

Bu pattern ile;

  • null reference kontrollerinden kurtulduk,
  • duplicate kod oranını azalttık,
  • memory de değeri olmayan bir alana erişmek yerine null value görevi gören bir nesneye eriştik,
  • dahası client tarafı için daha temiz ve kolay anlaşılır bir kod bıraktık,

Daha öncede belirtiğim gibi bu pattern'i her zaman uygulama gibi bir durum söz konusu değil, daha doğrusu sürekli null check yapmak yerine bu pattern'i uygulayalım gibi bir düşünce doğru değil. Client-side geliştirme yapan developer'a bu kontrolleri yaptırmak istemediğimizde yada "ben server-side'dan hiçbir zaman null dönmicem.." şeklinde bir garanti vermek istediğinizde kullanabileceğimiz bir pattern dir.

OptimisticLock using Fluent NHibernate

OptimisticLock ve PessimisticLock konuları hakkında Optimistic Lock Nedir ? Pessimistic Lock Nedir ? Data concurrency yazımızda bahsetmiştik. Kısaca hatırlatmak gerekirse;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.

Bu yazıda ise Nhibernate kullanarak optimistic lock nasıl yapılır bunu inceleyeceğiz. Daha önceki Unit of Work Interceptor, Castle Windsor, NHibernate ve Generic Repository yazısında geliştirdiğimiz proje üzerinden ilerleyelim. Bir web api projesi oluşturmuştuk ve nuget üzerinden Fluent Nhibernate'i yüklemiştik. İçerisinde User ve Address adında iki tane tablomuz bulunuyordu. Nhibernate için optimistic lock konfigurasyonu mapping işlemi yapılırken belirtiliyor. Bizde öncelikle versiyonlamak veya optimistic lock uygulamak istediğimiz entity'ler için bir base model oluşturalım.

    public abstract class VersionedEntity
    {
        public virtual int EntityVersion { get; set; }
    }

User modelimiz ise yukarıda tanımladığımız modelden inherit olsun ve aşağıdaki gibi UserMapping.cs içerisinde konfigurasyonlarımızı yapalım.

 

    public class User : VersionedEntity
    {
        public virtual int Id { get; set; }
        public virtual string Name { get; set; }
        public virtual string SurName { get; set; }
    }

    public class UserMap : ClassMap<User>
    {
        public UserMap()
        {
            Table("Users");
            Id(x => x.Id);
            Map(x => x.Name);
            Map(x => x.SurName);

            // versiyon işlemi için kullanılacak column
            Version(X => X.EntityVersion);
            
            // optimistic lock'ı versiyonlama üzerinden aktif hale getiriyoruz
            OptimisticLock.Version();
        }
    }

Database de Users tablomuzda EntityVersion adında bir column yaratılacak ve bu column o row için yapılan her bir update işleminde 1 artacaktır.

Konfigurasyon işlemi bu kadar şimdi test yapalım. Aşağıdaki gibi AddnewUser metoduna postman üzerinden sırayla 1 insert 2 get 2 put(update) request'i atalım.

İlk insert işlemi sonrasında db deki kayıt aşağıdaki gibi EntityVersion= 1 şeklinde olacaktır.

Sonrasında ardı ardına 2 get işlemi yapıp db deki kaydı alalım ve sonrasındaki ilk update işlemi sonrasında kaydımız aşağıdaki gibi EntityVersion = 2 şeklinde güncellenecektir.

İkinci get işlemini yapan transaction için yani üstte update yapılmışken eline stale/bayat veriye sahipken update işlemi yapmaya çalıştığında diğer bir değişle db de ki EntityVersion = 2 iken ikinci işlemin elinde EntityVersion = 1 olan kayıt varken update yapmaya çalıştığında aşağıdaki gibi bir exception throw edilir.

Hata mesajı bize o row'un bize başkabir transaction tarafından update veya delete edildiğini belirtmekte. Bu durumu yaşamamak için ikinci işlem için tekrardan db de bulunan kayıt get edilip üzerinden bir update işlemi yapıldığında db deki son görüntüsü aşağıdaki gibi EntityVersion = 3 şeklinde olacaktır.

 

Optimistic Lock için yazımız buraya kadar. Yukarıda da belirttiğim gibi örnek kodlar Unit of Work Interceptor, Castle Windsor, NHibernate ve Generic Repository yazısında bulunmakta. Eksik kalan yerler için ordan devam edebilirsiniz.

Unit of Work Interceptor, Castle Windsor, NHibernate ve Generic Repository

Unit of Work Pattern Martin Fowler'ın 2002 yılında yazdığı Patterns of Enterprise Application Architecture kısaca PoEAA olarak da adlandırılan kitabında bahsetmesiyle hayatımıza girmiş bir pattern dır.

M.Fowler kitabında UoW'ü şu şekilde tanımlar,

Maintains a list of objects affected by a business transaction and coordinates the writing out of changes and the resolution of concurrency problems.

Unit of Work; database'de execute etmemiz gereken bir dizi işlemin yani birden fazla transaction'a ihtiyaç duyarak yapacağımız işlemler (Create, Update,  Insert, Delete, Read) dizinini success veya fail olması durumunda tek bir unit yani tek bir birim olarak ele alıp yönetilmesini sağlayan pattern dir.

Diğer bir değişle; ardı ardına çalışması gereken 2 sql transaction var ve bunlardan biri insert diğeride update yapsın. İlk olarak insert yaptınız ve hemen sonrasında update sorgusunu çalıştırdınız fakat update işlemi bir sorun oluştu ve fail oldu. Unit of work tam da bu sırada araya girerek bu iki işlemi bir birimlik bir işlem olarak ele alır ve normal şartlarda ikisininde success olması durumunda commit edeceği sessino'ı update işlemi fail verdiğinden ilk işlem olan insert'ü rollback yapar ve db de yanlış veya eksik kayıt oluşmasını engeller. Yada ikiside success olduğunda session'ı commit ederek consistency'i sağlar.

Örnek üzerinden ilerleyecek olursak; bir data-access katmanımız olsun ve ORM olarak da NHibernate'i kullanıyor olalım. Projemizde IoC container olarak da Castle Windsor'ı entegre edelim. İlk olarak Vs'da "UoW_Sample" adında bir Empty Asp.Net Web Api projesi oluşturalım ve sonrasında nugetten Sırasıyla Fluent-NHibernate ve Castle Windsor'ı yükleyelim.

Case'imiz şu şekilde olsun; User ve Address adında tablolarımız var ve AddNewUser adında bir endpoint'ten hem kullanıcı hemde address bilgileri içeren bir model alarak sırasıyla User'ı ve Address'i insert etmeye çalışalım. User'ı insert ettikten sonra Address insert sırasında bir sorun oluşsun ve UoW araya girerek kaydedilecek olan user'ı da rollback yapsın.

Öncelikle User ve Address modellerimizi aşağıdaki gibi oluşturalım.

public class User
   {
       public virtual int Id { get; set; }
       public virtual string Name { get; set; }
       public virtual string SurName { get; set; }
   }
public class Address
   {
       public virtual int Id { get; set; }
       public virtual string CityCode { get; set; }
       public virtual string DistrictCode { get; set; }
       public virtual string Description { get; set; }
       public virtual int UserId { get; set; }
   }

Bu modellere ait Nhibernate Mapping'lerini de aşağıdaki gibi oluşturalım.

public class UserMap : ClassMap<User>
{
    public UserMap()
    {
        Id(x => x.Id);
        Map(x => x.Name);
        Map(x => x.SurName);
        Table("Users");
    }
}
public class AddressMap : ClassMap<Address>
{
    public AddressMap()
    {
        Id(x => x.Id);
        Map(x => x.CityCode);
        Map(x => x.DistrictCode);
        Map(x => x.Description);
        Map(x => x.UserId);
        Table("Address");
    }
}

Repository kullanımı için aşağıdaki gibi generic repo class'larını oluşturalım. Bu arayüz üzerinden db de bulunan tablolarımız için CRUD işlemlerini yapacağız.

    public interface IRepository<T> where T : class
    {
        T Get(int id);
        IQueryable<T> SelectAll();
        T GetBy(Expression<Func<T, bool>> expression);
        IQueryable<T> SelectBy(Expression<Func<T, bool>> expression);
        int Insert(T entity);
        void Update(T entity);
    }
  public abstract class BaseRepository<T> : IRepository<T> where T : class
    {
        public ISessionFactory SessionFactory { get; private set; }

        public ISession _session
        {
            get { return this.SessionFactory.GetCurrentSession(); }
        }

        public BaseRepository(ISessionFactory sessionFactory)
        {
            SessionFactory = sessionFactory;
        }

        public T Get(int id)
        {
            return _session.Get<T>(id);
        }

        public IQueryable<T> SelectAll()
        {
            return _session.Query<T>();
        }

        public T GetBy(Expression<Func<T, bool>> expression)
        {
            return SelectAll().Where(expression).SingleOrDefault();
        }
        public IQueryable<T> SelectBy(Expression<Func<T, bool>> expression)
        {
            return SelectAll().Where(expression).AsQueryable();
        }

        public int Insert(T entity)
        {
            var savedId = (int)_session.Save(entity);
            _session.Flush();
            return savedId;
        }

        public void Update(T entity)
        {
            _session.Update(entity);
            _session.Flush();
        }
    }

Tablolarımıza karşılık UserRepository ve AddressRepository class'larını arayüzleri ile birlikte aşağıdaki gibi tanımlayalım.

    public interface IUserRepository : IRepository<User>
    { }

    public class UserRepository : BaseRepository<User>, IUserRepository
    {
        public UserRepository(ISessionFactory sessionFactory) : base(sessionFactory)
        {
        }
    }
    public interface IAddressRepository : IRepository<Address>
    { }

    public class AddressRepository : BaseRepository<Address>, IAddressRepository
    {
        public AddressRepository(ISessionFactory sessionFactory) : base(sessionFactory)
        {
        }
    }

Repository'lerimiz direkt olarak api'ın controller'ları ile haberleşmesini istemediğimizden bir service katmanımızın olduğunu düşünerek UserService adında doğrudan Repository'ler ile iletişim kurabilen class'ımızı oluşturalım ve Unit Of Work interceptor'ı da bu service class'ları seviyesinde container'a inject edeceğiz.

Projede yer alan service'leri bir çeşit flag'lemek adına IApiService adında bir base interface tanımlayalım.Bu interface'i daha sonrasında container'a bütün service'leri register etmede de kullanacağız.

    public interface IApiService
    {   }

    public interface IUserService : IApiService
    {
        void AddNewUser(AddNewUserRequest reqModel);
    }
    public class UserService : IUserService
    {
        private readonly IUserRepository _userRepository;
        private readonly IAddressRepository _addressRepository;

        public UserService(IUserRepository userRepository, IAddressRepository addressRepository)
        {
            _userRepository = userRepository;
            _addressRepository = addressRepository;
        }

        public void AddNewUser(AddNewUserRequest reqModel)
        {
            var user = new User { Name = reqModel.User.Name, SurName = reqModel.User.SurName };
            var userId = _userRepository.Insert(user);

            var address = new Address { UserId = userId, CityCode = reqModel.Address.CityCode, Description = reqModel.Address.Description, DistrictCode = reqModel.Address.DistrictCode };
            _addressRepository.Insert(address);
        }
    }

    public class AddNewUserRequest
    {
        public UserDto User { get; set; }
        public AddressDto Address { get; set; }
    }
    public class UserDto
    {
        public string Name { get; set; }
        public string SurName { get; set; }
    }
    public class AddressDto
    {
        public string CityCode { get; set; }
        public string DistrictCode { get; set; }
        public string Description { get; set; }
    }

Yukarıda end-point'imizin alacağı request model ve onun dto class'larını da oluşturduk. Şimdi ise api end-point'imizi tanılayalım.  UserController adında client'ların call yapacağı controller'ımız aşağıdaki gibi olacaktır.

    public class UserController : ApiController
    {
        private readonly IUserService _userService;

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

        [HttpPost]
        public virtual HttpResponseMessage AddNewUser(AddNewUserRequest reqModel)
        {
            _userService.AddNewUser(reqModel);
            return Request.CreateResponse();
        }
    }

Geliştirmemiz gereken 2 yer kaldı Castle Windsor implementasyonu ve UnitOfWork Interceptor oluşturulması. Projemizde her şeyi interface'ler üzerinden yaptık ve constructor injection'dan faydalandık. Şimdi ise Repository, Service ve Controller'lar için bağımlılıkları enjekte edelim ve UnitOfWork Interceptor'ı oluşturalım. 

İlk olarak NHibernateInstaller.cs'i tanımlayalım. Burda web.config/app.config dosyamızda "ConnString" key'i ile kayıtlı database conenction string'imiz olduğunu varsayalım ve aşağıdaki gibi tanımlamalarımızı yapalım.

    public class NHibernateInstaller : IWindsorInstaller
    {
        public void Install(IWindsorContainer container, IConfigurationStore store)
        {
            var sessionFactory = Fluently.Configure()
               .Database(MsSqlConfiguration.MsSql2012.ConnectionString(c => c.FromConnectionStringWithKey("ConnString")).ShowSql())
               .Mappings(m => m.FluentMappings.AddFromAssemblyOf<UserMap>())
               .ExposeConfiguration(cfg => new SchemaUpdate(cfg).Execute(false, true))
                        .ExposeConfiguration(cfg =>
                        {
                            cfg.CurrentSessionContext<CallSessionContext>();
                        })
               .BuildSessionFactory();

            container.Register(
                Component.For<ISessionFactory>().UsingFactoryMethod(sessionFactory).LifestyleSingleton());
        }
    }

İkinci olarak RepositoryInstaller.cs'i oluşturalım. Bu installer ile projemizde bulunan bütün repository interfacelerini ve onların implementasyonlarını container'a register etmiş olucaz. Her bir repository'i ayrı ayrı register etmek yerine bütün repository'lerimiz IRepository interface'in den türediğinden container'a IRepository'i implement eden bütün class'ları register etmesini belirteceğiz.

    public class RepositoryInstaller : IWindsorInstaller
    {
        public void Install(IWindsorContainer container, IConfigurationStore store)
        {
            container.Register(
                Classes.FromThisAssembly()
                    .Pick()
                    .WithServiceAllInterfaces()
                    .LifestylePerWebRequest()
                    .Configure(x => x.Named(x.Implementation.Name))
                          .ConfigureIf(x => typeof(IRepository<>).IsAssignableFrom(x.Implementation), null));
        }
    }

Üçüncü olarak ServiceInstaller.cs class'ını tanımlayalım ancak öncesinde yukarıda da belirttiğimiz gibi UnitOfWork'ü service seviyesinde container'a register edeceğiz. Sebebi ise repository'e erişimimiz service class'ları üzerinden olması. UnitOfWork'ü de interceptor olarak yaratacağız ve böylelikle service metoduna girerken session'ı bind edip metot içerisinde herhangi bir exception aldığında rollback yapacağız yada herhangi bir sorun yoksada session'ı commit edip query'leri execute etmesini sağlayacağız. Aşağıda ilk olarak unitofwork manager ve interceptor class'larını oluşturalım.

    public interface IUnitOfWorkManager
    {
        void BeginTransaction();
        
        void Commit();
        
        void Rollback();
    }
    public class UnitOfWorkManager : IUnitOfWorkManager
    {
        public static UnitOfWorkManager Current
        {
            get { return _current; }
            set { _current = value; }
        }
        [ThreadStatic]
        private static UnitOfWorkManager _current;
        
        public ISession Session { get; private set; }
        
        private readonly ISessionFactory _sessionFactory;
        
        private ITransaction _transaction;
        
        public UnitOfWorkManager(ISessionFactory sessionFactory)
        {
            _sessionFactory = sessionFactory;
        }
        
        public void BeginTransaction()
        {
            Session = _sessionFactory.OpenSession();
            CurrentSessionContext.Bind(Session);
            _transaction = Session.BeginTransaction();
        }

        public void Commit()
        {
            try
            {
                _transaction.Commit();
            }
            finally
            {
                Session.Close();
            }
        }

        public void Rollback()
        {
            try
            {
                _transaction.Rollback();
            }
            finally
            {
                Session.Close();
            }
        }
    }

 Yukarıda oluşturduğumuz manager'ı kullanarak UnitOfWorkInterceptor'ı da aşağıdaki gibi tanımlayalım.

    public class UnitOfWorkInterceptor : Castle.DynamicProxy.IInterceptor
    {
        private readonly ISessionFactory _sessionFactory;

        public UnitOfWorkInterceptor(ISessionFactory sessionFactory)
        {
            _sessionFactory = sessionFactory;
        }

        public void Intercept(IInvocation invocation)
        {
            try
            {
                UnitOfWorkManager.Current = new UnitOfWorkManager(_sessionFactory);
                UnitOfWorkManager.Current.BeginTransaction();

                try
                {
                    invocation.Proceed();
                    UnitOfWorkManager.Current.Commit();
                }
                catch
                {
                    UnitOfWorkManager.Current.Rollback();
                    throw new Exception("Db operation failed.");
                }
            }
            finally
            {
                UnitOfWorkManager.Current = null;
            }
        }
    }

Yukarıda tanımladığımız interceptor'ı aşağıdaki gibi service'leri register ederken bu service class'larına ait metotlar için UnitOfWorkInterceptor'ı configure etmesini belirteceğiz.

    public class ServiceInstaller : IWindsorInstaller
    {
        public void Install(IWindsorContainer container, IConfigurationStore store)
        {
            container.AddFacility<TypedFactoryFacility>();

            container.Register(
                Classes.FromAssemblyContaining<UserService>()
                    .Pick()
                    .WithServiceAllInterfaces()
                    .LifestylePerWebRequest()
                    .Configure(x => x.Named(x.Implementation.Name))
                          .ConfigureIf(x => typeof(IApiService).IsAssignableFrom(x.Implementation),
                            y => y.Interceptors<UnitOfWorkInterceptor>()));

        }
    }

Projemiz bir Web Api projesi olduğundan controller'lar ile ilgili container registration işlemleri için gerekli olan WebApiControllerInstaller.cs class'ı ve ControllerActivator.cs class'ı tanımlamaları da aşağıdaki gibidir.

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

Geldik son adıma. Yukarıda tanımladığımız bütün installer class'larını container'a install etmeye. Bunun için projede yer alan Global.asax.cs içerinde yer alan Application_Start metodu içerisine aşağıdaki gibi installation işlemlerini yapalım.

        protected void Application_Start()
        {
            var container = new WindsorContainer();
            container.Register(Component.For<UnitOfWorkInterceptor>().LifestyleSingleton());
            container.Install(new ServiceInstaller());
            container.Install(new RepositoryInstaller());
            container.Install(new NHibernateInstaller());
            container.Install(new WebApiControllerInstaller());
            GlobalConfiguration.Configuration.Services.Replace(
                typeof(IHttpControllerActivator),
                new ApiControllerActivator(container));
            GlobalConfiguration.Configure(WebApiConfig.Register);
        }

Postman üzerinden aşağıdaki gibi end-point'imize call yapalım ve hem iki insert işlemininde başarılı olduğu case'i hemde user insert başarılı olduktan sonra address insert sırasında bir hata verdirip ilk işleminde rollback olduğu case'i oluşturup gözlemleyebiliriz.

Unit of Work pattern gözlemlediğim kadarıyla genellikle projede her query execution sırasında o satırları try-catch e alarak değişik logic'ler uygulanarak yapılıyor ancak. Aspect oriented'ın bize sağladıklarından faydalanarak bir interceptor ile projede her yerde kullanabileceğimiz basit bir infrastructure geliştirebiliriz. Bu pattern ile aynı işleve hizmet eden birden fazla küçük küçük db transaction'ını tek bir unit olarak yönetip dirty data'nın da önüne geçmiş oluyoruz.

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.

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.

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.

Repository Katmanı için Mocking Infrastructure Oluşturma (Moq Library)

Daha önceki Unit Test yazılarımızda Unit Test Nedir Nasıl Yazılır ve Moq Library Kullanarak Unit Test Yazma konularına değinmiştik. Bu yazımızda ise çokça kullandığımız Repository Pattern CRUD işlemlerinin yapıldığı metotlar için reusable bir mocking yapısı oluşturacağız. 

Öncelikle VS'da RepositoryMocking adında bir proje oluşturalım ve sonrasında projemize Generic Repository ile ilgili tanımlamalarımızı yapalım. İlk olarak IRepository adında bir interface ve database de bulunan tablolardaki unique Id-primary key alanına karşılık gelen generic IUniqueIdentifier interface'ini oluşturalaım. Bu interface'i oluşturmamızdaki amaç her tabloda Id alanı farklı tiplerde olabilir bu nedenle objelerimizi oluştururken IUniqueIdentifier interface'inden implement ederek Id alanı için veri tipini belirteceğiz. Bu bize test metotlarımızı tanımlarken ilgili linq sorgularını oluşturmada yarar sağlayacak.

public interface IUniqueIdentifier<Tkey>
{
    TKey Id { get; set; }
}
 
public interface IRepository<Tkey,TEntity> where TEntity : IUniqueIdentifier<Tkey>
{
    IQueryable<TEntity> All();
    TEntity Get(TKey Id);
    TEntity Add(TEntity entity);
    void Update(TEntity entity);
    void Delete(TEntity entity);
}

Şimdi ise abstract olan ve IRepository den inherit olan BaseRepository class'ını oluşturalım.

    public abstract class BaseRepository<TKey, TEntity> : IRepository<TKey, TEntity> where TEntity : IUniqueIdentifier<TKey>
    {
        public IQueryable<TEntity> All()
        {
            throw new NotImplementedException();
        }

        public TEntity Get(TKey id)
        {
            throw new NotImplementedException();
        }

        public TEntity Add(TEntity entity)
        {
            throw new NotImplementedException();
        }

        public void Update(TEntity entity)
        {
            throw new NotImplementedException();
        }

        public void Delete(TEntity entity)
        {
            throw new NotImplementedException();
        }
    }

Mocking işlemi yapacağımızdan metot içlerini doldurmadım ancak tabikide ilgili linq sorgularının yazılmasını gerekir.

BaseRepository tanımlamasını da yaptıktan sonra database de bulunan User tablosu için bir object ve bu tabloya ait UserRepository class'ını oluşturalım. 

    public class User : IUniqueIdentifier<int>
    {
        public int Id { get; set; }
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public string Email { get; set; }
    }
    public class UserRepository : BaseRepository<int, User>
    {

    }

Buraya kadar olan kısımda Repository katmanı için gerekli olan her şey hazır. Artık Test projemizi oluşturabiliriz. Solution'a RepositoryMocking.UnitTest adında yeni bir test projesi oluşturalım ve içerisine RepositoryBaseTest adında bir class ekleyelim. Bu class reusable mocking setup işlemlerini yapacağımız class olacak.

 public abstract class RepositoryBaseTest
    {
        public void SetupRepositoryMock<TK, TE>(Mock mockRepo, List<TE> data) where TE : class, IUniqueIdentifier<TK>
        {
            var mock = mockRepo.As<IRepository<TK, TE>>();

            // setup All method
            mock.Setup(x => x.All()).Returns(data.AsQueryable());

            // setup Add method
            mock.Setup(x => x.Add(It.IsAny<TE>()))
                .Returns(new Func<TE, TE>(x =>
                {
                    dynamic lastId = data.Last().Id;
                    dynamic nextId = lastId + 1;
                    x.Id = nextId;
                    data.Add(x);
                    return data.Last();
                }));

            // setup Update method
            mock.Setup(x => x.Update(It.IsAny<TE>()))
                .Callback(new Action<TE>(x =>
                {
                    var i = data.FindIndex(q => q.Id.Equals(x.Id));
                    data[i] = x;
                }));

            // setup Get method
            mock.Setup(x => x.Get(It.IsAny<TK>()))
                .Returns(new Func<TK, TE>(
                    x => data.Find(q => q.Id.Equals(x))
                ));

            // setup Delete
            mock.Setup(x => x.Delete(It.IsAny<TE>()))
                .Callback(new Action<TE>(x =>
                {
                    var i = data.FindIndex(q => q.Id.Equals(x.Id));
                    data.RemoveAt(i);
                }));
        }
    }

Üstte bulunan kodlarda BaseRepository de bulunan db için All, Get, Insert, Update ve Delete işlemlerini yapacak olan metotlar için ortak bir setup yapısı oluşturduk ve UserRepository gibi diğer oluşturacağınız repository ler içinde RepositoryBaseTest class'ını kullanabileceğiz. Buda bizi her bir repository için ayrı ayrı setup işlemleri yapmaktan kurtarıyor. IRepository interface'ine yeni bir metot eklemek istediğinizde tekrardan yukarıda yazdığımız SetupRepositoryMock içerisine bu metot için gerekli setup işlemini tanımlayabiliriz. 

Şimdi ise UserRepository için UserRepositoryTest adında bir sınıf oluşturalım ve RepositoryBaseTest class'ını kullanarak mock işlemleri yapalım.

    [TestClass]
    public class UserRepositoryTest: RepositoryBaseTest
    {
        //db de bulunan tablo yerine geçecek fake tablomuz
        private List<User> _userList;

        //mock user repository
        private Mock<IRepository<int, User>> _mockRepo;

        [TestInitialize]
        public void Setup()
        {
            //tablomuzun içerisini dolduralım
            _userList = new List<User>();
            var user1 = new User
            {
                Id = 1,
                Email = "canertosuner@gmail.com",
                FirstName = "Caner",
                LastName = "Tosuner"
            };
            _userList.Add(user1);

            var user2 = new User
            {
                Id = 2,
                Email = "tanertosuner@gmail.com",
                FirstName = "Taner",
                LastName = "Tosuner"
            };
            _userList.Add(user2);

            var user3 = new User
            {
                Id = 3,
                Email = "janertosuner@gmail.com",
                FirstName = "Janer",
                LastName = "Tosuner"
            };
            _userList.Add(user3);

            var user4 = new User
            {
                Id = 4,
                Email = "yenertosuner@gmail.com",
                FirstName = "Yeneer",
                LastName = "Tosuner"
            };
            _userList.Add(user4);
             
            //mock respository değerini initialize edelim
            _mockRepo = new Mock<IRepository<int, User>>();

            //repositorybasetest class'ını kullarak crud metotlarını için setup işlemlerini yapalım
            SetupRepositoryMock<int, User>(_mockRepo, _userList);
        }
    }

UserRepository için setup işlemlerimizi tamamladık. Yukarıdaki işlemler sonrasında elimizde db de bulunan User tablosu yerine geçen bir _userList array'imiz ve bu array üzerinden repository metotlarını setup ettik. Şimdi bir kaç test metodu yazıp kodlarımızı test edelim. UserRepositoryTest class'ının son hali aşağıdaki gibidir.

    [TestClass]
    public class UserRepositoryTest: RepositoryBaseTest
    {
        private List<User> _userList;
        private Mock<IRepository<int, User>> _mockRepo;

        [TestInitialize]
        public void Setup()
        {
            _userList = new List<User>();
            var user1 = new User
            {
                Id = 1,
                Email = "canertosuner@gmail.com",
                FirstName = "Caner",
                LastName = "Tosuner"
            };
            _userList.Add(user1);

            var user2 = new User
            {
                Id = 2,
                Email = "tanertosuner@gmail.com",
                FirstName = "Taner",
                LastName = "Tosuner"
            };
            _userList.Add(user2);

            var user3 = new User
            {
                Id = 3,
                Email = "janertosuner@gmail.com",
                FirstName = "Janer",
                LastName = "Tosuner"
            };
            _userList.Add(user3);

            var user4 = new User
            {
                Id = 4,
                Email = "yenertosuner@gmail.com",
                FirstName = "Yeneer",
                LastName = "Tosuner"
            };
            _userList.Add(user4);

            _mockRepo = new Mock<IRepository<int, User>>();

            SetupRepositoryMock<int, User>(_mockRepo, _userList);
        }

        [TestMethod]
        public void Get_All_Count()
        {
            Assert.AreEqual(_userList.Count, _mockRepo.Object.All().Count());
        }

        [TestMethod]
        public void Get_By_Id_Then_Check_Name()
        {
            var item = _mockRepo.Object.FindBy(4);
            Assert.AreEqual("Yeneer", item.FirstName);
        }

        [TestMethod]
        public void Remove_User_Then_Check_Count()
        {
            var user4 = new User
            {
                Id = 4,
                Email = "yenertosuner@gmail.com",
                FirstName = "Yeneer",
                LastName = "Tosuner"
            };
            _mockRepo.Object.Delete(user4);
            Assert.AreEqual(3, _mockRepo.Object.All().Count());
        }

        [TestMethod]
        public void Add_New_User_Then_Check_Count()
        {
            var tempCount = _userList.Count;

            var user5 = new User
            {
                Email = "yenertosuner@gmail.com",
                FirstName = "Yeneer",
                LastName = "Tosuner"
            };
            _mockRepo.Object.Add(user5);

            Assert.AreEqual(tempCount + 1, _mockRepo.Object.All().Count());
        }
    }

RepositoryPattern için reusable mocking işlemi için hepsi bu kadar. Projenizde UserRepository dışında bulunan diğer repository'ler içinde aynı UserRepository de olduğu gibi generic oluşturduğumuz RepositoryBaseTest'i kullanarak setup işlemini yapıp testlerinizi yazabilirsiniz.

ServiceLocator Design Pattern

Design pattern'leri spagetti kodlara veya tekrar eden kod bloklarına çözümler olarak ortaya çıkmışlardır. ServiceLocator design pattern ile bağımsız bir şekilde geliştirilebilen, test edilebilen loosely coupled modüller inşa edebilirsiniz. 

ServiceLocator'ı kısaca tanımlamak gerekirse projede kullanılan service instance'larını tek bir yerden add ve get yönetimini yapma işlemlerinden sorumludur diyebiliriz.

İmplemente ederken bağımlılığı interface ve propertilerden veya constructor seviyesinde inject edilebilirsiniz.

Örnek verecek olursak birden fazla service instance'ınızın bulunduğu bir proje var ve bir ara katman web service projesi ile bu servislerin metotlarını dışarıya açtınız diyelim. Her bir metot call işleminde service instance oluşturmak yerine app-start veya başka bir anda instance'ları ServiceLocator'a register edip sonrasında ihtiyaç duyulan yerlerde burada bulunan instance'lar üzerinden service'leri kullanmak diyebiliriz.

ServiceLocator'ın temel yapı taşlarını aşağıdaki gibi sıralayabiliriz;

  1. ServiceLocator , İhtiyaç duyulan service instance'ının yaratılmasından sorumludur, eğer yaratılmışsa da serviceRepository görevi görür diyebiliriz,
  2. Initializer ,  Runtime'da service instance'ının yaratılmasından sorumludur. Instance bir kere oluşturulduğunda ServiceLocator içerisinde store edilir,
  3. Client , Service consumer'larını veya service client'larını temsil eder,
  4. Service(s) , Service contract'larını ve onların implementasyonunu temsil eder.

 

ServiceLocator Patter'nin Implementasyonu

İlk olarak VS kullanarak bir tane WCF projesi oluşturalım ve sonrasında CustomerService ve ProductService adında 2 tane service oluşturalım. Bu iki service'in contract interface'leride aşağıdaki gibi tanımlayalım.

        [ServiceContract]
        public interface ICustomerService
        {
            [OperationContract]
            string GetName();
        }

        [ServiceContract]
        public interface IProductService
        {
            [OperationContract]
            string GetName();
        }

Sırada bu contract'ları service'lere implement etme var.

        public class ProductService : IProductService
        {
            public string GetName()
            {
                return "Fitbit";
            }
        }

        public class CustomerService : ICustomerService
        {
            public string GetName()
            {
                return "John travolta";
            }
        }

Şimdi ise sırada ServiceLocator class'ını yaratmak var. Bu class static olacak ve içerisinde service instance'larını tutan bir Dictionary bulunduracak. Bu Dictionary için get ve set operasyonları kullanılmak üzre ServiceLocator class içerisinde RegisterService ve GetService adında 2 adet metot tanımlayacağız. RegisterService instance'ları dictionary'e eklemek için kullanacağımız metot. GetService ise dictionary'de bulunan service instance'ını return eden metot olacak.

    public static class ServiceLocator
    {
        private static readonly Dictionary<Type, object> registeredServices = new Dictionary<Type, object>();

        public static T GetService<T>()
        {
            return (T)registeredServices[typeof(T)];
        }

        public static void RegisterService<T>(T service)
        {
            registeredServices[typeof(T)] = service;
        }

        public static int Count
        {
            get { return registeredServices.Count; }
        }
    }

Bu aşamaya kadar oldukça basit bir ServiceLocator pattern ile projemizi oluşturduk. Şimdi ise sırada oluşturduğumuz bu Locator'ı proje içerisinde kullanmak var.

    static void Main(string[] args)
       {
           ICustomerService customerService = new CustomerService();//service instance aldık

           IProductService productService = new ProductService();//service instance aldık

           ServiceLocator.RegisterService(customerService);//service locator'da bulunan dictionary içerisine attık

           ServiceLocator.RegisterService(productService);//service locator'da bulunan dictionary içerisine attık

           ICustomerService customerClientService = ServiceLocator.GetService<ICustomerService>();//dictionary içerisinde bulunan service instance'ını aldık

           string msg = customerClientService.GetName();

           Console.WriteLine(msg);
       }

 

ServiceLocator pattern'i objelerinizi bağımlılıklarından ayırmak istediğinizde veya compile-time'da bağımlılıkları bilinmeyen objeler oluşturmanız gerektiğinde güzel bir seçenek olabilir.