Eternity's Chit-Chat

aeternum.egloos.com



Domain-Driven Design의 적용-3.Dependency Injection과 Aspect-Oriented Programming 6부 Domain-Driven Design

너무  보폭으로 진행한  같다잠시 숨을 고르고 ProductRepository 대한 테스트를 추가하자지금 까지는 JUnit에서 제공하는 TestCase 클래스를 상속 받아 테스트 클래스를 작성했지만 Spring 사용할 경우 컨텍스트에 대한 세밀한 제어가 가능한 AbstractDependencyInjectionSpringContextTests 상속받는 것이 좋다테스트를 실행하고 녹색 막대인지를 확인하자.



ProductRepositoryTest.java

package org.eternity.customer;

 

import org.springframework.test

          .AbstractDependencyInjectionSpringContextTests;

 

public class ProductRepositoryTest

extends AbstractDependencyInjectionSpringContextTests {

private Registrar registrar;

private ProductRepository productRepository;

            

public void setProductRepository(ProductRepository productRepository) {

this.productRepository = productRepository;

}

      

@Override

protected String[] getConfigLocations() {

return new String[] { "org/eternity/order-beanContext.xml" };

}

 

public void onSetUp() throws Exception {

registrar.init();

}

      

public void testProductSave() throws Exception {

Product saveProduct = new Product("상품1", 1000);

       productRepository.save(saveProduct);

            

       assertSame(saveProduct, productRepository.find("상품1"));

} 

}


getConfigLocations() 
메소드는 AbstractDependencyInjectionSpringContextTests 클래스가 테스트를 수행하기 위해 사용할 Spring  컨텍스트 파일의 경로를 반환한다. onSetUp() 메소드는 모든 테스트 메소드가 실행되기 전에 실행되는 메소드이다. AbstractDependencyInjectionSpringContextTests 클래스의 setUp() 메소드는 getConfigLocations() 메소드가 반환하는  켄텍스트를 로드한  서브 클래스의 onSetUp() 메소드를 호출한다. AbstractDependencyInjectionSpringContextTests 클래스는 매번  컨텍스트를 로딩하지 않고 최초에 로딩한  켄텍스트를 재사용한다따라서 테스트 메소드 호출 시마다 매번 불필요한  컨텍스트 로딩을 막을  있으므로 테스트 수행 속도를 향상시킬  있다.

 

AbstractDependencyInjectionSpringContextTests 클래스를 사용함으로써 얻을  있는  다른 장점은 Spring getConfigLocations() 메소드에 설정된 빈들을 조사하여 테스트 케이스의 프로퍼티와 동일한 타입의빈이 존재할 경우 자동으로 의존성을 주입한다는 점이다 예에서 ProductRepositoryTest 클래스는ProductRepository 설정할  있는 setter 메소드를 제공하기 때문에  컨텍스트에 선언된ProductRepository 타입의 빈을 자동으로 프로퍼티에 설정해 준다따라서 별도로 빈을 룩업할 필요가 없다.

 

Registrar ProductRepository 대한 테스트가 성공했으니 용기를 내서 주문 시스템 전체의 결합도를 낮추어보자

관점을 바꾸자

ProductRepository 경우와 마찬가지로 CustomerRepository OrderRepository EXTRACT INTERFACE리팩토링을 수행해서 CustomerRepository, OrderRepository 인터페이스와 CollectionCustomerRepository, CollectionOrderRepository 클래스로 분리하자. Registrar 대한 setter 메소드를 추가한  이들을 Spring 컨텍스트에 정의한다.


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>

 

<bean id="customerRepository"

class="org.eternity.customer.memory.CollectionCustomerRepository">

      <property name="registrar" ref="registrar"/>

  </bean>

  

<bean id="orderRepository"

class="org.eternity.customer.memory.CollectionOrderRepository">

      <property name="registrar" ref="registrar"/>

  </bean>



REPOSITORY들을  컨텍스트에 추가했으니 망가진 것이 없는  확인하기 위해 회귀 테스트를 수행하도록하자기존의 테스트 케이스들이 Spring 경량 컨테이너의 지원을 받을  있도록 테스트 케이스를 수정하자우선 OrderTest 클래스를 AbstractDependencyInjectionSpringContextTests 클래스의 서브 클래스로 변경한 ProductRepositoryTest 동일한 방식으로 getConfigLocations() onSetUp() 메소드를 오버라이딩한다.



OrderTest.java

package org.eternity.customer;

 

import org.eternity.common.Registrar;

importorg.springframework.test.AbstractDependencyInjectionSpringContextTests;

 

public class OrderTest

extends AbstractDependencyInjectionSpringContextTests {

private Customer customer;   

private OrderRepository orderRepository;

private ProductRepository productRepository;

      

@Override

protected String[] getConfigLocations() {

return new String[] { "org/eternity/order-beanContext.xml" };

}

 

public void onSetUp() throws Exception {

((Registrar)applicationContext.getBean("registrar")).init();

 

       orderRepository = (OrderRepository)applicationContext

           .getBean("orderRepository");

       productRepository = (ProductRepository)applicationContext

           .getBean("productRepository");

 

       productRepository.save(new Product("상품1", 1000));

       productRepository.save(new Product("상품2", 5000));

            

       customer = new Customer("CUST-01""홍길동""경기도 안양시", 200000);

}

 

public void testOrderPrice() throws Exception {

Order order = customer.newOrder("CUST-01-ORDER-01")

.with("상품1", 10)

.with("상품2", 20);

       orderRepository.save(order);

       assertEquals(new Money(110000), order.getPrice());

}

 


테스트를 
실행해 보자이런 빨간 막대다호출 스택을 뒤져보니 OrderLineItem 생성자에서productRepository 참조할  NullPointerException 발생했다


OrderLineItem.java
public OrderLineItem(String productName, int quantity) {

this.product = productRepository.find(productName);

this.quantity = quantity;

}


그러고 보니 OrderLineItem Order에서 직접 new연산자를 사용하여 인스턴스를 생성했었다, OrderLineItem Spring  컨텍스트에 의해 관리되지 않는 객체이므로  컨텍스트로부터 획득되는 다른REPOSITORY들처럼 Spring 의존성 주입 서비스를 받을  없다따라서 Spring 컨테이너 외부에서 생성되는 OrderLineItem ProductRepositoty null 밖에 없다.



Order.java
public Order with(String productName, int quantity)

throws OrderLimitExceededException {

return with(new OrderLineItem(productName, quantity));

}


핑백

  • Domain-Driven Design | Jongmin Kim&#039;s Blog 2014-09-02 01:18:22 #

    ... Aspect-Oriented Programming 7부 [2] 2009/01/02 Domain-Driven Design의 적용-3.Dependency Injection과 Aspect-Oriented Programming 6부 2008/12/24 Domain-Driven Design의 적용-3.Depe ... more