Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor/#634: Bookmark 동시성 이슈 해결 및 이벤트 발행 #640

Open
wants to merge 2 commits into
base: refactor/dependency
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,11 @@
import com.mapbefine.mapbefine.bookmark.exception.BookmarkException.BookmarkBadRequestException;
import com.mapbefine.mapbefine.bookmark.exception.BookmarkException.BookmarkConflictException;
import com.mapbefine.mapbefine.bookmark.exception.BookmarkException.BookmarkForbiddenException;
import com.mapbefine.mapbefine.topic.domain.BookmarkAdditionEvent;
import com.mapbefine.mapbefine.topic.domain.BookmarkDeleteEvent;
import com.mapbefine.mapbefine.topic.domain.Topic;
import com.mapbefine.mapbefine.topic.domain.TopicRepository;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

Expand All @@ -22,12 +25,16 @@ public class BookmarkCommandService {

private final TopicRepository topicRepository;

private final ApplicationEventPublisher eventPublisher;

public BookmarkCommandService(
BookmarkRepository bookmarkRepository,
TopicRepository topicRepository
TopicRepository topicRepository,
ApplicationEventPublisher eventPublisher
) {
this.bookmarkRepository = bookmarkRepository;
this.topicRepository = topicRepository;
this.eventPublisher = eventPublisher;
}

public void addTopicInBookmark(AuthMember authMember, Long topicId) {
Expand All @@ -37,7 +44,7 @@ public void addTopicInBookmark(AuthMember authMember, Long topicId) {

Bookmark bookmark = Bookmark.of(topic, authMember.getMemberId());
bookmarkRepository.save(bookmark);
topic.increaseBookmarkCount();
eventPublisher.publishEvent(new BookmarkAdditionEvent(topicId));
}

private void validateBookmarkDuplication(AuthMember authMember, Long topicId) {
Expand All @@ -63,14 +70,12 @@ private void validateBookmarkingPermission(AuthMember authMember, Topic topic) {
throw new BookmarkForbiddenException(FORBIDDEN_TOPIC_ADD);
}

// TODO: 2023/12/03 BookmarkCount의 정합성을 어떻게 맞출 것인가 ? 매번 topic 조회하는 것은 불필요한 행위같아보임
public void deleteTopicInBookmark(AuthMember authMember, Long topicId) {
validateBookmarkDeletingPermission(authMember, topicId);
BookmarkId bookmarkId = BookmarkId.of(topicId, authMember.getMemberId());

bookmarkRepository.deleteById(bookmarkId);
Topic topic = getTopicById(topicId);
topic.decreaseBookmarkCount();
eventPublisher.publishEvent(new BookmarkDeleteEvent(topicId));
}

private void validateBookmarkDeletingPermission(AuthMember authMember, Long topicId) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,16 @@
import com.mapbefine.mapbefine.pin.domain.PinRepository;
import com.mapbefine.mapbefine.pin.exception.PinException.PinBadRequestException;
import com.mapbefine.mapbefine.pin.exception.PinException.PinForbiddenException;
import com.mapbefine.mapbefine.topic.domain.BookmarkAdditionEvent;
import com.mapbefine.mapbefine.topic.domain.BookmarkDeleteEvent;
import com.mapbefine.mapbefine.topic.domain.Topic;
import com.mapbefine.mapbefine.topic.domain.TopicRepository;
import com.mapbefine.mapbefine.topic.dto.request.TopicCreateRequest;
import com.mapbefine.mapbefine.topic.dto.request.TopicMergeRequest;
import com.mapbefine.mapbefine.topic.dto.request.TopicUpdateRequest;
import com.mapbefine.mapbefine.topic.exception.TopicException.TopicBadRequestException;
import com.mapbefine.mapbefine.topic.exception.TopicException.TopicForbiddenException;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package com.mapbefine.mapbefine.topic.domain;

public record BookmarkAdditionEvent(Long topicId) {
}
Comment on lines +1 to +4
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

기술 스택으로 Java17을 선택한 만큼, 레코드를 사용했는데요.
이를 도메인 패키지에 두었는데 너무 DTO 같아 보이려나요 ?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

아니요!

보니까 PinUpdateEvent 도 Record 네용 ㅎㅎ

오히려 일관성을 위해서 더욱이 Record 로 두어야 할 것 같습니다.

Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package com.mapbefine.mapbefine.topic.domain;

public record BookmarkDeleteEvent(Long topicId) {
}
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,6 @@ public class Topic extends BaseTimeEntity {
@ColumnDefault(value = "0")
private int pinCount = 0;

// TODO: 2023/12/03 간접 참조에 따른 bookmark Count 정합성을 어떻게 맞출 것인가 ?
@Column(nullable = false)
@ColumnDefault(value = "0")
private int bookmarkCount = 0;
Expand Down Expand Up @@ -145,12 +144,4 @@ public void decreasePinCount() {
pinCount--;
}

public void increaseBookmarkCount() {
bookmarkCount++;
}

public void decreaseBookmarkCount() {
bookmarkCount--;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,15 @@ public interface TopicRepository extends JpaRepository<Topic, Long> {
@EntityGraph(attributePaths = {"creator"})
List<Topic> findAllByCreatorId(Long creatorId);

@Modifying(clearAutomatically = true)
@Query("update Topic t set t.bookmarkCount = t.bookmarkCount - 1 where t.id = :topicId")
void decreaseBookmarkCountById(@Param("topicId") Long topicId);


@Modifying(clearAutomatically = true, flushAutomatically = true)
@Query("update Topic t set t.bookmarkCount = t.bookmarkCount + 1 where t.id = :topicId")
void increaseBookmarkCountById(@Param("topicId") Long topicId);
Comment on lines +36 to +38
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

왜 이 녀석만 flushAutomatically 옵션을 두었는지 궁금해하실 것 같아요 ㅎㅎ

이거 진짜 팀 블로그에 쓸건데... 최대한 간략하게 설명드리자면 다음과 같아요.
이전에 discussion에서, JPQL로 작성된 메서드를 호출하면 영속성 컨텍스트가 flush된다고 말씀드렸는데요.
이때 flush되는 대상들이 해당 JPQL을 수행하는 객체와 관련된 내용만 flush가 된다고 합니다.
그래서, Bookmark 저장 -> 이벤트 발행 -> Topic JPQL 수행의 과정으로 로직이 수행된다면..
clearAutomatically 옵션에 의해서 Bookmark 저장과 관련하여 쓰기 지연 저장소에 대기중인 쿼리가 삭제되버리게 되는 것이죠..
그래서 위 옵션을 통해 영속성 컨텍스트에 있는 모든 것(?)들을 flush 해주는 옵션이 생겨나게 되었답니다 !

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Topic JPQL 이 수행될 때, 자기 것만 flush 하고, 또한 clearAutomatically 옵션으로 인해서, 영속성 컨텍스트가 비워져 Bookmark 저장 쿼리가 사라진다는 의미인가요?? 제가 이해한 게 맞나요?


@Modifying(clearAutomatically = true)
@Query("update Topic t set t.isDeleted = true where t.id = :topicId")
void deleteById(@Param("topicId") Long topicId);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package com.mapbefine.mapbefine.topic.listener;

import com.mapbefine.mapbefine.topic.domain.BookmarkAdditionEvent;
import com.mapbefine.mapbefine.topic.domain.BookmarkDeleteEvent;
import com.mapbefine.mapbefine.topic.domain.TopicRepository;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;

@Component
public class TopicEventListener {

private final TopicRepository topicRepository;

public TopicEventListener(TopicRepository topicRepository) {
this.topicRepository = topicRepository;
}

@EventListener
public void removeBookmark(BookmarkDeleteEvent event) {
topicRepository.decreaseBookmarkCountById(event.topicId());
}

@EventListener
public void addBookmark(BookmarkAdditionEvent event) {
topicRepository.increaseBookmarkCountById(event.topicId());
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -541,8 +541,6 @@ void findAllBestTopics_Success1() {
Topic topicWithNoBookmark = TopicFixture.createPublicAndAllMembersTopic(member);
Topic topicWithOneBookmark = TopicFixture.createPublicAndAllMembersTopic(member);
Topic topicWithTwoBookmark = TopicFixture.createPublicAndAllMembersTopic(member);
increaseBookmarkCount(topicWithOneBookmark, 1);
increaseBookmarkCount(topicWithTwoBookmark, 2);

topicRepository.save(topicWithNoBookmark);
topicRepository.save(topicWithOneBookmark);
Expand Down Expand Up @@ -654,13 +652,10 @@ void findClusteringPinsByIds_fail() {
}

private Bookmark saveBookmark(Topic topic, Member member) {
return bookmarkRepository.save(Bookmark.of(topic, member.getId()));
}
Bookmark bookmark = bookmarkRepository.save(Bookmark.of(topic, member.getId()));
topicRepository.increaseBookmarkCountById(topic.getId());

private void increaseBookmarkCount(Topic topic, int count) {
for (int i = 0; i < count; i++) {
topic.increaseBookmarkCount();
}
return bookmark;
}

}