-
DDD - ORM과 투명한 영속성 5부JAVA/DDD 2021. 1. 24. 06:48
객체 관계 맵핑과 도메인 모델
- 상태와 행위를 함께 가지는 풍부한 객체 모델로 도메인 레이어를 구성하는 것을 DOMAIN MODEL 패턴이라고 한다.
- DOMAIN MODEL 패턴은 상속, 캡슐화, 다형성과 같은 객체 지향의 장점을 십분 활용함으로써 수정이 용이하고 확장성이 높으며 이해하기 쉬운 시스템을 개발할 수 있도록 한다.
- DOMAIN MODEL 패턴이 객체 지향의 모든 특징을 활용하기 때문에 영속성 메커니즘을 주도하는 관계형 데이터베이스와의 임피던스 불일치 문제가 발생한다.
- 임피던스 불일치를 해결하는 가장 좋은 방법은 도메인 로직을 처리하는 도메인 레이어와 영속성 로직을 처리하는 퍼시스턴스 레이어 간에 불일치를 조정하는 중간 레이어를 도입하는 것이다.
- 중간 레이어가 불일치 사항들을 조정함으로써 객체와 테이블이 독립적으로 발전할 수 있다.
- 이처럼 객체와 관계형 데이터베이스 간의 독립성을 보장할 수 있도록 객체와 데이터베이스 테이블 간의 데이터를 이동시키는 객체를 DATA MAPPER라고 한다.
- DATA MAPPER를 구현한 소프트웨어를 ORM(Object-Relational Mapper)이라고 한다.
- ORM은 객체와 데이터베이스 테이블 간의 매핑 정보를 XML 파일이나 Java 5 Annotation 등의 메타 데이터로 표현할 수 있도록 METADATA MAPPING을 지원한다.
- 도메인 객체 자체는 자신이 데이터베이스에 저장된다는 사실 자체를 알지 못한다
- 객체가 데이터베이스에서 조회된 정보를 저장하고 있는지 아니면 메모리에 임시로 생성된 일시적인 상태를 표현하고 있는 지와 무관하게 도메인 로직을 작성할 수 있다.
- 도메인 레이어는 메모리에 생성된 객체 그래프를 대상으로 로직을 실행한다.
- 이 과정에서 하부의 영속성 메커니즘이 개입하지만 도메인 레이어의 객체들은 이 영속성 메커니즘에 대해 투명하다.
- 이처럼 도메인 객체가 하부의 영속성 메커니즘에 독립적인 특징을 투명한 영속성(transparent persistence)이라고 한다.
- 도메인 객체가 영속성에 대해 투명해지기 위해서는 비침투적인 인프라 스트럭처를 사용해야 한다.
- EJB3.0 이전의 엔티티 빈과 같은 초기의 미숙한 ORM 기술들은 도메인 객체에 대해 침투적이었다.
- 따라서 도메인 객체는 하부 인프라 스트럭처의 특정 클래스나 인터페이스와 강한 결합도를 가졌으며, 이로 인해 개발 복잡도 증가, 단위 테스트 및 재사용성 저해, 개발 리듬의 저하와 같은 다양한 문제점이 제기되었다.
- 최근의 ORM 제품 또는 JPA와 같은 스펙은 도메인 객체에 비침투적인 기법을 사용함으로써 투명한 영속성(transparent persistence)을 지원한다.
- 정교한 ORM은 로드된 객체들의 상태 변경을 자동으로 감지하고 트랜잭션 커밋(commit) 시에 변경된 내용을 데이터베이스에 저장한다.
- 이를 위해 ORM 내부에서는 UNIT OF WORK가 현재 트랜잭션 내에서 비즈니스 연산에 의해 수정된 객체들의 집합을 유지한다.
- 트랜잭션 수행 동안 변경된 객체들은 커밋 시점에 더티(dirty) 상태에 있다고 한다.
- UNIT OF WORK는 실제로 변경된 객체들에 대해서만 update문을 실행시킨다.
- 이처럼 데이터베이스 커밋 시점에 더티 상태에 있는 객체들을 자동으로 데이터베이스에 저장하는 ORM의 특징을 자동 더티 체킹(automatic dirty checking)이라고 한다.
- ORM은 동일 트랜잭션 내에서 동일한 객체들이 한번만 로드될 수 있도록 하기 위해 IDENTITY MAP을 유지한다.
- 객체를 로드하라는 요청을 받으면 ORM은 내부의 IDENTITY MAP을 조사한다.
- 만약 IDENTITY MAP에 존재하지 않으면 이를 데이터베이스로부터 로드하고 IDENTITY MAP에 추가한다.
- IDENTITY MAP에 존재할 경우 데이터베이스에 요청을 보내지 않고 IDENTITY MAP에 저장되어 있는 객체를 반환한다.
- IDENTITY MAP을 사용하면 동일 트랜잭션 내에서 객체들을 캐싱함으로써 성능을 향상시키는 동시에 자동으로 트랜잭션의 REPEATABLE READ 격리 레벨의 특성을 얻을 수 있다.
- 또한 동일한 트랜잭션 내에서는 항상 동일한 객체가 반환되기 때문에 데이터베이스 식별자가 동일한 객체의 경우 객체 식별자도 항상 동일하게 유지시켜 준다.
- 즉, 트랜잭션 범위 내에서는 객체 식별자를 비교하는 “==” 연산자와 데이터베이스 식별자를 비교하는 equals() 메소드의 결과가 동일하다.
- 일반적으로 IDENTITY MAP은 UNIT OF WORK 내에 위치한다.
- 트랜잭션 확약 시에 UNIT OF WORK는 IDENTITY MAP에 저장된 모든 객체들의 변경 상태를 확인한 후 모든 외래 키 제약 조건을 위반하지 않으면서도 가장 효율적인 SQL문 조합을 생성한다.
- 이를 트랜잭션 이면 쓰기(transactional write-behind)라고 한다.
- 객체들이 최초에 로드될 때 데이터베이스 접근 코드는 객체 그래프의 어떤 부분들 까지 사용될 지 알 수 없다.
- 따라서 접근되지 않을 수도 있는 객체들을 모두 로딩하는 것은 매우 비효율적이며 성능에 문제를 일으킬 것이다.
- 따라서 최초에 필요한 객체만을 로드한 후 나머지 객체들은 연관 관계를 통한 항해를 통해 필요 시점에 로딩하는 것이 효율적일 것이다.
- 이처럼 객체를 필요한 시점에 데이터베이스로부터 로드하는 것을 LAZY LOADING이라고 한다.
- 주문 시스템의 Order AGGREGATE를 살펴 보자.
- AGGREGATE의 정의에 따라 Order AGGREGATE 내에서 전역적으로 접근할 수 있는 객체는 ENTRY POINT인 Order 뿐이다.
- 따라서 OrderRepository를 통해 Order만을 로드하고 OrderLineItem은 필요 시 Order로부터의 항해를 통해서만 접근 가능하다.
- 이것을 ORM 관점에서 살펴보면 OrderRepository는 데이터베이스로부터 Order 객체만을 로드하고 이를 UNIT OF WORK 내의 IDENTITY MAP에 추가한다.
- 후에 Order로부터 OrderLineItem으로 연관을 통해 항해가 일어날 경우 ORM은 LAZY LOADING 기법을 사용하여 OrderLineItem을 로딩한다.
- 따라서 AGGREGATE와 ENTRY POINT 별 REPOSITORY 할당의 배후에는 구현 기술로서의 LAZY LOADING이 위치하고 있다.
참조
'JAVA > DDD' 카테고리의 다른 글
DDD - ORM과 투명한 영속성 8부 9부 (0) 2021.01.24 DDD - ORM과 투명한 영속성 6부 7부 (0) 2021.01.24 DDD. ORM과 투명한 영속성 4부 (0) 2021.01.24 DDD. ORM과 투명한 영속성 3부 (0) 2021.01.23 DDD. ORM과 투명한 영속성 2부 (0) 2021.01.23