Eternity's Chit-Chat

aeternum.egloos.com



Domain-Driven Design의 적용-3.Dependency Injection과 Aspect-Oriented Programming 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 인터페이스라는 것에 주의하자.


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;

}


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


OrderLineItem.java
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 인터페이스의 구현 클래스는 이상 SINGLETON 필요가 없다. static 멤버 변수와 CREATION METHOD, static 메소드들을 인스턴스 메소드로 변경하자.


EntryPointRegistrar.java
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 메소드를 추가하자


CollectionProductRepository.java
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 프로퍼티에 의존성이 주입되도록 설정한다.


order-beanContext.xml
<?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>


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


덧글

  • cavin 2008/12/24 16:08 #

    DDD 좋은 글 감사히 잘 보고 있답니다.
    좋은 하루 되세요. :)
  • 이터너티 2008/12/24 16:20 #

    부족한 글이지만 잘 보고 계신다니 마음이 뿌듯하네요.
    행복한 크리스마스 보내세요. ^^
  • 정주행중 2016/10/14 11:48 # 삭제

    와, 정주행중인데요. 글이 참 깔끔하고, 술술 읽히다 못해 중간중간 깔끔함에 감탄까지 하게 되네요.
    출판하신 책이 있다면, 심심할때 읽어보고 싶습니다~
※ 로그인 사용자만 덧글을 남길 수 있습니다.