Eternity's Chit-Chat

aeternum.egloos.com



Domain-Driven Design의 적용-4.ORM과 투명한 영속성 8부 Domain-Driven Design

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

 

package org.eternity.customer;

 

import org.springframework.beans.factory.annotation.Configurable;

 

@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;

  }

}

 

OrderLineItem Order HashSet에 저장되기 때문에 equals() hashCode()를 반드시 오버라이딩해야 한다. 하나의 Order 내에는 하나의 Product에 대해 하나의 OrderLineItem 만이 존재해야 한다는 도메인 규칙이 있으므로 Product quantity를 사용하여 비교를 수행하면 될 것 같다. OrderLineItem 역시 지연 로딩 문제를 방지하기 위해 getQuantity() 메소드를 사용한다.

 

  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;

  }

 

매핑 파일은 도메인 클래스 명에 .hbm.xml을 붙이고 도메인 클래스와 동일한 클래스 경로에 위치시키는 것이 관례이다. Order OrderLineItem의 매핑 파일은 Order.hbm.xml이며 org/eternity/customer 클래스 패스에 위치한다.

 

<?xml version="1.0" encoding="UTF-8"?>

<!DOCTYPE hibernate-mapping PUBLIC

  "-//Hibernate/Hibernate Mapping DTD 3.0//EN"

  "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd" >

 

<hibernate-mapping package="org.eternity.customer" default-access="field">

  <class name="Order" table="ORDERS">

    <id name="id" column="ID" type="long">

      <generator class="native"/>

    </id>           

    <property name="orderId" column="ORDER_ID" type="string" length="10"/>

    <many-to-one name="customer" class="Customer"

      column="CUSTOMER_ID" not-null="true"/>

    <set name="lineItems" table="ORDER_LINE_ITEMS"

      cascade="all, delete-orphan">

      <key column="ORDER_ID"/>

      <one-to-many class="OrderLineItem"/>

    </set>          

  </class>

 

  <class name="OrderLineItem" table="ORDE_LINE_ITEMS">

    <id name="id" column="ID" type="long">

      <generator class="native"/>

    </id>           

    <property name="quantity" column="QUANTITY" type="int"/>

    <many-to-one name="product" class="Product"

      column="PRODUCT_ID" not-null="true"/>        

  </class>

</hibernate-mapping>

 

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

 

위 맵핑 정보에서 눈여겨 볼 부분은 Order 클래스의 lineItems 속성에 OrderLineItem Set으로 맵핑하는 부분이다. Hibernate의 경우 맵핑 파일 내에 cascade 속성을 사용하여 영속성 전이(transitive persistence)를 지원한다. OrderLineItem의 생명주기는 Order에 종속적이므로 OrderOrderRepository를 통해 데이터베이스에 저장되거나 삭제되었을 때 함께 저장되어야 한다. 또한 Order Set으로부터 제거되었을 때 데이터베이스에서 삭제되어야 한다. cascade="all, delete-orphan"을 명시함으로써 AGGREGATE의 생명주기 제약 조건을 만족시킬 수 있다.

 

<set name="lineItems" table="ORDER_LINE_ITEMS" 

  cascade="all, delete-orphan">

  <key column="ORDER_ID"/>

  <one-to-many class="OrderLineItem"/>

</set>

 

이제 Produc 클래스의 변경 사항을 살펴 보자. 데이터베이스의 주 키를 추적하기 위한 id 속성과 함께 기본 생성자가 추가되었다. 이 역시 하부 인프라스트럭처의 제약 사항이 도메인 클래스의 구현에 영향을 미치는 예로 Hibernate의 경우 객체를 생성할 때 newInstance()를 호출하기 때문에 기본 생성자가 존재해야 한다. 기본 생성자는 public일 필요는 없다.

 

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);

  }

 

ProductVALUE OBJECT Money를 속성으로 포함한다. 앞에서 설명한 바와 같이 VALUE OBJECT는 별도의 테이블로 맵핑되지 않고 의존하는 ENTITY가 맵핑되는 테이블의 컬럼으로 맵핑된다. Hibernate component를 사용하여 VALUE OBJECT의 개념을 지원한다. 다음 맵핑 파일을 살펴 보자. <component> 엘리먼트는 PRODUCTS 테이블의 PRICE 컬럼의 값을 Mony 클래스의 amount 속성에 맵핑항 후 이를 Product 클래스의 price 속성에 설정한다.

 

<?xml version="1.0" encoding="UTF-8"?>

 

<!DOCTYPE hibernate-mapping PUBLIC

"-//Hibernate/Hibernate Mapping DTD 3.0//EN"

"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd" >

 

<hibernate-mapping package="org.eternity.customer" default-access="field">

<class name="Product" table="PRODUCTS">

<id name="id" column="ID" type="long">

<generator class="native"/>

</id>

<property name="name" column="NAME" type="string"

length="10" not-null="true"/>

<component name="price" class="Money">

<property name="amount" column="PRICE" type="big_decimal"/>

</component>

</class>

</hibernate-mapping>


핑백

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

    ... 2009/07/29 Domain-Driven Design의 적용-4.ORM과 투명한 영속성 9부 2009/07/13 Domain-Driven Design의 적용-4.ORM과 투명한 영속성 8부 [2] 2009/06/29 Domain-Driven Design의 적용-4.ORM과 투명한 영속성 7부  2 ... more

덧글

  • 이종민 2009/07/27 17:49 # 삭제

    도메인주도설계편 24편을 쉬지 않고, 읽었습니다.
    아~ 좋은 글을 읽으면 왜 이리도 행복한지...^^
    식견이 짧아 이해를 못한 부분도 있지만, 대체로 이해할 수 있어서 더 좋았습니다.
    ( 아나~,...그 동안 이 책, 저책 읽은 것이 오늘에서야 빛을 발하게 되는군...)
  • 이터너티 2009/07/29 00:24 #

    읽어 주셔서 감사합니다.
    다른 분들이 쓰신 글을 읽는 도중에 자신이 익히고 습득한 지식에 대한 확신이 들 때가 있죠.
    저 역시 그런 느낌을 받을 때 이종민님처럼 기분이 좋아지곤 합니다. ^^
※ 로그인 사용자만 덧글을 남길 수 있습니다.