ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • DDD - Dependency Injection과 AOP 4부 5부
    JAVA/DDD 2021. 1. 22. 22:17

    우선 ProductRepository 리팩토링하자. 

    • 구체적인 클래스에서 인터페이스를 추출하는 EXTRACT INTERFACE 적용하자. 
    • 인터페이스 명은 ProductRepository 하고 구현 클래스 명은 CollectionProductRepository 하자.

     

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    ProductRepository.java
     
     
    public interface ProductRepository {
     
        public void save(Product product);
     
        public Product find(String productName);
     
    }
    cs

     

     

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    CollectionProductRepository.java
     
     
    public class CollectionProductRepository implements ProductRepository {
     
        public ProductRepository() {
      
        }
            
        public void save(Product product) {
            Registrar.add(Product.class, product);
        }
           
        public Product find(String productName) {
            return (Product)Registrar.get(Product.class, productName);
        }
    }
    cs

     

    • EXTRACT INTERFACE 리팩토링을 통해 구체적인 클래스인 CollectionProductRepository 인터페이스인 ProductRepository 의존하도록 수정했다. 
    • 이제 OrderLineItem ProductRepository 인터페이스에 의존하도록 코드를 수정하자.

     

     

     

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    OrderLineItem.java
     
    public class OrderLineItem {
        private ProductRepository productRepository = new CollectionProductRepository();
     
     
        public OrderLineItem() {
        }
     
        public OrderLineItem(String productName, int quantity) {
            this.product = productRepository.find(productName);
            this.quantity = quantity;
        }
    }
    cs

     

    • OrderLineItem productRepository 속성의 타입을 ProductRepository 인터페이스로 변경함으로써 OrderLineItem 인터페이스에 의존하도록 수정했다.
    • OrderLineItem의 생성자 내부에서는 ProductRepository 인터페이스 타입의 productRepository만을 사용하기 때문에 인터페이스에만 의존하고 구체적인 클래스에는 의존하고 있지 않다. 
    • 그러나 여전히 OrderLineItem 자체는 구체적인 클래스인 CollectionProductRepository 강하게 결합되어 있다. 원인이 무엇일까?

     

     

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


     

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

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

     

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

     

     

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

     

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

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

     

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

     

     

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    OrderLineItem.java
     
    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;
        }
    }
    cs

     

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

     

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    OrderLineItem.java
     
    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);
    }
    cs
    • Registrar 인터페이스의 구현 클래스는  이상 SINGLETON 필요가 없다.
    • static 멤버 변수와 CREATION METHOD, static 메소드들을 인스턴스 메소드로 변경하자.

     

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    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);
        }
        
    }
     
    cs
    • 이제 CollectionProductRepository 클래스는 Registrar 인터페이스에 의존할  있다.
    • SETTER INJECTION 위해 setter 메소드를 추가하자

     

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    CollectionProductRepository.java
     
    public class CollectionProductRepository implements ProductRepository {
     
        private Registrar registrar;
        
        public CollectionProductRepository() {
        }
     
        public void setRegistrar(Registrar registrar) {
            this.registrar = registrar;
        }
    }
    cs

     

     

     

     

    • Spring EntryPointRegistrar CollectionProductRepository 클래스의 생명 주기를 관리하도록 하기 위해서는 Spring  컨텍스트에  클래스의 설정 정보를 정의해야 한다.
    •  EntryPointRegistrar id “registrar” 빈으로 등록한  CollectionProductRepository registrar 프로퍼티에 의존성이 주입되도록 설정한다.

    =

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

     

    참조


    이터니티 - Domain-Driven Design의 적용

Designed by Tistory.