JAVA/DDD

DDD - Dependency Injection과 AOP 4부 5부

100win10 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의 적용