-
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 속성을 추가하자.
1234567891011121314151617181920212223public 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를 사용하면 될 것 같다.
1234567891011121314151617181920212223...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)는 좋은 습관이므로 지킨다고 해서 손해볼 것은 없을 것이다.
참조
'JAVA > DDD' 카테고리의 다른 글
DDD - ORM과 투명한 영속성 10부 11부 (0) 2021.01.24 DDD - ORM과 투명한 영속성 8부 9부 (0) 2021.01.24 DDD - ORM과 투명한 영속성 5부 (0) 2021.01.24 DDD. ORM과 투명한 영속성 4부 (0) 2021.01.24 DDD. ORM과 투명한 영속성 3부 (0) 2021.01.23