본문 바로가기

IT 살이/04. 기술 - 프로그래밍

개발 프레임워크 만들기 대장정 14- Unity 컨테이너 확장? ObjectBuilder ?

이제 Unity 컨테이너가 하는 일에 대해서 굵직한 것은 거의 모두 알아본 것이나 마찬가지다. 타입 매핑 정보를 등록하고 인스턴스를 얻는 API를 컨테이너가 제공해주고 있다는 것을 알았다. 설명하지 않은 API도 더 있긴 하지만. 그리고 config의 스키마에 대해서도 좀 더 자세히 알아봐야 한다. 그러나 구체적인 사항들에 대해서는 개념과 구조 설명을 먼저 마치고 뒤에 일괄적으로 정리해보도록 하겠다. 물론 장담은 못한다 -_-;; 시간이 없거나 힘들면 관련 웹 페이지에 대한 링크로 대신할 수도 있다. 내가 원하는 방법대로 하겠다. 내 블로그니까.

이번 포스트는 다음 링크의 문서들을 참조했다. 이 문서를 처음부터 읽어서 이해가 가는 독자라면 필자의 이번 포스트는 읽지 않아도 되겠다. 

ObjectBuilder의 아키텍쳐

그리고 영문이긴 하지만 다음 포스트를 읽어보기 바란다.

Decontructing ObjectBuilder


■ Unity 컨테이너

좀 더 심화 학습으로 나가기 전에 이즈음해서 Unity 컨테이너가 뭔지 함 정의를 찍고 가보자.

Unity 컨테이너는 마이크로소프트의 dependency injection이 가능하고 확장 가능한 컨테이너이다. 가릿?

근데 이 정의는 초보자를 위한 정의이다. 좀 더 심화된 정의라면 "ObjectBuilder"라는 용어가 Unity 컨테이너의 정의에 포함되어 있어야 한다. 다음은 한국 마이크로소프트의 장현춘 아키텍트분의 설명이다.

Unity 는 ObjectBuilder에 wrapper를 씌워 개발자의 편의성을 높여 범용 개발 프레임웍으로 사용할 수 있게 P&P 팀에서 내놓은 것으로 Dependency Injection을 제공하는 컨테이너라 할 수 있다.

Unity 컨테이너는 내부적으로 ObjectBuilder라는 것을 기반으로 하고 있다는 것이다. 앞에서 Unity 컨테이너의 개념을 설명하는 그림을 보였지만, 이것을 이제 좀 더 자세히 그려보자.


ObjectBuilder를 그대로 사용해서 지금의 Unity기능(DI 컨테이너)을 개발자가 매번 구현하는 것이 너무 힘들기에 마이크로소프트에서 Unity라는 이름으로 미리 만들어 제공하고 있는 것이다. ObjectBuilder 자체는는 컨테이너도 컨터이너 프레임워크도 아니라 단지 DI 컨테이너를 만들 수 있는 기반을 제공한다. 말 디따 어렵네. 내가 써 놓고도 다시 읽어 보니 어렵다. 누가 이렇게 말해놓고 날 보고 이해하라면 난 화내는데 -_-;; 쉽게 말하면 ObjectBuilder라는 것은 객체를 만들어 낼 수 있는 공장(object factory)인데 외부(개발자)에서 그 객체를 만들어내는 과정에 참여할 수 있다는 것이다. 외부에서 참여할 수 있다는 것이 바로 기존의 것을 확장한다는 것이다.

■ Unity 컨테이너를 확장한다.

확장한다는 것은 상황에 따라서 다른 의미를 가질 수 있고 그 개념을 구현하는 기술또한 달라질 수 있다. 앞의 포스트에서 코어 프레임워크에서 UserInfo라는 베이스 클래스를 만들고 그리고 사이트 프레임워크에서 그것을 상속해서 사이트에 맞게 구현하는 것도 일종의 확장이다. 그럼 "Unity 컨테이너를 확장"한다는 것은 무슨 말인가를 먼저 이해할 필요가 있다.

그러나 방금전에 말했듯이 Unity의 거의 모든 기능은 ObjectBuilder를 기반으로 하고 있다. 해서 "Unity를 확장한다는 것이 무엇인지"를 알아 보기 전에  ObjectBuilder의 구조와 메커니즘을 이해하는 과정이 먼저여야 한다. 즉 Unity 를 확장한다는 것은 ObjectBuilder를 확장한다는 말이기 때문이다( 맞나? 아님 말고). 근데 ObjectBuilder 구조와 메커니즘을 이해한다는 것은 아키텍쳐와 패턴에 자주 접하는 사람들에게는 익숙할지 모르지만 처음 접하는 사람들에게는 간단한 것만은 아니다.  혹시 AOP(Aspect Oriented Programming)를 경험해본 사람들이라면 기존의 .NET 프레임워크의 확장 방법을 연상하면 쉽게 이해가 갈 것이다. 

■ ObjectBuilder 아키텍쳐

ObjectBuilder가 객체를 생성하는 과정은 단순하지 않다. ObjectBuilder가 최종 객체를 생성해서 요청한 코드에 반환하기까지는 미리 정해진  단계의 과정을 거친다. 예를 들어 PreCreation, Creation, Initialization, PostInitialization과 같은 단계가 있을 수 있다. 각 단계를 stage라고 한다. 

각 스테이지에서는 또 여러 작업이 포함될 수 있다. 예를 들어 PreCreation 스테이지에서는 현재 요청한 객체의 실제 타입(concrete type)이 뭔지를 등록된 매핑 정보를 통해서 확인하는 작업, 클라이언트(여기서 클라이언트는 코드를 말한다)가 singleton객체를 원하는지를  확인하는 작업, 그리고 constructor 호출시 자동 dependency injection이 발생해야 하는지 확인하는 작업, 속성에 Dependency 어트리뷰트가 붙어있는지 확인하는 작업, 메소드에 InjectionMethod 어트리뷰트가 붙어 있는지 확인하는 작업등등이 포함될 수 있다. 이런 스테이지별 실행되는 각각의 작업을 바로 strategy라고 한다. 앞에서 예를 든 작업의 strategy는 각각 TypeMappingStrategy, SingletonStrategy, ContructorReflectionStrategy, PropertyReflectionStrategy, MethodReflectionStrategy같은 이름이 붙을 수 있다.

그리고 이 strategy에 어떤 정보를 건네주고 싶은때는  policy라는 것을 사용한다. PreCreation 스테이지의 ContructionReflectionStrategy에서 현재 객체를 생성할때 어떤 버전의 contructor를 사용할지를 확인했다. 즉 객체를 생성하는데 어떤 파라미터를 넘겨줘야 하는지를 확인하는 작업을 했다. 그럼 확인된 그 파라미터에 대한 정보를 Creation 스테이지의 CreationStrategy로 넘겨줘야 한다. ContructionReflectionStrategy에서는 policy 객체를 생성해서 공용 저장소(컨텍스트 객체)에 저장한다. 이때의 컨텍스트 객체는 해당 객체를 생성하는과정별로 하나씩 존재해서 앞 스테이지의 각 strategy에서 저장한 정보를 계속 누적해서 다음 단계의 strategy로 전달될 수 있다. Creation 스테이지의 CreationStrategy에서는 컨텍스트 객체에서 ConstructionReflectionStrategy에서 저장한 policy 객체를 가져와서 객체를 생성할 때 필요한 파라미터를 구해서 contructor 또는 Activator 클래스를 이용해서 객체를 생성한다.

ObjectBuilder가 객체를 생성하는데 사용하는 개념 3가지를 알아봤다. 이 개념은 중요하다. 바로 ObjectBuilder의 확장 즉 Unity의 확장은 이 개념을 알아야 하기때문이다. 지금까지 설명한 내용을 다시 도식화해보자. 앞의 그림에서 2,3단계의 과정을 다시 상세히 그린것으로 보면 된다.

역시 그림이 좋아. 단순 명료해지거든. 음....!

이것은 ObjectBuilder가 기본적으로 구현해 놓고 있는 모델이다.  ObjectBuilder의 각 스테이지의 strategy를 정리하면 다음과 같다. 절대 욀려고 하지 말라. 각 스테이지 및 stragegy를 욀려고 하지 마라. 지금은 그 개념을 이해하면 된다.

스테이지 strategy 동작 입력 출력
PreCreation TypeMapping Strategy 인터페이스나 베이스 클래스의 매핑 정보를 이용해서 실제 타입으로 변환한다. IType Mapping Policy  
  Singleton Strategy 지정된 형태의 객체가 이미 존재하는 경우엔 그것을 돌려주고, 존재하지 않는 경우엔 새롭게 생성한다 Locator  
  Constructor Reflection Strategy InjectionConstructorAttribute가 붙은 constructor를 찾고, ConstructorPolicy(ICreationPolicy를 계승)를 작성한다 리플렉션 ICreation Policy
  Property Reflection Strategy ParameterAttribute가 붙은 프로퍼티를 찾고 PropertySetterPolicy를 작성한다 리플렉션 IProperty Setter Policy
  Method Reflection Strategy InjectionMethodAttribute가 붙은 메소드를 찾고 MethodPolicy를 작성한다 리플렉션 IMethod Policy
Creation Creation Strategy constructor 또는 Activator 클래스를 사용해 객체를 생성한다. ISingletonPolicy가 있다면 Locator에 등록한다 ICreation Policy ISingleton Policy Locator
Initialization Property Setter Strategy IPropertySetterPolicy를 기본으로 프로퍼티에 값을 설정한다   IProperty Setter Policy
  Method Execution Strategy IMethodPolicy를 기본으로 메소드를 실행한다 IMethod Policy  
Post Initialization Builder Aware Strategy 객체 생성의 완료를 통지한다    

앞의 그림은 ObjctBuilder기준으로 그린 것이다. ObjectBuilder2에서 구현해 놓은 각 strategy 클래스는 이와는 조금 다른 듯하다.  Untiy 소스 코드를 훑어 보다가 UnityBuildStage라는 클래스를 보게 되었다. 이 클래스를 보면 Unity 컨테이너에서는 ObjectBuilder에서 정의한 4단계의 스테이지 대신에 7단계의 스테이지를 사용하고 있는 것을 알 수 있다.  또한 각 stage에 포함되는 strategy 리스트도 달라지고 있다. 지금은 버전간의 차이를 이야기하려는 것이 아니다. ObjectBuilder의 아키텍쳐에 집중하길 바란다.

Unity의 재정의된 스테이지는 다음 코드를 보면 알 수 있다.

namespace Microsoft.Practices.Unity.ObjectBuilder

{

    /// <summary>

    /// The build stages we use in the Unity container

    /// strategy pipeline.

    /// </summary>

    public enum UnityBuildStage

    {

        /// <summary>

        /// First stage. By default, nothing happens here.

        /// </summary>

        Setup,


        /// <summary>

        /// Second stage. Type mapping occurs here.

        /// </summary>

        TypeMapping,


        /// <summary>

        /// Third stage. lifetime managers are checked here,

        /// and if they're available the rest of the pipeline is skipped.

        /// </summary>

        Lifetime,


        /// <summary>

        /// Fourth stage. Reflection over constructors, properties, etc. is

        /// performed here.

        /// </summary>

        PreCreation,


        /// <summary>

        /// Fifth stage. Instance creation happens here.

        /// </summary>

        Creation,


        /// <summary>

        /// Sixth stage. Property sets and method injection happens here.

        /// </summary>

        Initialization,


        /// <summary>

        /// Seventh and final stage. By default, nothing happens here.

        /// </summary>

        PostInitialization

    }

}

그리고 Unity의 기존 strategy 목록은 다음 코드를 보면 알 수 있다.

/// <summary>

/// This extension installs the default strategies and policies into the container

/// to implement the standard behavior of the Unity container.

/// </summary>

public class UnityDefaultStrategiesExtension : UnityContainerExtension

{

    /// <summary>

    /// Add the default ObjectBuilder strategies &amp; policies to the container.

    /// </summary>

    protected override void Initialize()

    {

        //

        // Main strategy chain

        //

        Context.Strategies.AddNew<BuildKeyMappingStrategy>(UnityBuildStage.TypeMapping);

        Context.Strategies.AddNew<LifetimeStrategy>(UnityBuildStage.Lifetime);


        Context.Strategies.AddNew<BuildPlanStrategy>(UnityBuildStage.Creation);


        //

        // Build plan strategy chain

        //

        Context.BuildPlanStrategies.AddNew<DynamicMethodConstructorStrategy>(

            UnityBuildStage.Creation);

        Context.BuildPlanStrategies.AddNew<DynamicMethodPropertySetterStrategy>(

            UnityBuildStage.Initialization);

        Context.BuildPlanStrategies.AddNew<DynamicMethodCallStrategy>(

            UnityBuildStage.Initialization);

        //

        // Policies - mostly used by the build plan strategies

        //

        Context.Policies.SetDefault<IConstructorSelectorPolicy>(

            new DefaultUnityConstructorSelectorPolicy());

        Context.Policies.SetDefault<IPropertySelectorPolicy>(

            new DefaultUnityPropertySelectorPolicy());

        Context.Policies.SetDefault<IMethodSelectorPolicy>(

            new DefaultUnityMethodSelectorPolicy());


        Context.Policies.SetDefault<IBuildPlanCreatorPolicy>(

            new DynamicMethodBuildPlanCreatorPolicy(Context.BuildPlanStrategies));

    }

}

Unity에서 제공하는 기본적인 익스텐션은 다음과 같은 스테이지와 strategy 타입을 사용하고 있다는 것을 알 수 있다.

스테이지 strategy 타입
TypeMapping BuildKeyMappingStrategy
Lifetime LifetimeStrategy
Creation BuildPlanStrategy
Initialization DynamicMethodConstructorStrategy
DynamicMethodPropertySetterStrategy

스토리는 이렇다. ObjectBuilder( 또는 ObjectBuilder2 )는 스테이지와 strategy를 확장할 수 있는 구조를 가지고 있다. Unity에서는 앞의 코드에서 처럼 스테이지를 UnityBuildStage로 정의하고 있고 그리고 여러개의 기본적인 Strategy를 정의해놓고 있다.

Unity 컨테이너를 확장한다는 것은 이런 스테이지와 strategy 목록을 Unity의 사용자가 직접 추가, 제거할 수 있다는 것이다. 

그러나 스테이지까지 재정의할 일이 있을까하는 생각도 든다.
스테이지와 strategy와 함께 Policy로 만들어야 한다. 앞에서 말한 것처럼 파이프라인상에서 뒤에 실행될 스테이지의 Strategy로 원하는 정보를 전달해야 할때 Strategy 타입에 해당하는 Policy 객체도 정의할 필요가 있다.

다른 Application Block에서도 ObjectBuilder가 제공하는 기본적인 단계 및 strategy를 사용하지 않고 나름대로의 정의해서 사용하고 있다는 것을 보면 ObjectBuilder의 위치를 알 수 있다.  그림을 그리고 싶은데, 아. 배고프다. 빨리 끊고 나가야 겠다. -_-;;;;;

이 포스트의 내용이 모두 정확한지는 잘 모르겠다. ObjectBuilder2.0의 구조를 자세히 설명해 놓은 문서를 찾지 못해서, 버전 1.0을 설명해 놓은 해외 블로그를 많이 참조했다.

실제로 이 Strategy를 사용자 정의해서 확장해서 뭘 어떻게 할 수 있다는 건지 프레임워크 입장에서 설명하고 싶은데, 생각할 힘이 없다. 진짜 배고파서 진땀이 난다. 힝.

컨테이너 확장하는 샘플은 다음 포스트로 넘겨야 겠다.