MVVM+Command Pattern+Dependency Injection #1에 이어지는 Ninject를 사용한 Dependency Injection편이에요 :D

Ninject는 ‘번개처럼 빠른 인젝션’을 모토로 한 DI 프레임워크죠. Unity랑 비교해 보면 Unity가 전통적인 개발자에게 친숙한 인터페이스와 네이밍을 가진 반면 Ninject는 모던하고 약간은 팬시한 문법과 네이밍을 가졌고 다양한 조건에서 좀 더 자동화된 인젝션을 지원한다는 느낌이 들어요. 여튼 이 전에 만든 프로젝트를 정말로 약간만 수정하면 Unity를 Ninject로 바꿀 수 있어요. 이것이 바로 모듈화된 프로그래밍의 장점이죠 ;~)

우선 프로젝트 먼저 받아가시고요~


1. Ninject 라이브러리 참조

당연하겠지만 제일 먼저 라이브러리를 교체해야겠죠?

image

Ninject는 현재 5개의 어셈블리로 이루어져 있고 3개는 Extension, 2개는 거의 필수로 사용되는 어셈블리죠. 그 중 필수인 Ninject.Core와 Ninject.Conditions를 참조하면 돼요.
※Ninject.Condition 역시 사실 필수는 아니지만 프로젝트가 조금만 커져도 거의 필수로 사용됩니다.

2. ServiceConfiguration 수정

/// 
/// 서비스에서 사용할 인젝션 매핑을 설정하는 클래스
/// 
public class ServiceConfiguration : StandardModule
{
    public override void Load()
    {
        bool isBlend = App.IsBlend;
        bool isWeb = !isBlend;

        // IPhotoSerivce에 대한 바인딩 설정
        // 블렌드에서 실행할 때 바인딩만 다르게 표현
        if (isBlend)
        {
            Bind< IPhotoService>().To< MockPhotoService>();
        }
        else
        {
            Bind< IPhotoService>().To< LivePhotoService>().Only(When.Context.Variable("ServiceName").IsNotDefined);
            Bind< IPhotoService>().To< LivePhotoService>().Only(When.Context.Variable("ServiceName").EqualTo("Live"));
            Bind< IPhotoService>().To< FlickrPhotoService>().Only(When.Context.Variable("ServiceName").EqualTo("Flickr"));
            Bind< IPhotoService>().To< DaumPhotoService>().Only(When.Context.Variable("ServiceName").EqualTo("Daum"));
            Bind< IPhotoService>().To< NaverPhotoService>().Only(When.Context.Variable("ServiceName").EqualTo("Naver"));
        }

        // 공통으로 사용할 바인딩
        // 서비스 제공자의 목록을 싱글턴으로 바인딩
        Bind< PhotoServiceProviderList>().ToConstant< PhotoServiceProviderList>(
            new PhotoServiceProviderList
            {
                new PhotoServiceProvider { Name = "Live", DisplayName = "Live", LogoSource = "/MashupImageSearch;component/Resource/WindowsLive2_20.png" },
                new PhotoServiceProvider { Name = "Flickr", DisplayName = "Flickr", LogoSource = "/MashupImageSearch;component/Resource/flickr2_20.png" },
                new PhotoServiceProvider { Name = "Daum", DisplayName = "Daum", LogoSource = "/MashupImageSearch;component/Resource/daum_20.png" },
                new PhotoServiceProvider { Name = "Naver", DisplayName = "Naver", LogoSource = "/MashupImageSearch;component/Resource/naver_20.png" },
            }
        ).Using< SingletonBehavior>();
    }
}

Unity가 UnityContainer를 제공했던 것과 유사하게 Ninject는 StandardModule을 제공해요. Ninject에서 Module이란 하나의 서비스를 구성하는 DI들의 바인딩 구성을 정의하죠.

바인딩(매핑)을 등록하는 문장도 굉장히 다른데요, RegisterType()이라는 하나의 메서드로 인자의 위치나 배열에 따라 순서를 정했던 Unity와 달리 Ninject는 Bind<타입>().To<타입>().조건… 과 같은 명시적인 형식을 가지고 있어요. 마치 LINQ 표현식의 발전 과정을 보는 듯하죠.

동적 조건 식은 조금 복잡한데요, Unity가 단순히 이름만 가지고 바인딩을 결정했던 것과는 달리 Ninject는 굉장히 다양한 시나리오와 커스터마이징이 가능한 파라미터들로 바인딩을 결정할 수 있어요. Only(When.Context.Variable(“키”).조건…과 같이 “오직 컨텍스트에 특정 변수가 조건을 만족할 때만 바인딩”으로 글을 읽듯이 읽을 수 있죠. 굉장히 가독성이 좋은 문법이라고 봐요. 다만 지네릭과 람다 익스프레션 문법을 굉장히 많이 활용하기 때문에 익숙하지 않다면 어려울 수 있겠네요.

마지막으로 Unity의 RegisterInstance()메서드는 Ninject는 Bind<타입>().ToConstant<타입>() 으로 표현하는 걸 볼 수 있고 인젝션할 대상의 라이프 타임 관리를 .Using<라이프타임 동작>() 과 같이 할 수있어요. 여기에서 서비스 제공자의 목록은 항상 같으므로 단순한 SingletonBehavior로 지정했죠.

3. ServiceLocator 수정

/// 
/// 서비스에서 사용할 설정 모듈에서 타입을 제공하는 클래스
/// 
public class ServiceLocator
{
    #region Singlton 컨테이너
    private static IKernel _kernel;
    /// 
    /// 클래스 초기화.
    /// 서비스에서 사용할 DI 컨테이너를 초기화합니다.
    /// 
    static ServiceLocator()
    {
        if (_kernel == null)
        {
            _kernel = new StandardKernel(new ServiceConfiguration());
        }
    }

    /// 
    /// 주어진 타입에 대한 인젝션을 수행한 참조를 반환합니다.
    /// 
    /// 인젝션을 처리할 대상
    /// 인젝션이 처리된 결과
    public static T Get< T>()
    {
        return _kernel.Get< T>();
    }

    /// 
    /// 주어진 타입에 대한 인젝션을 수행한 참조를 반환합니다.
    /// 
    /// 인젝션을 처리할 대상
    /// 파라미터 이름
    /// 인젝션이 처리된 결과
    public static T Get< T>(string name)
    {
        return _kernel.Get< T>(With.Parameters.Variable("ServiceName", name));
    }
    #endregion Singlton 컨테이너

    /// 
    /// 설정에 따라 이미지 검색 뷰모델의 인스턴스를 반환합니다.
    /// 
    public ImageSearchViewModel ImageSearchViewModel
    {
        get { return Get< ImageSearchViewModel>(); }
    }

    /// 
    /// 설정에 따라 포토 서비스 제공자의 목록을 반환합니다.
    /// 
    public PhotoServiceProviderList PhotoServiceProviders
    {
        get { return Get< PhotoServiceProviderList>(); }
    }
}		

Unity에서는 UnityContainer가 인젝션 바인딩(매핑) 정보를 보관하고 관리하는 역할을 모두 수행하지만 Ninject는 Kernel이 관리하는 Module에서 인젝션 바인딩 정보를 이용하여 인젝션을 관리해요. 특별히 다른 점은 없고 다만 파라미터를 줄 때도 Kernel.Get<타입>(With.Parameters.Variable(“변수이름”, 변수값) 같은 식으로 명시적이고 가독성 있는 문법을 지원해요.

이 정도 수정만으로 Unity를 Ninject로 대체하는 마이그레이션이 끝났어요. 실행 결과는 당연히 동일하고요. 간단하죠?

DI 자체에 대해서는 언제 한번 자세히 다루고 싶지만 아직 내공이 딸려서 정확한 설명은 힘드네요. 시간도 부족하고요. 우선은 지금까지 했던 프로젝트를 기반으로 지식을 확장해가면서 경험을 쌓아도 충분할거라고 봐요.

Posted by gongdo


티스토리 툴바