Hyun's Wonderwall

[EFUB 4기 BE Lead] 도메인 주도 개발 시작하기 - 4. 리포지토리와 모델 구현 본문

Study/Java, Spring

[EFUB 4기 BE Lead] 도메인 주도 개발 시작하기 - 4. 리포지토리와 모델 구현

Hyun_! 2024. 3. 18. 17:17

EFUB 4기 BackEnd Lead_ 도메인 주도 개발 스터디

  • 스터디 커리큘럼: 최범균, "도메인 주도 개발 시작하기: DDD 핵심 개념 정리부터 구현까지"
  • 2주차 과제: Chapter 3. 애그리거트, Chapter 4. 리포지토리와 모델 구현

Chapter 4. 리포지토리와 모델 구현

  • Keywords: 키워드

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

자바의 ORM 표준인 JPA를 이용해서 리포지터리와 애그리거트를 구현하는 방법에 대해 살펴보자.

JPA: 도메인 모델과 리포지터리를 구현할 때 사용

*ORM(Object Relational Mapping): 객체 관계 매핑

4.1.1 모듈 위치

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

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

4.1.2 리포지터리 기본 기능 구현

리포지터리 기본 기능 두 가지: (1) ID로 애그리거트 조회 (2) 애그리거트 저장

- 인터페이스는 애그리거트 루트를 기준으로 작성한다. OrderRepository

- 애그리거트 조회 메서드 명명법: 'findBy프로퍼티이름(프로퍼티값)'

- Order 자료형 메서드의 경우 값이 존재하면 Order를 리턴, 존재하지 않으면 null을 리턴한다.
(null을 사용하고 싶지 않다면 Optional을 사용할 수 있다. / 의의: NPE 막아줌, 값 존재하는지는 .empty()로 확인)

 

이 인터페이스를 구현한 클래스는 

JPA의 EntityManager을 이용해서 기능을 구현한다.JpaOrderRepository 구현 클래스 코드

 

JPA를 사용하면 트랜잭션 범위에서 변경한 데이터를 자동으로 DB에 반영한다.메서드 실행이 끝나면 트랜잭션을 커밋하는데 이때 JPA는 트랜잭션 범위에서 변경된 객체의 데이터를 DB에 반영하기 위해 UPDATE 쿼리를 실행한다. - findBy 뒤에 조건 대상이 되는 프로퍼티 이름을 붙이면 다른 걸 찾을수 있다.

 

 

한 개 이상의 객체를 리턴할 수 있는 경우 메서드 리턴 타입을 List로 사용한다.ID 외에 다른 조건으로 애그리거트를 조회하기 위해 Criteria나 JPQL을 사용할 수 있다.애그리거트를 삭제하는 기능은 delete구현 클래스는 entityManager의 remove 메서드를 이용해서 삭제 기능을 구현한다.- 삭제 기능... 바로 삭제하기보다 삭제 플래그를 사용해서 데이터를 화면에 보여줄지 여부를 결정하는 방식으로 구현하기도

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

스프링 + JPA 적용 시 스프링 데이터 JPA를 사용한다.

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

리포지터리 인터페이스를 직접 구현하지 않아도 되기 떄문에 리포지터리를 쉽게 정의할 수 있다.

 

스프링 데이터 JPA는 다음 규칙에 따라 작성한 인터페이스를 찾아서 인터페이스를 구현한 스프링 빈 객체를 자동으로 등록한다.

  • org.springframework.data.repository.Repository<T, ID> 인터페이스 상속
  • T: 데이터 타입, ID: 식별자 타입을 지정

메서드 설명

4.3 매핑 구현

4.3.1 엔티티와 밸류 기본 매핑 구현

애그리거트 루트는 엔티티이므로 @Entity

한 테이블에 엔티티와 밸류 데이터가 같이 있다면밸류 Embeddable로 매필 설정밸류 타입 프로퍼티 Embedded로 매핑 설정...

4.3.2 기본 생성자

JPA에서 @Entity와 @Embeddable로 클래스를 매핑하려면 기본 생성자를 제공해야 한다.

 

4.3.3 필드 접근 방식 사용

JPA는 필드와 메서드 두 가지 방식으로 매핑을 처리할 수 있다. 메서드 방식을 사용하려면 프로퍼티를 위한 get/set 메서드를 구현해야 한다. 그러나 이렇게 되면 캡슐화 깨는 등의 문제...set 메서드 대신 의도가 잘 드러나는 기능을 제공해야 한다...불필요한 get/set 메서드 구현하지 말아야 한다.

4.3.4 AttributeConverter를 이용한 밸류 매핑 처리

int, long, String, LocalDate와 같은 타입 DB 테이블의 한 개 칼럼에 매핑된다. 이와 비슷하게 밸류 타입의 프로퍼티를 한 개 칼럼에 매핑해야 할 때도 있다.

4.3.5 밸류 컬렉션: 별도 테이블 매핑

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

List 타입 자체가 인덱스를 가지고 있다.

4.3.6 밸류 컬렉션: 한 개 칼럼 매핑

AttributeConverter를 사용. 단 밸류 컬렉션을 표현하는 새로운 밸류 타입을 추가해야 한다.

4.3.7 밸류를 이용한 ID 매핑

식별자 자체를 밸류 타입으로 만들 수도 있다.

@Id 대신 @EmbeddedId 애너테이션 사용

- 밸류 타입으로 식별자를 구현할 때 장점: 식별자에 기능을 추가할 수 있다.

- 식별자로 사용할 밸류 타입은 Serializable 인터페이스를 상속받아야 한다. 

- JPA는 내부적으로 엔티티를 비교할 목적으로 equals 메서드와 hashcode() 값을 사용하므로 식별자로 사용할 밸류 타입은 이 두 메서드를 알맞게 구현해야 한다.

4.3.8 별도 테이블에 저장하는 밸류 매핑

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

매핑되는 테이블에 식별자가 있다고 엔티티인건 아니니 주의. 다른 테이블의 데이터와 연결하기 위해서 사용하는 식별자일수도.

4.3.9 밸류 컬렉션을 @Entity로 매핑하기

개념적으로 밸류인데 @Entity를 사용해야 하는 상황

- 상속 구조를 갖는 밸류 타입을 사용하려면: @Entity를 이용해 상속 매핑으로 처리해야 한다.

- 밸류 타입을 @Entity로 매핑 시 식별자 매핑을 위한 필드도 추가해야 한다. 또한 구현 클래스를 구현하기 위한 타입 식별 칼럼을 추가해야 한다.

- 밸류이므로 상태 변경 기능은 추가 x

상속받은 클래스는 @Entity와 @Discriminator을 사용해서 매핑을 설정한다.

 

하이버네이트의 방법

한 번의 delet 쿼리로 삭제 처리 수행

4.3.10 ID 참조와 조인 테이블을 이용한 단방향 M-N 매핑

애그리거트 간 집합 연관을 사용해야 하는 경우

- ID 참조를 이용한 단방향 M-N 집합 연관

- 밸류 컬렉션 매핑과 동일한 방식. 차이점: 집합의 값에 밸류 대신 연관을 맺는 식별자가 온다

@ElementCollection을 이용

- Product를 삭제할 때 매핑에 사용한 조인 테이블의 데이터도 함께 삭제됨

4.4 애그리거트 로딩 전략

애그리거트 루트 로딩 시 루트에 속한 모든 객체가 완전한 상태여야 함

조회 시점에서

- 연관 매핑 조회 방식을 즉시 로딩(FetchType.EAGER)으로 설정

 

컬렉션이나 @Entity에 대한 매핑의 fetch 속성을 즉시 로딩(FetchType.EAGER)으로 설정시 EntityManager#inf() 메서드로 애그리거트 루트를 구할 때 연관된 구성요소를 DB에서 하께 읽어온다.

 

즉시 로딩 방식의 단점:  쿼리 결과에 중복을 발생시킨다.

지연 로딩도 사용 가능.

애그리거트에 맞게 사용하자.

4.5 애그리거트의 영속성 전파

애그리거트 루트를 조회할 때 뿐만 아니라 저장하고 삭제할 때도 하나로 처리해야 한다.

cascade 속성.

@Entity 타입에 대한 매핑은 cascade 속성을 사용해서 저장과 삭제 시에 함꼐 처리되도록 설정해야 한다.

@OneToOne, @OneToMany: 기본값 x. cascade 속성값으로 CascadeType.PERSIST, CascadeType.REMOVE를 설정한다.

4.6 식별자 생성 기능

식별자는 크게 세 가지 방식 중 하나로 생성한다.

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

*자동 증가 칼럼

4.7 도메인 구현과 DIP

사실 이 장에서 구현한 리포지터리는 DIP 원칙을 어기고 있다. 도메인 모델이 jpa에 의존하고 있다.

수정하는 방법 알려줌

그러나 JPA로 구현한 것 변경 계획 없는 상황이기에 타협을 했다. 리포지터리 테스트 가능성을 해지지 않는다.

합리적인 선택.