김동형수 개발기

도메인 주도 개발 시작하기 - 4장 본문

책 스터디/[완료] DDD - 도메인 주도 개발 시작하기

도메인 주도 개발 시작하기 - 4장

김동형수 2023. 3. 23. 00:53

4장 리포지터리와 모델 구현

 

JPA를 이용한 리포지터리 구현

 

데이터 보관소로 RDBMS를 사용할 때, 객체 기반의 도메인 모델과 관계형 데이터 모델간의 매핑을 처리하는 기술로 ORM

자바의 ORM 표준인 JPA를 사용한다.

 

리포지터리 인터페이스는 에그리거트와 같이 도메인 영영에 속하고 리포지터리를 구현한 클래스는 인프라스트럭처 영역에 속한다.

 

리포지토리 구현 클래스를 dmoain.impl 과 같은 패키지에 위치시킬 수 있는데 이는 좋은 원칙을 따르는 것은 아니다.

리포지토리 구현 클래스를 인프라스트럭처 영역에 위치 시켜서 인프라스트럭처에 대한 의존을 낮춰야 한다.

 

리포지터리 구현 객체는 스프링 데이터 JPA가 알아서 만들어준다.

JPA를 사용하면 저장소에 반영하는 메서드를 추가할 필요는 없다. 트랜젝션 커밋 시 반영된다.

 

Criteria, JPQL을 사용해서 ID외 다른 조건으로 조회할 수 있다.

 

삭제는 보통 직접 삭제보단 flag를 사용한다.

 

스프링 데이터 JPA를 이용한 리포지터리 구현

JPA는 지정한 규칙에 맞게 리포지터리 인터페이스를 정의하면 리포지터리를 구현한 객체를 알아서 만들어 스프링 빈으로 등록해준다.

 

매핑 구현

애그리거트 루트는 엔티티이므로 @Entity로 매핑 설정한다.

밸류는 @Embeddable로 매핑 설정한다.

밸류 타입 프로퍼티는 @Embedded로 매핑 설정한다.

 

불변 타입이면 생성 시점에 필요한 값을 모두 전달받으므로 값을 변경하는 set 메서드를 제공하지 않는다.

불변 타입은 기본 생성자가 필요없음에도 불구하고 다른 코드에서 사용 못하도록 protected로 선언한다.

 

JPA는 필드/메서드 두 가지 접근방식이 있다.

메서드 방식은 get, set 메서드를 구현해야 한다.

set 메서드는 캡슐화를 깨는 원인이 될 수 있다.

의도가 잘 드러나는 기능을 제공하려면 set메서드 보다 의미를 담는 메서드 이름이 좋다?

필드 방식으로 선택해서 불필요한 get/set 메서드 구현을 하지 말아야 한다.

 

두 개 프로퍼티를 한 개 컬럼에 매핑할 때 AttributeConverter를 사용한다.

 

1:n 엔티티-밸류 관계일 때 List 타입의 프로퍼티로 지정할 수 있다.

밸류 컬렉션을 별도 테이블로 매핑할 때는 @ElementCollection, @CollectionTable을 함께 사용한다.

 

단지 별도 테이블에 데이터를 저장한다고 해서 엔티티는 아니다.

애그리거트에 속한 객체가 밸류인지 엔티티인지 구분하는 방법은 고유 식별자를 갖는지 확인하는 것이다.

@SecondaryTable을 이용하면 밸류를 저장할 테이블을 지정한다.

@SecondaryTable을 사용하면 목록 조회를 할 때 밸류 테이블까지 조인해서 데이터를 읽어오는데, 원하는 결과는 아니다.

조회 전용기능을 이용하는데, 5장에서 조회 전용 쿼리를 실행하는 방법에 대해서 알아본다.

 

밸류지만 @Entity를 사용해야할 때도 있다.

상속구조를 갖는 밸류 타입을 사용하려면 @Embeddable 대산 @Entity를 이용해서 상속 매핑으로 처리

OneToMany 매핑에서 컬렉션의 clear() 메서드를 호출하면 삭제 과정이 효율적이지 않다.

변경 빈도가 낮으면 괜찮지만 높으면 전체 서비스 성능에 문제가 될 수 있다.

Embeddable 타입에 대한 clear는 한 번의 delete 쿼리로 삭제 처리를 수행한다. 그러면 다형성을 포기하고 if-else 구문으로 구현해야한다.

코드 유지 보수와 성능의 두 가지 픅면을 고려해서 구현 방식을 선택해야 한다.

 

에그리거트 로딩 전략

애그리거트 루트를 로딩하면 루트에 속한 모든 객체가 완전한 상태여야 함을 의미한다.

즉시 로딩으로 설정하면 애그리거트는 완전한 상태가 된다.

이것이 항상 좋은 것은 아니다.

즉시 로딩은 카시티안조인을 사용하고 중복이 발생하는데, 하이버네이트가 중복된 데이터를 알맞게 제거해서 실제 메모리에는 중복이 제거된 내용이 들어간다.

 

일반적으로 상태 변경 기능을 실행하는 빈도보다 조회 기능을 실행하는 빈도가 훨씬 높다.

지연 로딩을 사용할 때 발생하는 추가 쿼리로 인한 실행 속도 저하는 보통 문제가 되지 않는다.

애그리거트 내의 모든 연관을 즉시 로딩으로 설정할 필요는 없다.

애그리거크에 맞게 즉시 로딩과 지연로딩을 선택해야 한다.

 

애그리거트의 영속성 전파

@Embeddable 매핑 타입은 cascade 속성을 추가로 설정하지 않아도 된다.

@Entity 타입에 대한 매핑은 cascade 속성을 설정해야 한다.

 

식별자 생성 기능

  • 사용자가 직접 생성
  • 도메인 로직으로 생성
  • DB를 이용한 일련번호 사용

도메인 구현과 DIP

이 장에서 구현된 리포지터리는 DIP 원칙을 어기고 있다.

 

구현 기술에 대한 의존 없이 도메인을 순수하게 유지하려면 스프링 데이터 JPA 의 Repository인터페이스를 상속받지 않도록 수정하고, 인터페이스를 구현한 클래스를 인프라에 위치시켜야 한다.

 

변경이 거의 없는 상황에서 변경을 미리 대비하는 것은 과하다고 생각한다.

DIP를 완벽하게 지키면 좋겠지만 개발 편의성과 실용성을 가져가면서 구조적인 유연함은 어느 정도 유지했다.

Comments