-
도메인 주도 개발 시작하기 CH.4 ~ CH.5DESIGN PATTERN & ARCHITECTURE 2024. 6. 19. 23:53
Ch. 4 : 레포지토리와 모델 구현
4.1.
- 레포지터리 인터페이스
- 애그리거트와 같이 도메인 영역에 속함
- 인터페이스는 애그리거트 루트를 기준으로 작성해야 함
- 레포지터리 구현 클래스
- 인프라스트럭처 영역에 속함
- 인프라스트럭처에 대한 의존을 낮춰야 함
- 레포지터리의 기본 기능
- ID로 애그리거트 조회
- ID외의 다른 조건으로 애그리거트를 조회할 때에는 JPA의 Criteria나 JPQL을 사용할 수 있음
- 애그리거트 저장
- JPA를 사용한다면 트랜잭션 범위에서 변경한 데이터는 자동 저장되므로 저장하는 메서드를 추가할 필요가 없음
- 삭제 기능의 경우 삭제 요구사항이 있더라도 데이터 조회가 필요한 경우나 원복해야하는 경우도 있기 때문에 데이터를 실제로 삭제하는 경우가 많지 않음
- ID로 애그리거트 조회
4.2
- 스프링 데이터 JPA
- 레포지터리 인터페이스를 정의하면 레포지터리 구현 객체를 알아서 만들어 스프링 빈으로 등록해줌
- 지정한 규칙에 맞게 메서드를 작성해야 함
4.3
- 애그리거트 루트는 엔티티 이므로 @Entity로 매핑 설정
- 밸류는 @Embddable 로 매핑 설정
- 밸류 타입 프로퍼티는 @Embedded로 매핑 설정
- @Embeddable과 @Entity로 매핑하려면 기본 생성자를 제공해야 함
- 기본 생성자는 JPA 프로바이더가 객체를 생성할 때만 사용할 수 있게 protected로 선언
- 필드 접근 방식
- JPA는 필드와 메서드 두가지 방식으로 매핑 처리 가능
- 메서드 방식
- 프로퍼티를 위한 get/set 메서드 구현 필요
- 도메인의 의도가 사라지고 객체가 아닌 데이터 기반으로 엔테테 구현의 가능성이 높아짐
- 밸류를 불변타입으로 구현하려면 set 메서드 자체가 필요없는데, JPA 구현 방식 때문에 공개 set 메서드를 추가하게 됨
- => 객체가 제공할 기능 중심으로 엔티티를 구현하게끔 유도하기 위해 JPA 매핑 처리를 프로퍼티 방식이 아닌 필드 방식으로 선택해서 불필요한 get/set 메서드를 구현하지 말아야 함
- 프로퍼티를 위한 get/set 메서드 구현 필요
- 메서드 방식
- JPA 구현체인 하이버네이트는 @Access를 통해서 명시적으로 접근방식을 지정하지 않으면, @Id나 @EmbeddedId가 어디에 위치했냐에 따라 접근 방식을 선택함
- @Id나 @EmbeddedId가 필드에 위치하면 필드 접근 방식, get 에 위치하면 메서드 접근 방식
- JPA는 필드와 메서드 두가지 방식으로 매핑 처리 가능
- AtributeConverter
- 두개 이상의 프로퍼리를 가진 밸류 타입을 한개 컬럼에 매핑
- @Converter의 authApply 속성의 기본값은 false
- false이면 프로퍼티 값을 변환할 때 사용할 컨버터를 직접 지정해야 함
- @ElementCollection, @CollectionTable
- 밸류컬렉션을 저장하는 별도 테이블을, 컬렉션이 속할 엔티티 외부 키를 이용해서 엔티티에 해당하는 테이블을 참조하도록 함
- 리스트 타입 자체가 인덱스를 가지고 잊기 때문에, 인덱스를 저장하기 위한 프로퍼티가 없음
- @OrderColumn 애노테이션을 이용해서 지정한 칼럼에 리스트의 인덱스 값 저장
- @CollectionTable
- 밸류를 저장할 테이블 지정
- 밸류컬렉션이 별도 테이블이 아닌 한개 컬럼에 저장하는 경우 AtrribudeConverter 사용
- 밸류 타입의 식별자
- 밸류타입을 식별자로 매핑하면 @Id 대신 @EmbeddedId 애노테이션을 사용
- 식별자는 Serializable 타입이어야 하므로 밸류 타입은 Serializable 인터페이스를 상속 받아야 함
- 밸류타입으로 식별자를 구현하면 식별자에 기능추가가 가능해짐
- JPA는 내부적으로 엔티티 비교 목적의 equals(), hashcode() 사용하므로 식별자로 사용할 때 밸류 타입은 이 메서드를 알맞게 구현해야 함
- 루트 엔티티 외에 또다른 엔티티가 한 애그리거트에 있다면, 진짜 엔티티 인지, 다른 애그리거트는 아닌지 의심해야함
- 밸류와 엔티티의 구분 방법 = 식별자
- 매핑되는 테이블의 식별자 != 애그리거트 구성요소의 식별자
- 데이터를 연결하기 위한 식별자와 구분해야 함
- 밸류와 엔티티의 구분 방법 = 식별자
- 밸류를 별도의 테이블에 저장하고 매핑한 테이블을 지정하기 위해 @SecondaryTable과 @AttributeOverride를 사용할 수 있음
- @SecondaryTable을 이용하면 두 테이블을 조인해서 데이터를 조회하게 됨을 유의
- 밸류 컬렉션의 Entity 매핑 (계층 구조의 밸류 타입)
- @Embeddable 타입의 클래스 상속 매핑을 지원하지 않음
- @Entity를 이용해서 상속 매핑으로 처리해야 함
- 한 테이블에 하위 클래스 매핑
- 상위 테이블
- @Inheritance (InheritanceType.strategy = SINGLE_TABLE)
- @DiscriminatorColumn 애너테이션으로 타입 구분용 칼럼 지정
- 하위 테이블
- @DiscriminatorValue로 매핑 설정
- 상위 테이블
- 한 테이블에 하위 클래스 매핑
- @Entity를 이용해서 상속 매핑으로 처리해야 함
- 하이버 네이트는 @Embeddable타입의 컬렉션의 clear()메서드 호출시 컬렉션에 속한 객체를 로딩하지 않고, 한번에 delete 쿼리로 삭제 처리 수행
- 애그리거트 특성을 유지하면서 문제해소를 위해 상속을 포기하고 embeddable 매핑 단일 클래스가 낫다
- @Embeddable 타입의 클래스 상속 매핑을 지원하지 않음
- ID 참조와 조인 테이블을 통한 단방향 M-N 매핑
- @ElementCollection, @CollectionTable 사용
- ElementCollection은 삭제할 때 매핑에 사용한 조인 테이블의 데이터도 함께 삭제
- @ElementCollection, @CollectionTable 사용
4.4
- 애그리거트
- 속한 객체가 모두 모여야 완전한 하나가 됨
- 조회 시점에 완전한 애그리거트 상태를 위해 연관 매핑 조회 방식을 즉시 로딩으로 설정할 수 있음
- 하지만 조회 성능 이슈로 검토가 필요함
- 애그리거트는 개념적으로 하나여야 하지만, 루트 엔티티를 로딩하는 시점에 모두 로딩해야 하는 것은 아님
- 애그리거트에 맞게 즉시 로딩과 지연로딩을 선택
- 속한 객체가 모두 모여야 완전한 하나가 됨
4.5
- 저장 매서드는 애그리거트 루트만 저장하면 안되고 애그리거트에 속한 모든 객체를 저장해야 함
- 삭제 메서드는 애그리거트 루트뿐 아니라 애그리거트에 속한 모든 객체를 삭제해야 함
- @Embeddable 매핑 타입은 함께 저장되고 삭제됨
- 엔티티 매핑의 경우 cascade 속성을 사용
4.6
- 식별자 생성
- 사용자 직접 할당
- 도메인 로직 할당
- 도메인이나 레포지토리에 구현
- DB의 일련번호 사용
- ex) JPA 의 경우 저장 이후에 엔티티 식별자 사용 가능
4.7
- 도메인 구현과 DIP
- 리포지토리와 도메인 모델의 구현 기술은 거의 바뀌지 않으므로 애그리거트, 레포지터리등 도메인 모델 구현시에 타협이 가능함
Ch. 5 : 스프링 데이터 JPA를 이용한 조회 기능
5.1
- CQRS
- 명령 모델(레포지토리)과 조회 모델(DAO)을 분리
- 도메인 모델을 명령모델로 주로 사용됨
- 명령 모델(레포지토리)과 조회 모델(DAO)을 분리
5.2
- 검색 조건 Specification
- agg 파라미터
- 검사 대상이되는 객체
- 레포지토리에 사용시 애그리거트 루트, DAO에 사용시 검색 결과로 리턴할 데이터 객체
- 실제 스펙은 사용하는 기술에 맞춰 구현
- agg 파라미터
5.3
- JPA 정적 메타 모델
- @StaticMetaModel 애너테이션을 이용해서 관련 모델 지정
- 오타등의 문제 발생을 방지
- 대신에, 문자열로 프로퍼티 지정도 가능
- 스펙 구현 클래스를 생성하거나 별도 클래스에 스펙 생성 기능을 모을 수도 있음
5.4
- findAll() 메서드는 스펙 인터페이스를 파라미터로 가짐
5.5
- 스펙 조합
- and
- or
- not
- where
5.6
- 정렬 지정
- 메서드 이름에 OrderBy를 사용해서 정렬 기준 지정
- 메서드에 Sort를 인자로 전달
5.7
- 페이징 처리
- Pageable 타입 사용
- PageRequest 클래스를 이용해 생성
- sort로 정렬순서도 지정 가능
- 프로퍼티를 비교하는 findBy프로퍼티 형식의 메서드는 Pageable 타입을 사용하더라도 리턴 타입이 List면 count 쿼리를 실행하지 않는다.
- => 반환 타입을 Page으로 지정한 경우에 추가 count 쿼리가 발생
- 단, 스펙을 사용하는 findAll 메서드에 Pageable 타입을 사용하면 리턴 타입이 Page가 아니어도 count 쿼리가 실행됨
- 페이징 처리와 관련된 정보가 필요 없다면 리턴타입을 List로 받아 불필요한 count 쿼리를 실행하지 않도록 해야함
- 스펙을 사용하고 페이징 처리를 하면서 count 쿼리는 실행하고 싶지 않다면, 스프링 테이터 JPA가 제공하는 커스텀 레포지토리 기능을 이용해서 직접 구현해야 함
- => 반환 타입을 Page으로 지정한 경우에 추가 count 쿼리가 발생
- PageRequest 클래스를 이용해 생성
- Pageable 타입 사용
5.8
- 스펙 빌더 클래스
- if와 각 스펙을 조합하는 코드에서 실수하지 않도록 방지
5.9
- 동적 인스턴스 생성
- JPQL의 동적 인스턴스 사용
- new 키워드
- 생성할 인스턴스의 완전한 클래스 이름을 지정하고 괄호 안에 생성자에 인자로 전달할 값을 지정
- 이때, 조회 전용 모델을 사용할 수 있음
- JPQL을 그대로 사용하므로 객체 기준으로 쿼리를 작성하면서도 동시에 지연/즉시 로딩과 같은 고민 없이 원하는 모습으로 데이터 조회 가능
- new 키워드
- JPQL의 동적 인스턴스 사용
- 조회 전용 모델
- 표현 영역을 통해 사용자에게 데이터 보여줌
5.10
- 하이버네이트 @Subselect, @Immutable, @Synchronize
- @Subselect
- 쿼리 결과를 @Entity로 매핑 가능
- 조회 쿼리를 값으로 가짐
- 쿼리 실행 결과를 매핑할 테이블 처럼 사용
- 해당 어노테이션을 조회한 @Entity역시 수정할 수 없음
- 수정하려고 하면 예외가 발생
- @Subselect의 값으로 지정한 쿼리를 from 절의 서브 쿼리로 사용
- 이를 사용하고 싶지 않다면 별도의 mapper 구현 필요
- @Immutable
- 하이버네이트가 해당 엔티티의 매핑 필드 / 프로퍼티가 변경되어도 DB에 반영하지 않고 무시
- @Synchronize
- 해당 엔티티와 관련된 테이블 목록을 명시
- 하이버네이트가 엔티티를 로딩하기 전에 지정한 테이블과 관련된 변경이 발생하면 플러시를 먼저 진행
- @Subselect
'DESIGN PATTERN & ARCHITECTURE' 카테고리의 다른 글
도메인 주도 개발 시작하기 CH.8 ~ CH.9 (1) 2024.07.03 도메인 주도 개발 시작하기 CH.6 ~ CH.7 (0) 2024.06.26 도메인 주도 개발 시작하기 CH.1 ~ CH.3 (0) 2024.06.10 도메인 주도 설계 핵심 (0) 2024.03.31 EDA ( Event - Driven -Architecture) (1) 2022.09.29 - 레포지터리 인터페이스