ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • DDD - ORM과 투명한 영속성 6부 7부
    JAVA/DDD 2021. 1. 24. 07:17
    • 고객이 새로운 주문을 입력했다고 하자. 시스템 내부에서는 새로운 트랜잭션이 시작되고 Order 객체와 OrderLineItem이 생성된 후 Order에 추가된다.
    • Order 객체가 생성은 되었지만 단지 메모리 상의 객체로만 존재하고 데이터베이스와 관계가 맺어지지 않은 상태를 비영속 상태(transient state)라고 한다. 
    • 어플리케이션은 트랜잭션 종료 시점에 OrderRepository.save()를 호출하여 생성된 Order를 데이터베이스에 저장한다. 
    • 이 때부터 Order는 데이터베이스 테이블의 한 레코드로 저장되는데 이처럼 데이터베이스와 연관 관계를 가지고 있는 상태를 영속 상태(persistence state)라고 한다.

     

     

    • 이제 OrderLineItem에 대해 생각해 보자. 앞에서 OrderRepository를 통해 Order만을 저장했을 뿐 OrderLineItem에 대한 영속성 로직은 처리하지 않았다. 
    • 이 경우 Order만 저장되고 OrderLineItem은 데이터베이스에 저장되지 않을까? ORM 영속성 전이(transitive persistence)라는 특징을 지원한다. 
    • 영속성 전이는 영속 객체와 연관된 객체들에게 영속성이 전이된다는 것을 의미한다. 
    • 따라서 영속 객체인 Order연관된 OrderLineItem 역시 영속 객체가 되며 따라서 트랜잭션 확약 시 자동으로 데이터베이스에 저장된다. 

     

     

    • 삭제의 경우도 마찬가지이다. 영속 객체인 Order가 삭제될 경우 연관 관계로 묶인 OrderLineItem 역시 함께 삭제된다. 
    • 이것은 AGGREGATE의 불변식을 보장하기 위해 AGGREGATE 전체가 하나의 단위로 처리돼야 하고 AGGREGATE 내부 객체들이 ENTRY POINT의 생명주기에 종속된다는 개념을 지원한다. 
    • , AGGREGATE의 생명주기와 관련된 제약사항의 구현 메커니즘으로 ORM의 영속성 전이를 사용할 수 있다.

     

     

    • 도달 가능성에 의한 영속성(persistence by reachability)은 어떤 영속 객체로부터 도달 가능한 모든 객체들이 영속 객체가 된다는 것을 의미한다. 
    • , 어떤 객체가 영속 객체라면 연관된 객체는 무조건 영속 객체가 되는 것이다. 
    • 반적인 ORM 솔루션들은 이 특징을 완전하게 지원하지는 않는다. 대신 영속성을 전이 시킬지를 매핑 시에 설정하게 함으로써 더 세밀한 제어가 가능하도록 한다.
    •  따라서 ORM을 사용할 경우 영속 객체와 연관된 객체라고 해서 무조건 함께 저장되거나 삭제되지 않는다는 사실에 주의하자.

     

     

    • ORM과 관련해서 살펴볼 마지막 이슈는 VALUE OBJECT의 매핑이다. 
    • VALUE OBJECT는 식별자를 가지지 않으며 ENTITY의 생명주기에 종속된다. 
    • VALUE OBJECT속성 값이 같은 경우 동등한 것으로 판단한다. 
    • 일반적으로 ENTITY가 별도의 테이블로 매핑되는 반면 VALUE OBJECT는 자신이 속한 ENTITY의 단순 컬럼으로 매핑된다. 이를 EMBEDDED VALUE 패턴이라고 한다.

     

     

    • 주문 시스템에서 Product ENTITY PRODUCTS 테이블에 매핑하는 경우를 살펴 보자.
    • Product는 상품의 가격을 나타내기 위해 VALUE OBJECT Money를 속성으로 포함한다. 
    • 이 경우 VALUE OBJECT Money를 별도의 테이블로 매핑하지 않고 PRODUCTS 테이블의 한 컬럼으로 매핑한다.  

     

     

     

     

    영속성 관리 REPOSITORY


    • Hibernate는 투명한 영속성을 지원하는 오픈 소스 ORM으로 Java 커뮤니티에서 사용되는 ORM의 표준이다.
    • Hibernate EJB 3.0의 엔티티 빈 스펙인 JPA에 큰 영향을 끼쳤으며 JBoss의 엔티티빈 구현체로 포함되어 있다. Spring 프레임웍은 Hibernate를 통합하기 편리하도록 여러 가지 지원 클래스들을 제공한다. 
    • 주문 시스템 역시 Hibernate를 적용하기 위해 Spring의 지원 클래스를 사용할 것이다.
    • 다음은 주문 시스템의 도메인 모델과 매핑될 데이터 모델이다.

    주문 도메인의 데이터 모델

     

    • 우선 Product PRODUCTS 테이블 간의 매핑을 살펴 보자. 지금까지는 도메인 객체들을 메모리 컬렉션 내에 관리하기 위해 EntryPoint라는 LAYER SUPERTYPE을 상속받아 구현했다.
    • EntryPoint Registrar 클래스가 도메인 객체를 클래스별로 관리할 수 있도록 강제하기 위해 추가된 상위 클래스이다. 
    • 따라서 지금까지의 도메인 모델은 Registrar라는 인프라 스트럭처와 결합되어 있었다.
    • 이처럼 하위 인프라 스트럭처에 의해 사용되는 클래스나 인터페이스에 도메인 레이어의 클래스들이 의존하는 것을 침투적(invasive)이라고 한다.

     

     

    • Spring Hibernate비침투적이다. 
    • 비침투적인 프레임워크를 사용할 경우 도메인 클래스들은 하위 인프라스트럭처에 독립적이다. 
    • , 프레임워크의 특정 인터페이스를 구현하거나 클래스를 상속받을 필요가 없다. 우리가 궁극적으로 지향하는 순수한 객체 지향 모델로 도메인 모델을 구성할 수 있는 것이다

     

     

    • Order 클래스와 EntryPoint 간의 상속 관계를 제거하자. 
    • 이제 Order는 어떤 하위 인프라 스트럭처에도 의존하지 않는 순수한 객체이다. 
    • 이처럼 어떤 인프라 스트럭처 코드에도 의존하지 않는 순수한 Java 객체를 POJO(Plain Old Java Object)라고 한다. 
    • 주문 ENTITY 추적성을 보장하기 위해서는 Order 클래스에 IDENTITY FIELD를 추가해야 한다. 
    • , ORDERS 테이블의의 주 키를 Order 클래스의 속성으로 포함시켜야 한다. ORDERS 테이블의 주 키와 매핑될 Long 타입의 id 속성을 추가하자.

     

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    public class Order {
     
        private Long id;
        private String orderId;
        private Set<OrderLineItem> lineItems = new HashSet<OrderLineItem>();
        private Customer customer;
     
     
        public static Order order(String orderId, Customer customer) {
            return new Order(orderId, customer);
        }
     
     
        Order(String orderId, Customer customer) {
            this.customer = customer;
            this.orderId = orderId;
        }
     
     
        public Long getId() {
            return id;
        }
    }
    cs

     

    • EntryPoint로부터의 상속 관계가 제거되었으며 id를 반환하기 위한 getter 메소드가 추가되었다.
    • id를 반환하는 getter 메소드를 추가한 이유는 대부분의 엔터프라이즈 어플리케이션이 프리젠테이션 레이어에서 해당 도메인 객체의 id를 필요로 하기 때문이다.

     

    • 이제 equals() hashCode()를 구현하자. 앞에서 설명한 바와 같이 equals() hashCode()에서는 대리 키 대신 자연 키를 비교해야 한다.
    • Order를 유일하게 식별하면서도 잘 변하지 않는 값이 무엇일까? orderId를 사용하면 될 것 같다
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
        ...    
     
        String getOrderId() {
            return orderId;
        }
     
        public boolean equals(Object object) {
            if (object == this) {
                return true;
            }
     
            if (!(object instanceof Order)) {
                return false;
            }
     
            final Order other = (Order)object;
            return this.orderId.equals(other.getOrderId());
        }
     
        public int hashCode() {
            return this.orderId.hashCode();
        }
    }
    cs

     

     

    • equals()에서 파라미터로 전달된 객체의 orderId 파라미터를 비교하지 않고 별도의 getOrderId() 메소드를 호출한 이유는 비교 대상으로 Proxy 객체가 전달될 수도 있기 때문이다. 
    • 따라서 equals()에서 비교를 수행하기 위해서는 getter 메소드를 직접 호출해서 지연 로딩이 일어나도록 해야한다. 도메인 모델이 하부 인프라스트럭처에 완전히 독립적일 수 없다는 것이 바로 이런 이유때문이다.
    • ORM으로 Hibernate를 쓰는 경우 Hibernate에 의한 제약사항이 도메인 모델의 구현에 미세하나마 영향을 미친다. 
    • 그러나 자기 캡슐화(self-encapsulation)는 좋은 습관이므로 지킨다고 해서 손해볼 것은 없을 것이다.

     

    참조


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

Designed by Tistory.