ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 도메인 주도 설계의 적용 - 3. 의존성 주입과 관점 지향 프로그래밍 4부
    [옛날 글들] 도메인 주도 설계 2024. 4. 18. 15:23
    728x90

    이전글 : 도메인 주도 설계의 적용 - 3. 의존성 주입과 관점 지향 프로그래밍 3부

     

    이 글은 제가 2008년 6월부터 10월까지 5개월간 마이크로소프트웨어에 연재했던 "도메인 주도 설계의 적용"이라는 원고의 원글입니다.  잡지에 맞추어 편집을 하는 과정에서 지면 상의 제약으로 인해 수정되거나 삭제된 부분이 있어 제 블로그에 원글을 올립니다. 도메인 주도 설계(Domain-Driven Design)에 관심 있는 분들에게 도움이 되었으면 합니다. 

    사용(Use)으로부터 구성(Configuration) 분리하라

    UML 다이어그램을 통해 현재 설계에 어떤 문제점이 있는지 살펴보자.

    그림2 OrderLineItem 직접 CollectionProductRepository 생성하기 때문에 OrderLineItem  여전히 구체적인 클래스에 의존한다.

     

    OrderLineItem 직접 CollectionProductRepository 생성하기 때문에 여전히 OrderLineItem CollectionProductRepository 간에 강한 결합 관계가 존재한다. 만약 CollectionProductRepository 데이터베이스에 접근하도록 수정한다면 여전히 리팩토링을 수행하기 이전의 설계가 안고 있던 OCP 위반, 단위 테스트의 번거로움, 데이터베이스에 대한 종속성과 같은 문제점을 고스란히 안게  것이다.

     

    문제의 원인은 객체의 구성(Configuration) 사용(Use) OrderLineItem  곳에 공존하고 있다는 것이다. 현재 설계에서는 OrderLineItem 직접 구체적인 클래스인 CollectionProductRepository와의 관계를 설정한다. 객체의 구성과 사용이  곳에 모여 있을 경우 객체 간의 결합도가 높아진다. 해결 방법은 외부의 객체가 OrderLineItem CollectionProductRepository 간의 관계를 설정하도록 함으로써 구성을 사용으로부터 분리시키는 것이다.

     

    그림3 구성과 사용을 분리시킴으로써 OrderLineItem CollectionProductRepository 간의 직접적인 결합도를 제거했다.


    이처럼 
    협력하는 객체들의 외부에 존재하는 제3의 객체가 협력하는 객체 간의 의존성을 연결하는 것을 의존성 주입
    (Dependency Injection)이라고한다. 직접 의존성 주입을 수행하는 인프라 스트럭처 코드를 작성할 수도 있으나 이를 수월하게 해주는 다양한 오픈소스 프레임워크가 개발되어 있다.  프레임워크들은 의존성을 주입할 객체들의 생명주기를 관리하는 컨테이너 역할을 수행하기 때문에 경량 컨테이너(lightweight container)라고도 불린다.  아티클에서는 이들  가장 폭넓은 기능을 제공하면서도 가장 많은 개발자 커뮤니티의 지지를 받고 있는 경량 컨테이너인 Spring 프레임워크를 사용하기로 한다.사용되는Spring의 버전은 2.5이다.

     

    우선 OrderLineItem에서 CollectionProductRepository 생성하는 부분을 제거한다. CollectionProductRepository OrderLineItem 간의 의존성을 삽입할  있도록 setter 메소드를 추가한다. 이처럼 setter 메소드를 이용하여 객체 간의 의존성을 삽입하는 것을 세터 주입(SETTER INJECTION)이라고 한다. setter 메소드로 전달된 인자의 타입 역시 ProductRepository 인터페이스라는 것에 주의하자.

    public class OrderLineItem {
      private ProductRepository productRepository;  
      
      public OrderLineItem() {            
      }
    
      public OrderLineItem(String productName, int quantity) {
        this.product = productRepository.find(productName);
        this.quantity = quantity;
      }
         
      public void setProductRepository(ProductRepository productRepository) {
        this.productRepository = productRepository;
      }
    }

     

    Spring 같은 경량 컨테이너를 사용함으로써 얻을  있는  하나의 장점은 불필요한 싱글턴(SINGLETON) 줄일  있다는 점이다. Spring 컨테이너에서 관리할 객체를 등록할  객체의 인스턴스를 하나만 유지할 지 필요 시 매번 새로운 인스턴스를 생성할 지를 정의할  있다. 따라서 오버라이딩이 불가능하고 결합도가 높은 static 메서드를사용하지 않고서도 객체를 싱글턴으로 유지할  있다. 따라서 Spring 사용하면 싱글턴으로 구현된 Registrar 인터페이스와 구체적인 클래스로 분리함으로써 낮은 결합도와 높은 유연성을 제공할  있다. EXTRACT INTERFACE 리팩토링을 적용하자.

    package org.eternity.common;
    
    import java.util.Collection;
    
    public interface Registrar {
      void init();
      void add(Class<?> entryPointClass, EntryPoint newObject);
      EntryPoint get(Class<?> entryPointClass, String objectName);
      Collection<? extends EntryPoint> getAll(Class<?> entryPointClass);
      EntryPoint delete(Class<?> entryPointClass, String objectName);
    }

     

    Registrar 인터페이스의 구현 클래스는  이상 싱글턴 필요가 없다. static 멤버 변수와 생성 메서드(CREATION METHOD), static 메소드들을 인스턴스 메소드로 변경하자.

    package org.eternity.common;
    
    import java.util.Collection;
    import java.util.Collections;
    import java.util.HashMap;
    import java.util.Map;
     
    public class EntryPointRegistrar implements Registrar {
      private Map<Class<?>,Map<String,EntryPoint>> entryPoints;
     
      public EntryPointRegistrar() {
        init();
      }
          
      public void init() {
        entryPoints = new HashMap<Class<?>, Map<String, EntryPoint>>();
      }
     
      public void add(Class<?> entryPointClass, EntryPoint newObject) {
        Map<String,EntryPoint> theEntryPoint = entryPoints.get(entryPointClass);
        
        if (theEntryPoint == null) {
          theEntryPoint = new HashMap<String,EntryPoint>();
          entryPoints.put(entryPointClass, theEntryPoint);
        }
    
        theEntryPoint.put(newObject.getIdentity(), newObject);
      }
    
      public EntryPoint get(Class<?> entryPointClass, String objectName) {
        Map<String,EntryPoint> theEntryPoint = entryPoints.get(entryPointClass);
        
        return theEntryPoint.get(objectName);
      }
     
      @SuppressWarnings("unchecked")
      public Collection<? extends EntryPoint> getAll(Class<?> entryPointClass) {
        Map<String,EntryPoint> foundEntryPoints = entryPoints.get(entryPointClass);
        
        return (Collection<? extends EntryPoint>)Collections.unmodifiableCollection(
          foundEntryPoints != null ? entryPoints.get(entryPointClass).values() : Collections.EMPTY_SET);
      }      
      
      @SuppressWarnings("unused")
      public EntryPoint delete(Class<?> entryPointClass, String objectName) {
        Map<String,EntryPoint> theEntryPoint = entryPoints.get(entryPointClass);
        
        return theEntryPoint.remove(objectName);     
      } 
    }

     

    이제 CollectionProductRepository 클래스는 Registrar 인터페이스에 의존할  있다. SETTER INJECTION 위해 setter 메소드를 추가하자

    public class CollectionProductRepository implements ProductRepository {
      private Registrar registrar;     
      
      public CollectionProductRepository() {
      }
              
      public void setRegistrar(Registrar registrar) {
        this.registrar = registrar;            
      }
    }

     

    Spring EntryPointRegistrar CollectionProductRepository 클래스의 생명 주기를 관리하도록 하기 위해서는 Spring  컨텍스트에  클래스의 설정 정보를 정의해야 한다. org/eternity 클래스 패스에 “order-beanContext.xml” 파일을 추가하자. EntryPointRegistrar id “registrar” 빈으로 등록한  CollectionProductRepository registrar 프로퍼티에 의존성이 주입되도록 설정한다.

    <?xml version="1.0" encoding="UTF-8" ?>
    
    <beans xmlns="http://www.springframework.org/schema/beans" 
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
           xmlns:context="http://www.springframework.org/schema/context" 
           xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd  
             http://www.springframework.org/schema/context
             http://www.springframework.org/schema/context/spring-context-2.5.xsd"> 
             
      <bean id="registrar" class="org.eternity.common.EntryPointRegistrar"/>
      
      <bean id="productRepository" class="org.eternity.customer.memory.CollectionProductRepository">
          <property name="registrar" ref="registrar"/>
      </bean>
    </beans>

     

    그림4 Spring 의존성 주입을 통해 약하게 결합된 클래스 구조도. 구체적인 클래스가 추상적인 인터페이스에 의존한다.

     

     

    다음글 : 도메인 주도 설계의 적용 - 3. 의존성 주입과 관점 지향 프로그래밍 5부

    728x90
Designed by Tistory.