Caner Tosuner

Leave your code better than you found it

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.

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.

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.

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.

NInject Nedir ? NInject Kullanarak Web Api İçin Dependency İnjection

Daha önceki IoC yazılarımızda Castle Windsor dan bahsedip örnekler üzerinden kütüphaneyi incelemiştik. Bu yazımızda ise çokça yaygın olarak kullanılan IoC container'lar dan biri olan NInject'i Web APi üzerinde inceleyeceğiz.

Ninject oldukça popüler IoC container'lar dan biri olup bağımlılıkları enjekte etmede kullanılan open source bir kütüphanedir. Dependency injection bizlere loosely coupled dediğimiz birbirlerine gevşek bağlı ve daha kolay test edilebilir geliştirmeler yapmamızı sağlayan bir design pattern dir. IoC ise belli özelliklere sahip ve birbirlerine bağımlı nesnelerin işlevlerini gerçekleştirmek için ihtiyacı olan instance'ları kendilerinin değilde bir IoC container tarafından yönetilmesini söyler.

Bu yazımızda ise Web Api üzerinde Ninject implementasyonu nasıl yapılır bunu inceliyor olacağız.

İlk olarak VS'da bir tane Web Api projesi oluşturalım ve proje referanslarına nuget üzerinden Ninject.Web.WebApi.WebHost'u aşağıdaki gibi bulup install edelim.

Kurulum bittikten sonra projede bulunan App_Start klasörü içerisinde NinjectWebCommon adında otomatik olarak bir class oluşturulduğunu göreceksiniz. Bu class içerisinde ilgili install ve register işlemlerini yapacağız. Class içerisinde baktığınızda oto-generated olan bir çok kod bloğu var ancak biz şimdilik sadece   RegisterServices() metodu ile haşır neşir olacağız.

        /// <summary>
        /// Load your modules or register your services here!
        /// </summary>
        /// <param name="kernel">The kernel.</param>
        private static void RegisterServices(IKernel kernel)
        {

        }  

Register etmek istediğimiz bağımlılıkları bu metot içerisine tanımlayacağız.

Öncelikle UserController adında örnek bir controller oluşturalım ve bu controller içerisinde tanımlı IUserService intercase'ini contructor injection yöntemi ile inject edelim.

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

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.

Şimdi sırada bağımlılıkları inject etmek var. Yukarıda NinjectWebCommon class'ı içerisinde bulunan RegisterServices metodunda IUserService'i register edeceğiz. 

        private static void RegisterServices(IKernel kernel)
        {
            kernel.Bind<IUserService>().To<UserService>();
        }

Yukarıda görüldüğü üzre implementasyon oldukça basit ve projemiz hazır durumda. UserController içerisindeki metoda Postman kullanarak aşağıdaki gibi request atıp sonucu görelim.

 

Görüldüğü üzre Ninject controller'ın constructor'ında IUserService'i için bize UserService'ini resolve etti ve herhangi bir yeni instance oluşturmadan kolayca controller'ı kullanabildik.

Ninject implementasyonu diğer container'lara göre daha basittir. Sonraki yazılarımızda Ninject ile ilgili örneklerimize devam edeceğiz.

Castle Windsor Kullanarak Logging Interceptor Oluşturma

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

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

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

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

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

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

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

    public class CommunicationController : ApiController
    {
        private readonly IEmailService _emailService;

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

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

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

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

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

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

            invocation.Proceed();

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

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

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

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

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

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

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

 

    public class ApiControllerActivator : IHttpControllerActivator
    {
        private readonly IWindsorContainer _container;

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

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

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

            return controller;
        }

        private class Release : IDisposable
        {
            private readonly Action _release;

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

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

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

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

Hepsi bu kadardı :) 

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

 

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

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

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

IoC Inversion of Control nedir ?

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

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

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

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

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

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

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

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

Test Verisi

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

 

Sonuç

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

 

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

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