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과 CollectionProductRepository 간에 강한 결합 관계가 존재한다.
- 만약 CollectionProductRepository를 데이터베이스에 접근하도록 수정한다면 여전히 리팩토링을 수행하기 이전의 설계가 안고 있던 OCP 위반, 단위 테스트의 번거로움, 데이터베이스에 대한 종속성과 같은 문제점을 고스란히 안게 될 것이다.
- 문제의 원인은 객체의 구성(Configuration)과 사용(Use)이 OrderLineItem 한 곳에 공존하고 있다는 것이다.
- 현재 설계에서는 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 프로퍼티에 의존성이 주입되도록 설정한다.
=
참조