이번 프로젝트를 하다가
게시판 - 댓글을 왜 연관관계를 하면안되는지
이것보다 더 연관관계가 깊은게 뭐가 있을까 하면서 간접 참조라는 개념을 알게되었다
게시판은 댓글이 없어도 된다
하지만 댓글은 게시판이 있어야 한다.
하지만 이것 외에 둘의 관계는 없다. 댓글이 게시판 생성 수정 삭제에 관여할 것은 아니지 않은가
이렇다면 간접 참조를 사용하여야 한다.
도메인 주도 설계를 공부하다 보면 아래의 예시 이미지 처럼 도메인을 기준으로에그리거트 단위로 그룹핑을 하게 된다.
에그리거트 : 관련된 도메인의 집합
DDD 에서는 에그리거트간에 참조 방식은 직접 참조 방식 보다 간접 참조하는 방식을 권장한다.
위의 이미지는 회원과 빵집의 관계 (회원이 빵집을 등록하는 관계) 를 나타낸 것 이다.
1. 직접 참조
JPA 에서 직접 참조는 Entity 클래스를 설계할 때 @OneToOne, @OneToMany ... 와 같은 어노테이션을 써서 Entity 간에 연관 매핑하는 것이다.
다음은, 빵집이 회원을 직접 참조하는 엔티티 클래스 이다.
@Entity
@Getter
@NoArgsConstructor
@ToString
public class Bakery {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "bakery_id")
private Long id;
// 빵집 명
private String title;
// 빵집 주소
@Enumerated
private Address address;
// 최초 등록자
@OneToOne
private User user;
@Builder
public Bakery(Long id, String title, Address address, User user) {
this.id = id;
this.title = title;
this.address = address;
this.user = user;
}
}
Bakery class 안에는 user 라는 변수가 있다. 현재 User 가 어떤 엔티티 인지는 자세하게는 모르지만,
위와 같은 에그리거트 간에 직접 참조의 방식은 빵집 에그리거트를 다룰 때, 회원 에그리거트를 변경할 수 있는 가능성이 있고, 이는 실수를 의미한다.
먼저 "빵집 에그리거트를 다룰 때 회원 에그리거트를 변경해서는 안되는 건가?" 라는 의문이 생길 수도 있다.
그렇다면 회원 에그리거트의 상태가 변경되는 곳이 회원 에그리거트 외부에서 일어나는 것일까?
@Transactional
public Long save(requestDto dto) {
final String address = dto.getAddress();
final String title = dto.getTitle();
final User user = userRepository.findById(dto.userid);
final Bakery bakery = Bakery.builder()
.title(title)
.address(address)
.user(user)
.build();
return bakeryRepository.save(bakery).getId();
}
즉 User 라는 Entity 자체가 날것으로 가져오게 되면, Entity 가 오염이 될 수 도 있다.
setter , 도메인 서비스를 통해서든 어떤 일이 벌어질 수 있는 가능성을 열어둔 것이다.
아래 코드 처럼 save 메소드 안에서 더티체킹(변경사항 탐지)으로 인해 유저가 갑자기 관리자가 되고,
닉네임이 끝빵왕이 될 수도 있다.
@Transactional
public Long save(requestDto dto) {
...
final User user = userRepository.findById(dto.userid);
user.setRole(ROLE.ADMIN); // 관리자로 변경 !
user.updateNickName("끝빵왕"); // 닉네임이 변경 !
final Bakery bakery = Bakery.builder()
.title(title)
.address(address)
.user(user)
.build();
return bakeryRepository.save(bakery).getId();
}
따라서 User 에그리거트도 안전하게 보호가 되고, Bakery 에그리거트에만 집중할 수 있는 방법이 필요하고
그 방법이 바로 간접 참조를 이용하는 것이다.
결론 부터 이야기 해보자면,
다른 에그리거트와 관계를 맺거나 이용하기 위해선 ResponseDto 를 반환하는 application layer 에 있는 서비스 객체를 통해야 하며, 날것의 Entity 클래스 그대로가 아닌 ! ResponseDto 에 있는 다른 루트 에그리거트의 아이디를 참조하는 방식인 간접 참조 방식으로 관계를 설정해야 한다.
2. 간접 참조
간접 참조는 외래키를 맺지 않고 다른 루트 에그리거트의 식별값을 참조하는 방식이다.
다음은, 빵집이 회원을 간접 참조하는 엔티티 클래스 이다.
@Entity
@Getter
@NoArgsConstructor
@ToString
public class Bakery {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "bakery_id")
private Long id;
// 빵집 명
private String title;
// 빵집 주소
@Enumerated
private Address address;
// 루트 에그리거트의 아이디를 참조하는 방식
// 최초 등록자
private Long user;
@Builder
public Bakery(Long id, String title, Address address, UserId user) {
this.id = id;
this.title = title;
this.address = address;
this.user = user.getValue();
}
}
@Embeddable
@Getter
@NoArgsConstructor
public class UserId {
private long value;
public UserId(long value) {
this.value = value;
}
}
@Transactional
public Long save(requestDto dto) {
final String address = dto.getAddress();
final String title = dto.getTitle();
// application layer 에 있는 서비스 객체를 이용
final UserResponseDto user = userService.findById(dto.userid);
final Bakery bakery = Bakery.builder()
.title(title)
.address(address)
.user(user.getUserId())
.build();
return bakeryRepository.save(bakery).getId();
}
기존에 있었던 @OneToOne 어노테이션과 User 타입을 지우고, Long 타입으로 변경함으로써 User의 식별값만 받도록 수정했다.
또한 userid 를 Long 타입으로 매개변수를 받지 않고 UserId 를 매개변수로 받음으로써, 다른 이상한 Long 값을 대입하는 실수를 방지 할 수 있었다.
이렇게 간접 참조로 에그리거트 간에 관계를 맺으면 User 에그리거트도 안전하게 보호가 되고, Bakery 에그리거트에만 집중할 수 있다.
하지만 간접참조에 대한 문제점이 있는데, 그것은 N+1 Query 이다.
만약에 Bakery 리스트를 뿌려주는데 거기에 User 정보도 같이 보여줘야 한다면, Bakery 마다 User 정보를 호출하는 Query 를 사용하게 된다.
이와 같은 문제점은 CQRS 패턴을 사용하여 명령, 조회 를 나누어서 관리하도록 하자.
'일-상 > 오류노트' 카테고리의 다른 글
[오류노트] log4j.properties 파일을 못찾을때 (0) | 2023.06.07 |
---|---|
[오류노트] Redis에 객체 저장 하기 (0) | 2023.04.05 |
[오류노트] If you want an embedded database (H2, HSQL or Derby), please put it on the classpath. (0) | 2022.12.14 |
[오류노트] Scanner InputMismatchException 에러 (1) | 2022.11.29 |
[오류노트]mongodb 데이터 삭제 및 데이터 검색실패 (0) | 2022.11.02 |