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

DeleteEventListener 내부 로직 비동기 처리 #748

Closed
wants to merge 3 commits into from
Closed
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
2 changes: 1 addition & 1 deletion backend/backend-submodule
76 changes: 18 additions & 58 deletions backend/src/main/java/hanglog/listener/DeleteEventListener.java
Original file line number Diff line number Diff line change
@@ -1,19 +1,9 @@
package hanglog.listener;

import static org.springframework.transaction.annotation.Propagation.REQUIRES_NEW;

import hanglog.expense.domain.repository.ExpenseRepository;
import hanglog.login.domain.repository.RefreshTokenRepository;
import hanglog.member.domain.MemberDeleteEvent;
import hanglog.trip.domain.TripDeleteEvent;
import hanglog.trip.domain.repository.CustomDayLogRepository;
import hanglog.trip.domain.repository.CustomItemRepository;
import hanglog.trip.domain.repository.DayLogRepository;
import hanglog.trip.domain.repository.ImageRepository;
import hanglog.trip.domain.repository.ItemRepository;
import hanglog.trip.domain.repository.PlaceRepository;
import hanglog.trip.domain.repository.TripCityRepository;
import hanglog.trip.domain.repository.TripRepository;
import hanglog.trip.dto.ItemElement;
import java.util.List;
import lombok.RequiredArgsConstructor;
Expand All @@ -28,68 +18,38 @@ public class DeleteEventListener {

private final CustomDayLogRepository customDayLogRepository;
private final CustomItemRepository customItemRepository;
private final PlaceRepository placeRepository;
private final ExpenseRepository expenseRepository;
private final ImageRepository imageRepository;
private final ItemRepository itemRepository;
private final DayLogRepository dayLogRepository;
private final TripCityRepository tripCityRepository;
private final TripRepository tripRepository;
private final RefreshTokenRepository refreshTokenRepository;
private final TransactionalDeleteProcessor transactionalDeleteProcessor;

@Async
@Transactional(propagation = REQUIRES_NEW)
@TransactionalEventListener(fallbackExecution = true)
@Transactional
@TransactionalEventListener
Copy link
Collaborator

Choose a reason for hiding this comment

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

fallbackExecution=true 왜 지우셨나용?

Copy link
Member Author

Choose a reason for hiding this comment

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

저번에 실험하다가 붙여놓고 까먹었습니다..
대충 요약하면
image
이런데 그냥 디폴트 해놔도 될듯...!

Copy link
Collaborator

Choose a reason for hiding this comment

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

현재 메서드에 @Transactional이 붙어있으니 무조건 트랜잭션이 있는 상태에서 이벤트를 처리하므로 상관이 없다는 뜻 맞죠? 👍

public void deleteMember(final MemberDeleteEvent event) {
final List<Long> dayLogIds = customDayLogRepository.findDayLogIdsByTripIds(event.getTripIds());
final List<ItemElement> itemElements = customItemRepository.findItemIdsByDayLogIds(dayLogIds);

deletePlaces(itemElements);
deleteExpenses(itemElements);
deleteImageAndItems(itemElements);

dayLogRepository.deleteByIds(dayLogIds);
tripRepository.deleteByMemberId(event.getMemberId());
refreshTokenRepository.deleteByMemberId(event.getMemberId());
transactionalDeleteProcessor.deleteRefreshTokens(event.getMemberId());
transactionalDeleteProcessor.deleteTrips(event.getMemberId());
transactionalDeleteProcessor.deleteTripCitesByTripIds(event.getTripIds());
deleteTripElements(dayLogIds);
}

@Async
@Transactional(propagation = REQUIRES_NEW)
@TransactionalEventListener(fallbackExecution = true)
@Transactional
@TransactionalEventListener
public void deleteTrip(final TripDeleteEvent event) {
final List<Long> dayLogIds = customDayLogRepository.findDayLogIdsByTripId(event.getTripId());
final List<ItemElement> itemElements = customItemRepository.findItemIdsByDayLogIds(dayLogIds);

deletePlaces(itemElements);
deleteExpenses(itemElements);
deleteImageAndItems(itemElements);

dayLogRepository.deleteByIds(dayLogIds);
tripCityRepository.deleteAllByTripId(event.getTripId());
}

private void deletePlaces(final List<ItemElement> itemElements) {
final List<Long> placeIds = itemElements.stream()
.map(ItemElement::getPlaceId)
.toList();

placeRepository.deleteByIds(placeIds);
}

private void deleteExpenses(final List<ItemElement> itemElements) {
final List<Long> expenseIds = itemElements.stream()
.map(ItemElement::getExpenseId)
.toList();

expenseRepository.deleteByIds(expenseIds);
transactionalDeleteProcessor.deleteTripCitesByTripId(event.getTripId());
deleteTripElements(dayLogIds);
}

private void deleteImageAndItems(final List<ItemElement> itemElements) {
private void deleteTripElements(final List<Long> dayLogIds) {
final List<ItemElement> itemElements = customItemRepository.findItemIdsByDayLogIds(dayLogIds);
final List<Long> itemIds = itemElements.stream()
.map(ItemElement::getItemId)
.toList();

imageRepository.deleteByItemIds(itemIds);
itemRepository.deleteByIds(itemIds);
transactionalDeleteProcessor.deleteDayLogs(dayLogIds);
transactionalDeleteProcessor.deleteItems(itemIds);
transactionalDeleteProcessor.deletePlaces(itemElements);
transactionalDeleteProcessor.deleteExpenses(itemElements);
transactionalDeleteProcessor.deleteImages(itemIds);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package hanglog.listener;

import hanglog.expense.domain.repository.ExpenseRepository;
import hanglog.login.domain.repository.RefreshTokenRepository;
import hanglog.trip.domain.repository.DayLogRepository;
import hanglog.trip.domain.repository.ImageRepository;
import hanglog.trip.domain.repository.ItemRepository;
import hanglog.trip.domain.repository.PlaceRepository;
import hanglog.trip.domain.repository.TripCityRepository;
import hanglog.trip.domain.repository.TripRepository;
import hanglog.trip.dto.ItemElement;
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

@Component
@RequiredArgsConstructor
@Transactional(propagation = Propagation.REQUIRES_NEW)
public class TransactionalDeleteProcessor {
Copy link
Collaborator

Choose a reason for hiding this comment

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

특정한 로직을 transactional하게 delete해준다는 invoker느낌이 강해지는 것 같네요! 이젠 얘한테 void타입의 delete로직을 수행하는 함수형인터페이스를 넘겨줘서 실행해도 괜찮겠다는 생각이 ......... 들었습니다...

Copy link
Collaborator

Choose a reason for hiding this comment

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

이런 느낌일까요?

    public void invoke(final Long memberId, final DeleteInvoker invoker) {
        tripDeleteInvoker.delete(memberId, tripRepository);
    }
  • 함수 호출시
transactionalDeleteProcessor
     .invoke(
           event.getMemberId(),
          (Long memberId, TripRepository repository) -> repository.deleteByMemberId(memberId)
);

Copy link
Collaborator

@hgo641 hgo641 Nov 23, 2023

Choose a reason for hiding this comment

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

굉장합니다! invoke의 인자로 익명클래스만 받아도 될 것 같습니다! memberId라는 인자에 국한되지 않아도 될 것 같습니다. AsyncDeleteProcessor or TransactionalDeleteProcessor라는 클래스를 만들거라면 이 방식도 괜찮아보입니다. 물론 전 Delete가 집중하지 말고 각 도메인별로 이벤트리스너만들자 파이긴합니다!!!


private final PlaceRepository placeRepository;
private final ExpenseRepository expenseRepository;
private final ImageRepository imageRepository;
private final ItemRepository itemRepository;
private final DayLogRepository dayLogRepository;
private final TripCityRepository tripCityRepository;
private final TripRepository tripRepository;
private final RefreshTokenRepository refreshTokenRepository;

public void deleteTrips(final Long memberId) {
tripRepository.deleteByMemberId(memberId);
}

public void deleteTripCitesByTripId(final Long tripId) {
tripCityRepository.deleteAllByTripId(tripId);
}

public void deleteTripCitesByTripIds(final List<Long> tripIds) {
tripCityRepository.deleteAllByTripIds(tripIds);
}

public void deleteDayLogs(final List<Long> dayLogIds) {
dayLogRepository.deleteByIds(dayLogIds);
}

public void deletePlaces(final List<ItemElement> itemElements) {
final List<Long> placeIds = itemElements.stream()
.map(ItemElement::getPlaceId)
.toList();
placeRepository.deleteByIds(placeIds);
}

public void deleteExpenses(final List<ItemElement> itemElements) {
final List<Long> expenseIds = itemElements.stream()
.map(ItemElement::getExpenseId)
.toList();
expenseRepository.deleteByIds(expenseIds);
}

public void deleteItems(final List<Long> itemIds) {
itemRepository.deleteByIds(itemIds);
}

public void deleteImages(final List<Long> itemIds) {
imageRepository.deleteByItemIds(itemIds);
}

public void deleteRefreshTokens(final Long memberId) {
refreshTokenRepository.deleteByMemberId(memberId);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,13 @@

public interface TripCityRepository extends JpaRepository<TripCity, Long> {

@Query("""
SELECT new hanglog.trip.dto.TripCityElement (tc.trip.id, tc.city)
FROM TripCity tc
WHERE tc.trip.id IN :tripIds
""")
List<TripCityElement> findTripIdAndCitiesByTripIds(@Param("tripIds") final List<Long> tripIds);

@Modifying
@Query("""
UPDATE TripCity tripCity
Expand All @@ -18,10 +25,11 @@ public interface TripCityRepository extends JpaRepository<TripCity, Long> {
""")
void deleteAllByTripId(@Param("tripId") final Long tripId);

@Modifying
@Query("""
SELECT new hanglog.trip.dto.TripCityElement (tc.trip.id, tc.city)
FROM TripCity tc
WHERE tc.trip.id IN :tripIds
UPDATE TripCity tripCity
SET tripCity.status = 'DELETED'
WHERE tripCity.trip.id IN :tripIds
""")
List<TripCityElement> findTripIdAndCitiesByTripIds(@Param("tripIds") final List<Long> tripIds);
void deleteAllByTripIds(@Param("tripId") final List<Long> tripIds);
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,12 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import hanglog.expense.domain.repository.ExpenseRepository;
import hanglog.listener.TransactionalDeleteProcessor;
import hanglog.listener.DeleteEventListener;
import hanglog.login.domain.repository.RefreshTokenRepository;
import hanglog.member.domain.MemberDeleteEvent;
import hanglog.trip.domain.TripDeleteEvent;
import hanglog.trip.domain.repository.CustomDayLogRepository;
import hanglog.trip.domain.repository.CustomItemRepository;
import hanglog.trip.domain.repository.DayLogRepository;
import hanglog.trip.domain.repository.ImageRepository;
import hanglog.trip.domain.repository.ItemRepository;
import hanglog.trip.domain.repository.PlaceRepository;
import hanglog.trip.domain.repository.TripCityRepository;
import hanglog.trip.domain.repository.TripRepository;
import java.util.ArrayList;
import java.util.List;
import org.junit.jupiter.api.DisplayName;
Expand All @@ -36,22 +29,10 @@ class DeleteEventListenerTest {
private CustomDayLogRepository customDayLogRepository;
@Mock
private CustomItemRepository customItemRepository;

@Mock
private PlaceRepository placeRepository;
@Mock
private ExpenseRepository expenseRepository;
@Mock
private ImageRepository imageRepository;
@Mock
private ItemRepository itemRepository;
@Mock
private DayLogRepository dayLogRepository;
@Mock
private TripRepository tripRepository;
@Mock
private RefreshTokenRepository refreshTokenRepository;
@Mock
private TripCityRepository tripCityRepository;
private TransactionalDeleteProcessor transactionalDeleteProcessor;

@InjectMocks
private DeleteEventListener listener;

Expand All @@ -62,28 +43,32 @@ void deleteMember() {
final MemberDeleteEvent event = new MemberDeleteEvent(List.of(1L, 2L, 3L), 1L);

when(customDayLogRepository.findDayLogIdsByTripIds(event.getTripIds())).thenReturn(new ArrayList<>());
doNothing().when(transactionalDeleteProcessor).deleteRefreshTokens(anyLong());
doNothing().when(transactionalDeleteProcessor).deleteTrips(anyLong());
doNothing().when(transactionalDeleteProcessor).deleteTripCitesByTripIds(anyList());

when(customItemRepository.findItemIdsByDayLogIds(anyList())).thenReturn(new ArrayList<>());
doNothing().when(placeRepository).deleteByIds(anyList());
doNothing().when(expenseRepository).deleteByIds(anyList());
doNothing().when(imageRepository).deleteByItemIds(anyList());
doNothing().when(itemRepository).deleteByIds(anyList());
doNothing().when(dayLogRepository).deleteByIds(anyList());
doNothing().when(tripRepository).deleteByMemberId(anyLong());
doNothing().when(refreshTokenRepository).deleteByMemberId(anyLong());
doNothing().when(transactionalDeleteProcessor).deleteDayLogs(anyList());
doNothing().when(transactionalDeleteProcessor).deleteItems(anyList());
doNothing().when(transactionalDeleteProcessor).deletePlaces(anyList());
doNothing().when(transactionalDeleteProcessor).deleteExpenses(anyList());
doNothing().when(transactionalDeleteProcessor).deleteImages(anyList());

// when
listener.deleteMember(event);

// then
verify(customDayLogRepository, times(1)).findDayLogIdsByTripIds(event.getTripIds());
verify(transactionalDeleteProcessor, times(1)).deleteRefreshTokens(anyLong());
verify(transactionalDeleteProcessor, times(1)).deleteTrips(anyLong());
verify(transactionalDeleteProcessor, times(1)).deleteTripCitesByTripIds(anyList());

verify(customItemRepository, times(1)).findItemIdsByDayLogIds(anyList());
verify(placeRepository, times(1)).deleteByIds(anyList());
verify(expenseRepository, times(1)).deleteByIds(anyList());
verify(imageRepository, times(1)).deleteByItemIds(anyList());
verify(itemRepository, times(1)).deleteByIds(anyList());
verify(dayLogRepository, times(1)).deleteByIds(anyList());
verify(tripRepository, times(1)).deleteByMemberId(anyLong());
verify(refreshTokenRepository, times(1)).deleteByMemberId(anyLong());
verify(transactionalDeleteProcessor, times(1)).deletePlaces(anyList());
verify(transactionalDeleteProcessor, times(1)).deleteExpenses(anyList());
verify(transactionalDeleteProcessor, times(1)).deleteItems(anyList());
verify(transactionalDeleteProcessor, times(1)).deleteImages(anyList());
verify(transactionalDeleteProcessor, times(1)).deleteDayLogs(anyList());
}

@DisplayName("deleteTrip 메서드에서 올바르게 레포지토리의 메서드를 호출한다.")
Expand All @@ -93,25 +78,27 @@ void deleteTrip() {
final TripDeleteEvent event = new TripDeleteEvent(1L);

when(customDayLogRepository.findDayLogIdsByTripId(event.getTripId())).thenReturn(new ArrayList<>());
doNothing().when(transactionalDeleteProcessor).deleteTripCitesByTripId(anyLong());

when(customItemRepository.findItemIdsByDayLogIds(anyList())).thenReturn(new ArrayList<>());
doNothing().when(placeRepository).deleteByIds(anyList());
doNothing().when(expenseRepository).deleteByIds(anyList());
doNothing().when(imageRepository).deleteByItemIds(anyList());
doNothing().when(itemRepository).deleteByIds(anyList());
doNothing().when(dayLogRepository).deleteByIds(anyList());
doNothing().when(tripCityRepository).deleteAllByTripId(anyLong());
doNothing().when(transactionalDeleteProcessor).deleteDayLogs(anyList());
doNothing().when(transactionalDeleteProcessor).deleteItems(anyList());
doNothing().when(transactionalDeleteProcessor).deletePlaces(anyList());
doNothing().when(transactionalDeleteProcessor).deleteExpenses(anyList());
doNothing().when(transactionalDeleteProcessor).deleteImages(anyList());

// when
listener.deleteTrip(event);

// then
verify(customDayLogRepository, times(1)).findDayLogIdsByTripId(event.getTripId());
verify(customItemRepository, times(1)).findItemIdsByDayLogIds(anyList());
verify(placeRepository, times(1)).deleteByIds(anyList());
verify(expenseRepository, times(1)).deleteByIds(anyList());
verify(imageRepository, times(1)).deleteByItemIds(anyList());
verify(itemRepository, times(1)).deleteByIds(anyList());
verify(dayLogRepository, times(1)).deleteByIds(anyList());
verify(tripCityRepository, times(1)).deleteAllByTripId(anyLong());

verify(customItemRepository, times(1)).findItemIdsByDayLogIds(anyList());
verify(transactionalDeleteProcessor, times(1)).deletePlaces(anyList());
verify(transactionalDeleteProcessor, times(1)).deleteExpenses(anyList());
verify(transactionalDeleteProcessor, times(1)).deleteItems(anyList());
verify(transactionalDeleteProcessor, times(1)).deleteImages(anyList());
verify(transactionalDeleteProcessor, times(1)).deleteDayLogs(anyList());
}
}