1. 어그리게이트란?
어그리게이트는 일관성을 유지하는 단위.
일관성에는 트랜젝션의 일관성과 결과론적 일관성이 있다.
어그리게이트를 통해서 내부의 엔티티끼리는 트랙젝션 단위의 일관성을 유지.
어그리게이트끼리는 결과론적 일관성을 유지하는 것.
즉 응집도를 다스리는 방법이다.
2. 어그리게이트가 왜 필요해?
어그리게이트가 너무 크면,
관리해야하는 엔티티가 많아지기 때문에,
동시성 이슈로, transaction rollback이 자주 발생하기 된다.
(optimistic transaction vs passimistic transaction)
따라서 적절한 크기로 어그리게이트를 유지해야할 필요가 있는데,
보통 엔티티끼리의 생명주기가 비슷하면 함께 묶는다.
3. 잘못된 어그리게이트 결과
그래서 기존에 짰던 게시글 도메인에서, 회원도메인과 메뉴 도메인을 분리하기로 했다.
하지만 문제는 엔티티를 참조하지 않자니, 게시글 엔티티는 어떤 식으로든 회원과 메뉴에 관한 정보를 갖고 있어야만 한다.
따라서 엔티티를 직접 참조하는 방식보다는, 다른 어그리게이트 루트의 id 만을 참조하도록 하는 것이다.
4. 어그리게이트 수정 사례
@Entity
@Table(name = "normal_board")
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@EntityListeners(AuditingEntityListener.class)
@Inheritance(strategy = InheritanceType.JOINED)
@DiscriminatorColumn(name = "TYPE", length = 15)
public class NormalBoard extends BaseEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
protected Integer id;
@Embedded
protected Title title;
@Embedded
protected Contents contents;
/*
@ManyToOne(fetch = FetchType.LAZY, optional = false)
@JoinColumn(name = "menu_id", foreignKey = @ForeignKey(name = "fk_board_to_menu"))
protected Menu menu;
@ManyToOne(fetch = FetchType.LAZY, optional = false)
@JoinColumn(name = "writer_id", foreignKey = @ForeignKey(name = "fk_board_to_user"))
protected Member writer;
*/
@Embedded
@AttributeOverride(name = "id", column = @Column(name = "menu_id"))
protected MenuId menuId; /* 수정된 부분 */
@Embedded
@AttributeOverride(name = "id", column = @Column(name = "writer_id"))
protected MemberId writerId; /* 수정된 부분 */
@OneToMany(mappedBy = "parentBoard", cascade = CascadeType.ALL, orphanRemoval = true)
protected List<Comment> comments = new ArrayList<>();
/* ... 생략 ... */
}
@Service
@Slf4j
@Transactional
@RequiredArgsConstructor
public class BoardServiceImpl implements BoardService {
private final NormalBoardRepository boardRepository;
//private final MenuRepository menuRepository;
//private final MemberRepository memberRepository;
@Override
public Integer create(MemberId memberId, SaveBoardDto saveBoardDto) {
//Menu menu = menuRepository.getById(saveBoardDto.getMenuId());
//Member writer = memberRepository.getById(memberId);
NormalBoard normalBoard = new NormalBoard(saveBoardDto.getTitle(), saveBoardDto.getContents())
.inMenu(saveBoardDto.getMenuId())
.writtenBy(memberId);
return boardRepository.save(normalBoard).getId();
}
@Override
public Integer update(MemberId memberId, UpdateBoardDto updateBoardDto) {
//Member writer = memberRepository.getById(memberId);
NormalBoard savedBoard = boardRepository.findById(updateBoardDto.getId())
.orElseThrow(BoardNotFoundException::new);
NormalBoard updatedBoard = new NormalBoard(updateBoardDto.getId(), updateBoardDto.getTitle(), updateBoardDto.getContents())
.writtenBy(memberId)
.inMenu(savedBoard.getMenuId());
return boardRepository.save(updatedBoard).getId();
}
/* ... 생략 ... */
}
5. 루트 ID 를 값객체로 변환할 때의 유의사항
jpa entity의 ID를 값객체로 wrapping 해야한다.
복합키를 사용하는 것과 같은 원리.
JPA auto increment 필드로는 불가능.
엔티티 내부에서는 primitive 값 사용하되, 외부에서는 wrapper type 사용하도록 약속.
'웹 프로젝트 (IBAS) > SpringBoot api 개편' 카테고리의 다른 글
[SpringBoot] 8. 하이버네이트 원격서버 암호화 연결 (SSH tunneling 설정) (4) | 2022.07.30 |
---|---|
[SpringBoot] 7. SpringSecurity 인증 모듈 개발 (OAuth2, jwt, 소셜로그인) (4) | 2022.07.02 |
OAuth2 naver 회원 id 형식 문제 (네아로) (0) | 2022.05.14 |
[RFC 표준] OAuth 2.0를 쉽고 정확하게 알아보자! (기초 개념 및 용어 정리) (1) | 2022.05.08 |
[Spring Boot] 4. 로컬 개발을 위한 CORS 설정 - (2) Spring MVC 와 Spring Security (0) | 2022.04.15 |