JAVA/DDD

DDD - ORM과 투명한 영속성 8부 9부

100win10 2021. 1. 24. 07:43
  • OrderLineItem에도 Long 타입의 IDENTITY FILED를 추가한다.
  • @Cofigurable Annotation이 계속 사용되고 있음에 주목하자.
  • @Cofigurable Annotation Spring 컨테이너 외부에서 생성되는 객체에 Spring 컨테이너에서 선언된 빈을 의존 삽입하기 위해 사용된다. 
  • 여기에서는 Hibernate가 생성하는 OrderLineItem 객체에 ProductRepository 타입의 빈을 의존 삽입하기 위해 사용되고 있다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Configurable(value="orderLineItem",preConstruction=true)
public class OrderLineItem {
    private Long id;
    private Product product;
    private int quantity;
 
    
    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
  • OrderLineItem Order HashSet에 저장되기 때문에 equals() hashCode()를 반드시 오버라이딩해야 한다. 
  • 하나의 Order 내에는 하나의 Product에 대해 하나의 OrderLineItem 만이 존재해야 한다는 도메인 규칙이 있으므로 Product quantity를 사용하여 비교를 수행하면 될 것 같다.
  • OrderLineItem 역시 지연 로딩 문제를 방지하기 위해 getQuantity() 메소드를 사용한다.

 

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
   ...
    
    int getQuantity() {
        return quantity;
    }
    
    public boolean equals(Object object) {
        
        if (object == this) {
            return true;
        }
        
        if (!(object instanceof OrderLineItem)) {
            return false;
        }
        
        final OrderLineItem other = (OrderLineItem)object;
 
        return this.product.equals(other.getProduct())
                && this.quantity == other.getQuantity();
    }
    
    public int hashCode() {
        int result = 17;
        result = 37*result + product.hashCode();
        result = 37*result + quantity;
        return result;
    }
}
cs

 

  • Order 클래스를 ORDERS 테이블에, OrderLineItem 클래스를 ORDER_LINE_ITEMS 테이블에 맵핑한다.
  •  Hibernate는 테이블의 컬럼을 도메인 객체의 속성이나 프로퍼티 둘 중 하나에 맵핑되도록 할 수 있다. 
  • 개인적으로 속성 맵핑을 선호한다. 프로퍼티 맵핑을 위해 불필요한 gettter/setter 메소드를 추가하는 것은 클래스 추상화의 일관성을 깨기 쉬우며 개발이 번거롭기 때문이다. 
  • 프로퍼티 접근에 의한 캡슐화 보장이라는 장점은 ORM 이라는 문맥 상에서 볼 때 그다지 설득력이 없어 보인다.

 

 

 

  • 위 맵핑 정보에서 눈여겨 볼 부분은 Order 클래스의 lineItems 속성에 OrderLineItem Set으로 맵핑하는 부분이다.
  • Hibernate의 경우 맵핑 파일 내에 cascade 속성을 사용하여 영속성 전이(transitive persistence)를 지원한다.
  • OrderLineItem의 생명주기는 Order에 종속적이므로 Order OrderRepository를 통해 데이터베이스에 저장되거나 삭제되었을 때 함께 저장되어야 한다.

어노테이션 방식

  •  또한 Order Set으로부터 제거되었을 때 데이터베이스에서 삭제되어야 한다. cascade = CascadeType.ALL 을 명시함으로써 AGGREGATE의 생명주기 제약 조건을 만족시킬 수 있다.

 

 

 

  • 이제 Produc 클래스의 변경 사항을 살펴 보자. 데이터베이스의 주 키를 추적하기 위한 id 속성과 함께 기본 생성자가 추가되었다. 
  • 이 역시 하부 인프라스트럭처의 제약 사항이 도메인 클래스의 구현에 영향을 미치는 예로 Hibernate의 경우 객체를 생성할 때 newInstance()를 호출하기 때문에 기본 생성자가 존재해야 한다. 
  • 기본 생성자는 public일 필요는 없다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Product {
 
    private Long id;
    private Money price;
    private String name;
 
    Product() {
    }
 
 
    public Product(String name, long price) {
        this.name = name;
        this.price = new Money(price);
    }
}
cs
  • Product VALUE OBJECT Money를 속성으로 포함한다. 
  • 앞에서 설명한 바와 같이 VALUE OBJECT는 별도의 테이블로 맵핑되지 않고 의존하는 ENTITY가 맵핑되는 테이블의 컬럼으로 맵핑된다.
  • Hibernate component를 사용하여 VALUE OBJECT의 개념을 지원한다. 

 

 

  • 이제 REPOSITORY를 구현하자. 우리는 이전 아티클에서 REPOSITORY의 인터페이스와 구현 클래스를 분리했으므로 도메인 레이어의 다른 부분에는 영향을 미치지 않고 Hibernate 전용 REPOSITORY를 구현할 수 있다.
  • Spring Hibernate를 쉽게 연동할 수 있는 기반 클래스인 org.springframework.orm.hibernate3.support.HibernateDaoSupport를 제공한다. 
  • 이 클래스는 앞에서 선언한 SessionFactory를 사용하여 내부적으로 HibernateTemplate을 생성한다.
  •  하위 클래스에서는 getHibernateTemplate()를 호출함으로써 Hibernate Session 관련 기능을 사용할 수 있다. OrderRepository 인터페이스의 구현 클래스인 HibernateOrderRepository의 코드를 살펴보자.

 

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
public class HibernateOrderRepository extends HibernateDaoSupport
        implements OrderRepository {
    
    public Order delete(Order order) {
        getHibernateTemplate().delete(order);
        return order;
    }
 
    
    @SuppressWarnings("unchecked")
    public Order find(String orderId) {
        List<Order> result = (List<Order>)getHibernateTemplate()
                .find("from Order where orderId=?", orderId);
        if (result != null && result.size() > 0) {
            return result.get(0);
        }
        return null;
    }
    
    @SuppressWarnings("unchecked")
    public Set<Order> findAll() {
        return new HashSet<Order>(getHibernateTemplate()
                .loadAll(Order.class));
    }
 
    @SuppressWarnings("unchecked")
    public Set<Order> findByCustomer(Customer customer) {
        return new HashSet<Order>(getHibernateTemplate()
                .find("from Order where customer=?", customer));
    }
    
    public void save(Order order) {
        getHibernateTemplate().save(order);
    }
}
cs

 

  • HibernateOrderRepository HibernateDaoSupport를 상속 받고 OrderRepository를 실체화한다. 
  • 각 메소들은 getHibernateTemplate()를 사용하여 데이터베이스 관련 작업을 수행한다.
  • 이제 REPOSITORY Hibernate와 연동하도록 수정했으므로 Spring 빈 컨텍스트에 추가하자.
  • Hibernate 기반 REPOSITORY를 설정하기 위해 “sessionFactory” 프로퍼티에 앞에서 설정한 “sessionFactory” 빈을 의존 주입하도록 설정한다.

 

 

 

참조


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