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.

Comments (2) -

  • FooController constructor'ında 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.

Add comment