diff --git a/app/api/show-api/src/main/java/com/example/artist/service/ArtistService.java b/app/api/show-api/src/main/java/com/example/artist/service/ArtistService.java index d1f22d1b..744c4465 100644 --- a/app/api/show-api/src/main/java/com/example/artist/service/ArtistService.java +++ b/app/api/show-api/src/main/java/com/example/artist/service/ArtistService.java @@ -12,6 +12,8 @@ import com.example.artist.service.dto.response.ArtistFilterTotalCountServiceResponse; import com.example.artist.service.dto.response.ArtistSubscriptionServiceResponse; import com.example.artist.service.dto.response.ArtistUnsubscriptionServiceResponse; +import com.example.mq.MessagePublisher; +import com.example.mq.message.ArtistSubscriptionServiceMessage; import java.util.List; import java.util.NoSuchElementException; import java.util.UUID; @@ -30,6 +32,7 @@ public class ArtistService { private final ArtistUseCase artistUseCase; private final ArtistSubscriptionUseCase artistSubscriptionUseCase; + private final MessagePublisher messagePublisher; public PaginationServiceResponse searchArtist( ArtistSearchPaginationServiceRequest request @@ -69,6 +72,14 @@ public ArtistSubscriptionServiceResponse subscribe(ArtistSubscriptionServiceRequ request.userId() ); + var messages = subscriptions.stream() + .map(ArtistSubscriptionServiceMessage::from) + .toList(); + messagePublisher.publishArtistSubscription( + "artistSubscription", + messages + ); + return ArtistSubscriptionServiceResponse.builder() .successSubscriptionArtistIds( subscriptions.stream() @@ -85,6 +96,14 @@ public ArtistUnsubscriptionServiceResponse unsubscribe( request.userId() ); + var messages = unsubscribedArtists.stream() + .map(ArtistSubscriptionServiceMessage::from) + .toList(); + messagePublisher.publishArtistSubscription( + "artistUnsubscription", + messages + ); + return ArtistUnsubscriptionServiceResponse.builder() .successUnsubscriptionArtistIds( unsubscribedArtists.stream() diff --git a/app/api/show-api/src/main/java/com/example/genre/service/GenreService.java b/app/api/show-api/src/main/java/com/example/genre/service/GenreService.java index 8d341e3c..39f2768e 100644 --- a/app/api/show-api/src/main/java/com/example/genre/service/GenreService.java +++ b/app/api/show-api/src/main/java/com/example/genre/service/GenreService.java @@ -8,6 +8,8 @@ import com.example.genre.service.dto.request.GenreUnsubscriptionServiceRequest; import com.example.genre.service.dto.response.GenreSubscriptionServiceResponse; import com.example.genre.service.dto.response.GenreUnsubscriptionServiceResponse; +import com.example.mq.MessagePublisher; +import com.example.mq.message.GenreSubscriptionServiceMessage; import java.util.List; import java.util.UUID; import lombok.RequiredArgsConstructor; @@ -25,6 +27,7 @@ public class GenreService { private final GenreUseCase genreUseCase; private final GenreSubscriptionUseCase genreSubscriptionUseCase; + private final MessagePublisher messagePublisher; public GenreSubscriptionServiceResponse subscribe(GenreSubscriptionServiceRequest request) { List existGenresInRequest = genreUseCase.findAllGenresInIds(request.genreIds()); @@ -37,6 +40,14 @@ public GenreSubscriptionServiceResponse subscribe(GenreSubscriptionServiceReques request.userId() ); + var messages = subscriptions.stream() + .map(GenreSubscriptionServiceMessage::from) + .toList(); + messagePublisher.publishGenreSubscription( + "genreSubscription", + messages + ); + return GenreSubscriptionServiceResponse.builder() .successSubscriptionGenreIds( subscriptions.stream() @@ -54,6 +65,14 @@ public GenreUnsubscriptionServiceResponse unsubscribe( request.userId() ); + var messages = unsubscriptionGenres.stream() + .map(GenreSubscriptionServiceMessage::from) + .toList(); + messagePublisher.publishGenreSubscription( + "genreUnsubscription", + messages + ); + return GenreUnsubscriptionServiceResponse.builder() .successUnsubscriptionGenreIds( unsubscriptionGenres.stream() diff --git a/app/api/show-api/src/main/java/com/example/mq/MessagePublisher.java b/app/api/show-api/src/main/java/com/example/mq/MessagePublisher.java new file mode 100644 index 00000000..4f7ed89d --- /dev/null +++ b/app/api/show-api/src/main/java/com/example/mq/MessagePublisher.java @@ -0,0 +1,18 @@ +package com.example.mq; + +import com.example.mq.message.ArtistSubscriptionServiceMessage; +import com.example.mq.message.GenreSubscriptionServiceMessage; +import com.example.mq.message.ShowRelationArtistAndGenreServiceMessage; +import com.example.mq.message.TicketingReservationServiceMessage; +import java.util.List; + +public interface MessagePublisher { + + void publishShow(String topic, ShowRelationArtistAndGenreServiceMessage message); + + void publishArtistSubscription(String topic, List messages); + + void publishGenreSubscription(String topic, List messages); + + void publishTicketingReservation(String topic, List messages); +} diff --git a/app/api/show-api/src/main/java/com/example/mq/message/ArtistSubscriptionServiceMessage.java b/app/api/show-api/src/main/java/com/example/mq/message/ArtistSubscriptionServiceMessage.java new file mode 100644 index 00000000..27c98b1e --- /dev/null +++ b/app/api/show-api/src/main/java/com/example/mq/message/ArtistSubscriptionServiceMessage.java @@ -0,0 +1,18 @@ +package com.example.mq.message; + +import java.util.UUID; +import lombok.Builder; +import org.example.entity.ArtistSubscription; + +@Builder +public record ArtistSubscriptionServiceMessage( + UUID userId, + UUID artistId +) { + public static ArtistSubscriptionServiceMessage from(ArtistSubscription artistSubscription) { + return ArtistSubscriptionServiceMessage.builder() + .userId(artistSubscription.getUserId()) + .artistId(artistSubscription.getArtistId()) + .build(); + } +} diff --git a/app/api/show-api/src/main/java/com/example/mq/message/GenreSubscriptionServiceMessage.java b/app/api/show-api/src/main/java/com/example/mq/message/GenreSubscriptionServiceMessage.java new file mode 100644 index 00000000..9b6b0380 --- /dev/null +++ b/app/api/show-api/src/main/java/com/example/mq/message/GenreSubscriptionServiceMessage.java @@ -0,0 +1,19 @@ +package com.example.mq.message; + +import java.util.UUID; +import lombok.Builder; +import org.example.entity.GenreSubscription; + +@Builder +public record GenreSubscriptionServiceMessage( + UUID userId, + UUID genreId +) { + + public static GenreSubscriptionServiceMessage from(GenreSubscription genreSubscription) { + return GenreSubscriptionServiceMessage.builder() + .userId(genreSubscription.getUserId()) + .genreId(genreSubscription.getGenreId()) + .build(); + } +} diff --git a/app/api/show-api/src/main/java/com/example/mq/message/ShowRelationArtistAndGenreServiceMessage.java b/app/api/show-api/src/main/java/com/example/mq/message/ShowRelationArtistAndGenreServiceMessage.java new file mode 100644 index 00000000..b5d15301 --- /dev/null +++ b/app/api/show-api/src/main/java/com/example/mq/message/ShowRelationArtistAndGenreServiceMessage.java @@ -0,0 +1,20 @@ +package com.example.mq.message; + +import java.util.List; +import java.util.UUID; +import lombok.Builder; + +@Builder +public record ShowRelationArtistAndGenreServiceMessage( + List artistIds, + List genreIds +) { + + public static ShowRelationArtistAndGenreServiceMessage of(List artistIds, List genreIds) { + return ShowRelationArtistAndGenreServiceMessage.builder() + .artistIds(artistIds) + .genreIds(genreIds) + .build(); + } + +} diff --git a/app/api/show-api/src/main/java/com/example/mq/message/TicketingReservationServiceMessage.java b/app/api/show-api/src/main/java/com/example/mq/message/TicketingReservationServiceMessage.java new file mode 100644 index 00000000..824b157a --- /dev/null +++ b/app/api/show-api/src/main/java/com/example/mq/message/TicketingReservationServiceMessage.java @@ -0,0 +1,15 @@ +package com.example.mq.message; + +import com.example.show.controller.vo.TicketingApiType; +import java.time.LocalDateTime; +import java.util.UUID; + +public record TicketingReservationServiceMessage( + LocalDateTime reserveAt, + String showName, + TicketingApiType type, + UUID userId, + UUID showId +) { + +} diff --git a/app/api/show-api/src/main/java/com/example/show/service/ShowAdminService.java b/app/api/show-api/src/main/java/com/example/show/service/ShowAdminService.java index 96dcb199..c3e0e02c 100644 --- a/app/api/show-api/src/main/java/com/example/show/service/ShowAdminService.java +++ b/app/api/show-api/src/main/java/com/example/show/service/ShowAdminService.java @@ -2,6 +2,8 @@ import com.example.component.FileUploadComponent; +import com.example.mq.MessagePublisher; +import com.example.mq.message.ShowRelationArtistAndGenreServiceMessage; import com.example.show.error.ShowError; import com.example.show.service.dto.request.ShowCreateServiceRequest; import com.example.show.service.dto.request.ShowUpdateServiceRequest; @@ -21,6 +23,7 @@ public class ShowAdminService { private final ShowUseCase showUseCase; private final FileUploadComponent fileUploadComponent; + private final MessagePublisher messagePublisher; public void save(ShowCreateServiceRequest showCreateServiceRequest) { String imageURL = fileUploadComponent.uploadFile("show", showCreateServiceRequest.post()); @@ -28,6 +31,14 @@ public void save(ShowCreateServiceRequest showCreateServiceRequest) { showUseCase.save( showCreateServiceRequest.toDomainRequest(imageURL) ); + + messagePublisher.publishShow( + "registerShow", + ShowRelationArtistAndGenreServiceMessage.of( + showCreateServiceRequest.artistIds(), + showCreateServiceRequest.genreIds() + ) + ); } public List findAllShowInfos() { @@ -51,6 +62,16 @@ public ShowInfoServiceResponse findShowInfo(UUID id) { public void updateShow(UUID id, ShowUpdateServiceRequest showUpdateServiceRequest) { String imageUrl = fileUploadComponent.uploadFile("show", showUpdateServiceRequest.post()); + var artistIdsToPublish = showUseCase.getArtistIdsToAdd( + showUpdateServiceRequest.artistIds(), + showUseCase.findShowArtistsByShowId(id) + ); + + var genreIdsToPublish = showUseCase.getGenreIdsToAdd( + showUpdateServiceRequest.genreIds(), + showUseCase.findShowGenresByShowId(id) + ); + try { showUseCase.updateShow( id, @@ -59,6 +80,16 @@ public void updateShow(UUID id, ShowUpdateServiceRequest showUpdateServiceReques } catch (NoSuchElementException e) { throw new BusinessException(ShowError.ENTITY_NOT_FOUND); } + + if (!artistIdsToPublish.isEmpty() || !genreIdsToPublish.isEmpty()) { + messagePublisher.publishShow( + "updateShow", + ShowRelationArtistAndGenreServiceMessage.of( + artistIdsToPublish, + genreIdsToPublish + ) + ); + } } public void deleteShow(UUID id) { diff --git a/app/api/show-api/src/test/java/artist/service/ArtistServiceTest.java b/app/api/show-api/src/test/java/artist/service/ArtistServiceTest.java index 3a4fe8e8..bfdbd0fc 100644 --- a/app/api/show-api/src/test/java/artist/service/ArtistServiceTest.java +++ b/app/api/show-api/src/test/java/artist/service/ArtistServiceTest.java @@ -1,14 +1,19 @@ package artist.service; import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.anyList; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; import artist.fixture.dto.ArtistRequestDtoFixture; import artist.fixture.dto.ArtistResponseDtoFixture; import com.example.artist.service.ArtistService; import com.example.artist.service.dto.request.ArtistSubscriptionServiceRequest; import com.example.artist.service.dto.request.ArtistUnsubscriptionServiceRequest; +import com.example.mq.MessagePublisher; import java.util.List; import java.util.NoSuchElementException; import java.util.UUID; @@ -28,9 +33,13 @@ class ArtistServiceTest { private final ArtistSubscriptionUseCase artistSubscriptionUseCase = mock( ArtistSubscriptionUseCase.class ); + + private final MessagePublisher messagePublisher = mock(MessagePublisher.class); + private final ArtistService artistService = new ArtistService( artistUseCase, - artistSubscriptionUseCase + artistSubscriptionUseCase, + messagePublisher ); @Test @@ -251,6 +260,41 @@ void artistSubscribe() { ); } + @Test + @DisplayName("아티스트를 구독하면 구독 성공한 아티스트 ID들을 메시지 발행한다.") + void artistSubscribePublishMessage() { + //given + List artistsId = List.of(UUID.randomUUID(), UUID.randomUUID()); + UUID userId = UUID.randomUUID(); + var request = new ArtistSubscriptionServiceRequest(artistsId, userId); + var existArtistsInRequest = ArtistFixture.manSoloArtists(3); + given( + artistUseCase.findAllArtistInIds(request.artistIds()) + ).willReturn( + existArtistsInRequest + ); + + var existArtistIdsInRequest = existArtistsInRequest.stream() + .map(Artist::getId) + .toList(); + int artistSubscriptionCount = 2; + given( + artistSubscriptionUseCase.subscribe(existArtistIdsInRequest, userId) + ).willReturn( + ArtistSubscriptionFixture.artistSubscriptions(artistSubscriptionCount) + ); + + //when + var result = artistService.subscribe(request); + + //then + assertThat(result).isNotNull(); + verify(messagePublisher, times(1)).publishArtistSubscription( + eq("artistSubscription"), + anyList() + ); + } + @Test @DisplayName("아티스트를 구독 취소하면 구독 취소 성공한 아티스트 ID들을 반환한다.") void artistUnsubscribe() { @@ -278,6 +322,32 @@ void artistUnsubscribe() { ); } + @Test + @DisplayName("아티스트를 구독 취소하면 구독 취소 성공한 아티스트 ID들을 메시지 발행한다.") + void artistUnsubscribePublishMessage() { + //given + List artistsId = List.of(UUID.randomUUID(), UUID.randomUUID()); + UUID userId = UUID.randomUUID(); + var request = new ArtistUnsubscriptionServiceRequest(artistsId, userId); + int artistSubscriptionCount = 2; + given( + artistSubscriptionUseCase.unsubscribe(request.artistIds(), userId) + ).willReturn( + ArtistSubscriptionFixture.artistSubscriptions(artistSubscriptionCount) + ); + + //when + var result = artistService.unsubscribe(request); + + //then + assertThat(result).isNotNull(); + verify(messagePublisher, times(1)) + .publishArtistSubscription( + eq("artistUnsubscription"), + anyList() + ); + } + @Test @DisplayName("페이지네이션을 이용해 구독한 아티스트를 반환한다.") void artistSubscribeWithPagination() { diff --git a/app/api/show-api/src/test/java/genre/service/GenreServiceTest.java b/app/api/show-api/src/test/java/genre/service/GenreServiceTest.java index 9fcfd5bb..910cc7db 100644 --- a/app/api/show-api/src/test/java/genre/service/GenreServiceTest.java +++ b/app/api/show-api/src/test/java/genre/service/GenreServiceTest.java @@ -1,11 +1,17 @@ package genre.service; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.anyList; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; import com.example.genre.service.GenreService; import com.example.genre.service.dto.request.GenreSubscriptionServiceRequest; import com.example.genre.service.dto.request.GenreUnsubscriptionServiceRequest; +import com.example.mq.MessagePublisher; import genre.fixture.GenreRequestDtoFixture; import genre.fixture.GenreResponseDtoFixture; import java.util.List; @@ -24,10 +30,14 @@ class GenreServiceTest { private final GenreUseCase genreUseCase = mock(GenreUseCase.class); private final GenreSubscriptionUseCase genreSubscriptionUseCase = mock( - GenreSubscriptionUseCase.class); + GenreSubscriptionUseCase.class + ); + private final MessagePublisher messagePublisher = mock(MessagePublisher.class); + private final GenreService genreService = new GenreService( genreUseCase, - genreSubscriptionUseCase + genreSubscriptionUseCase, + messagePublisher ); @Test @@ -67,6 +77,41 @@ void genreSubscribe() { ); } + @Test + @DisplayName("장르를 구독하면 구독 성공한 장르 ID들을 메시지 발행하다.") + void genreSubscribePublishMessage() { + //given + List genreIds = List.of(UUID.randomUUID(), UUID.randomUUID()); + UUID userId = UUID.randomUUID(); + var request = new GenreSubscriptionServiceRequest(genreIds, userId); + var existGenresInRequest = GenreFixture.genres(2); + given( + genreUseCase.findAllGenresInIds(request.genreIds()) + ).willReturn( + existGenresInRequest + ); + + var existGenreIdsInRequest = existGenresInRequest.stream() + .map(Genre::getId) + .toList(); + int genreSubscriptionCount = 2; + given( + genreSubscriptionUseCase.subscribe(existGenreIdsInRequest, request.userId()) + ).willReturn( + GenreSubscriptionFixture.genreSubscriptions(genreSubscriptionCount) + ); + + //when + var result = genreService.subscribe(request); + + //then + assertThat(result).isNotNull(); + verify(messagePublisher, times(1)).publishGenreSubscription( + eq("genreSubscription"), + anyList() + ); + } + @Test @DisplayName("장르를 구독 취소하면 구독 취소 성공한 장르 ID들을 반환하다.") void genreUnsubscribe() { @@ -94,6 +139,32 @@ void genreUnsubscribe() { ); } + @Test + @DisplayName("장르를 구독 취소하면 구독 취소 성공한 장르 ID들을 메시지 발행한다.") + void genreUnsubscribePublishMessage() { + //given + List genreIds = List.of(UUID.randomUUID(), UUID.randomUUID()); + UUID userId = UUID.randomUUID(); + var request = new GenreUnsubscriptionServiceRequest(genreIds, userId); + int genreUnsubscriptionCount = 2; + given( + genreSubscriptionUseCase.unsubscribe(request.genreIds(), request.userId()) + ).willReturn( + GenreSubscriptionFixture.genreSubscriptions(genreUnsubscriptionCount) + ); + + //when + var result = genreService.unsubscribe(request); + + //then + assertThat(result).isNotNull(); + verify(messagePublisher, times(1)) + .publishGenreSubscription( + eq("genreUnsubscription"), + anyList() + ); + } + @Test @DisplayName("페이지네이션을 이용해 구독한 장르를 반환한다.") void genreSubscribeWithPagination() { diff --git a/app/api/show-api/src/test/java/show/service/ShowAdminServiceTest.java b/app/api/show-api/src/test/java/show/service/ShowAdminServiceTest.java index 71e3f778..16f10386 100644 --- a/app/api/show-api/src/test/java/show/service/ShowAdminServiceTest.java +++ b/app/api/show-api/src/test/java/show/service/ShowAdminServiceTest.java @@ -1,19 +1,25 @@ package show.service; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.BDDMockito.given; +import static org.mockito.BDDMockito.willDoNothing; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import com.example.component.FileUploadComponent; +import com.example.mq.MessagePublisher; +import com.example.mq.message.ShowRelationArtistAndGenreServiceMessage; import com.example.show.service.ShowAdminService; import com.example.show.service.dto.request.ShowCreateServiceRequest; import com.example.show.service.dto.request.ShowUpdateServiceRequest; +import java.util.List; import java.util.UUID; -import org.example.dto.show.request.ShowCreationDomainRequest; import org.example.dto.show.request.ShowUpdateDomainRequest; +import org.example.fixture.domain.ShowArtistFixture; +import org.example.fixture.domain.ShowGenreFixture; import org.example.usecase.show.ShowUseCase; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -23,9 +29,13 @@ class ShowAdminServiceTest { private final ShowUseCase showUseCase = mock(ShowUseCase.class); private final FileUploadComponent fileUploadComponent = mock(FileUploadComponent.class); + private final MessagePublisher messagePublisher = mock(MessagePublisher.class); private final ShowAdminService showAdminService = new ShowAdminService( - showUseCase, fileUploadComponent); + showUseCase, + fileUploadComponent, + messagePublisher + ); @Test @DisplayName("공연은 업로드된 이미지 URL과 함께 생성된다.") @@ -38,8 +48,6 @@ void showCreateWithUploadedImageUrl() { showCreateServiceRequest.post() ) ).willReturn("test_imageUrl"); - ShowCreationDomainRequest request = showCreateServiceRequest.toDomainRequest( - "test_imageUrl"); //when showAdminService.save(showCreateServiceRequest); @@ -48,6 +56,26 @@ void showCreateWithUploadedImageUrl() { verify(showUseCase, times(1)).save(any()); } + @Test + @DisplayName("공연 생성 후 연관된 아티스트, 장르 아이디로 메시지를 전송한다.") + void showCreateWithPublishRelationArtistIdsAndGenreIds() { + //given + var showCreateServiceRequest = ShowRequestDtoFixture.showCreateServiceRequest(); + var showCreationDomainRequest = showCreateServiceRequest.toDomainRequest( + "test_imageUrl" + ); + willDoNothing().given(showUseCase).save(showCreationDomainRequest); + + //when + showAdminService.save(showCreateServiceRequest); + + //then + verify(messagePublisher, times(1)).publishShow( + anyString(), + any(ShowRelationArtistAndGenreServiceMessage.class) + ); + } + @Test @DisplayName("공연은 업로드된 이미지 URL과 함께 업데이트 된다.") void showUpdateWithUploadedImageUrl() { @@ -67,4 +95,47 @@ void showUpdateWithUploadedImageUrl() { //then verify(showUseCase, times(1)).updateShow(eq(showId), any(ShowUpdateDomainRequest.class)); } + + @Test + @DisplayName("공연 업데이트 후 새로 연관된 아티스트, 장르 아이디로 메시지를 전송한다.") + void showUpdateWithPublishNewRelationArtistIdsAndGenreIds() { + //given + ShowUpdateServiceRequest showUpdateServiceRequest = ShowRequestDtoFixture.showUpdateServiceRequest(); + UUID showId = UUID.randomUUID(); + var showArtists = ShowArtistFixture.showArtists(3); + given( + showUseCase.findShowArtistsByShowId(showId) + ).willReturn( + showArtists + ); + given( + showUseCase.getArtistIdsToAdd( + showUpdateServiceRequest.artistIds(), + showArtists + ) + ).willReturn(List.of(UUID.randomUUID())); + + var showGenres = ShowGenreFixture.showGenres(3); + given( + showUseCase.findShowGenresByShowId(showId) + ).willReturn( + showGenres + ); + given( + showUseCase.getGenreIdsToAdd( + showUpdateServiceRequest.genreIds(), + showGenres + ) + ).willReturn(List.of(UUID.randomUUID())); + + //when + showAdminService.updateShow(showId, showUpdateServiceRequest); + + //then + verify(messagePublisher, times(1)).publishShow( + anyString(), + any(ShowRelationArtistAndGenreServiceMessage.class) + ); + } + } diff --git a/app/domain/show-domain/src/main/java/org/example/usecase/show/ShowUseCase.java b/app/domain/show-domain/src/main/java/org/example/usecase/show/ShowUseCase.java index c55ec8af..25dc4421 100644 --- a/app/domain/show-domain/src/main/java/org/example/usecase/show/ShowUseCase.java +++ b/app/domain/show-domain/src/main/java/org/example/usecase/show/ShowUseCase.java @@ -14,6 +14,7 @@ import org.example.entity.show.Show; import org.example.entity.show.ShowArtist; import org.example.entity.show.ShowGenre; +import org.example.entity.show.ShowSearch; import org.example.entity.show.info.ShowTicketingTimes; import org.example.repository.show.ShowRepository; import org.example.repository.show.ShowTicketingTimeRepository; @@ -75,10 +76,9 @@ public void updateShow(UUID id, ShowUpdateDomainRequest request) { updateShowTicketingTimes(request.showTicketingTimes(), show); } - private void updateShowSearch(Show show) { + public void updateShowSearch(Show show) { var newShowSearch = show.toShowSearch(); - var currentShowSearches = showSearchRepository.findAllByShowIdAndIsDeletedFalse( - show.getId()); + var currentShowSearches = findShowSearchesByShowId(show.getId()); if (!currentShowSearches.contains(newShowSearch)) { showSearchRepository.save(newShowSearch); @@ -90,45 +90,55 @@ private void updateShowSearch(Show show) { } } - private void updateShowArtist(List newArtistIds, Show show) { - var currentShowArtists = showArtistRepository.findAllByShowIdAndIsDeletedFalse( - show.getId()); + public void updateShowArtist(List newArtistIds, Show show) { + var currentShowArtists = findShowArtistsByShowId(show.getId()); + var artistIdsToAdd = getArtistIdsToAdd(newArtistIds, currentShowArtists); + showArtistRepository.saveAll(show.toShowArtist(artistIdsToAdd)); + + var showArtistsToRemove = currentShowArtists.stream() + .filter(showArtist -> !newArtistIds.contains(showArtist.getArtistId())) + .toList(); + showArtistsToRemove.forEach(BaseEntity::softDelete); + } + + public List getArtistIdsToAdd( + List newArtistIds, + List currentShowArtists + ) { var currentArtistIds = currentShowArtists.stream() .map(ShowArtist::getArtistId) .toList(); - var artistIdsToAdd = newArtistIds.stream() + return newArtistIds.stream() .filter(newArtistId -> !currentArtistIds.contains(newArtistId)) .toList(); - var showArtistsToAdd = show.toShowArtist(artistIdsToAdd); - showArtistRepository.saveAll(showArtistsToAdd); + } - var showArtistsToRemove = currentShowArtists.stream() - .filter(showArtist -> !newArtistIds.contains(showArtist.getArtistId())) + public void updateShowGenre(List newGenreIds, Show show) { + List currentShowGenres = findShowGenresByShowId(show.getId()); + List genreIdsToAdd = getGenreIdsToAdd(newGenreIds, currentShowGenres); + showGenreRepository.saveAll(show.toShowGenre(genreIdsToAdd)); + + List showGenresToRemove = currentShowGenres.stream() + .filter(showGenre -> !newGenreIds.contains(showGenre.getGenreId())) .toList(); - showArtistsToRemove.forEach(BaseEntity::softDelete); + showGenresToRemove.forEach(BaseEntity::softDelete); } - private void updateShowGenre(List newGenreIds, Show show) { - var currentShowGenres = showGenreRepository.findAllByShowIdAndIsDeletedFalse( - show.getId()); + public List getGenreIdsToAdd( + List newGenreIds, + List currentShowGenres + ) { var currentGenreIds = currentShowGenres.stream() .map(ShowGenre::getGenreId) .toList(); - var genreIdsToAdd = newGenreIds.stream() + return newGenreIds.stream() .filter(newGenreId -> !currentGenreIds.contains(newGenreId)) .toList(); - var showGenresToAdd = show.toShowGenre(genreIdsToAdd); - showGenreRepository.saveAll(showGenresToAdd); - - var showGenresToRemove = currentShowGenres.stream() - .filter(showGenre -> !newGenreIds.contains(showGenre.getGenreId())) - .toList(); - showGenresToRemove.forEach(BaseEntity::softDelete); } - private void updateShowTicketingTimes(ShowTicketingTimes ticketingTimes, Show show) { + public void updateShowTicketingTimes(ShowTicketingTimes ticketingTimes, Show show) { var currentShowTicketingTimes = showTicketingTimeRepository.findAllByShowIdAndIsDeletedFalse( show.getId() ); @@ -172,10 +182,22 @@ public void deleteShow(UUID id) { } public ShowSearchPaginationDomainResponse searchShow( - ShowSearchPaginationDomainRequest request) { + ShowSearchPaginationDomainRequest request + ) { return showSearchRepository.searchShow(request); } + public List findShowArtistsByShowId(UUID showId) { + return showArtistRepository.findAllByShowIdAndIsDeletedFalse(showId); + } + + public List findShowGenresByShowId(UUID showId) { + return showGenreRepository.findAllByShowIdAndIsDeletedFalse(showId); + } + + public List findShowSearchesByShowId(UUID showId) { + return showSearchRepository.findAllByShowIdAndIsDeletedFalse(showId); + } private Show findShowById(UUID id) { return showRepository.findById(id).orElseThrow(NoSuchElementException::new); diff --git a/app/domain/show-domain/src/testFixtures/java/org/example/fixture/domain/ShowArtistFixture.java b/app/domain/show-domain/src/testFixtures/java/org/example/fixture/domain/ShowArtistFixture.java new file mode 100644 index 00000000..8d29b348 --- /dev/null +++ b/app/domain/show-domain/src/testFixtures/java/org/example/fixture/domain/ShowArtistFixture.java @@ -0,0 +1,18 @@ +package org.example.fixture.domain; + +import java.util.List; +import java.util.UUID; +import java.util.stream.IntStream; +import org.example.entity.show.ShowArtist; + +public class ShowArtistFixture { + public static List showArtists(int count) { + return IntStream.range(0, count) + .mapToObj(i -> ShowArtist.builder() + .showId(UUID.randomUUID()) + .artistId(UUID.randomUUID()) + .build() + ) + .toList(); + } +} diff --git a/app/domain/show-domain/src/testFixtures/java/org/example/fixture/domain/ShowGenreFixture.java b/app/domain/show-domain/src/testFixtures/java/org/example/fixture/domain/ShowGenreFixture.java new file mode 100644 index 00000000..a214049b --- /dev/null +++ b/app/domain/show-domain/src/testFixtures/java/org/example/fixture/domain/ShowGenreFixture.java @@ -0,0 +1,19 @@ +package org.example.fixture.domain; + +import java.util.List; +import java.util.UUID; +import java.util.stream.IntStream; +import org.example.entity.show.ShowGenre; + +public class ShowGenreFixture { + + public static List showGenres(int count) { + return IntStream.range(0, count) + .mapToObj(i -> ShowGenre.builder() + .showId(UUID.randomUUID()) + .genreId(UUID.randomUUID()) + .build() + ) + .toList(); + } +} diff --git a/app/infrastructure/build.gradle b/app/infrastructure/build.gradle index a3ca096f..e273be43 100644 --- a/app/infrastructure/build.gradle +++ b/app/infrastructure/build.gradle @@ -1,7 +1,7 @@ -bootJar.enabled = false -jar.enabled = true - allprojects { + bootJar.enabled = false + jar.enabled = true + dependencies { implementation "org.springframework.boot:spring-boot-starter-web" } @@ -10,4 +10,5 @@ allprojects { dependencies { implementation project(":app:infrastructure:redis") implementation project(":app:infrastructure:s3") + implementation project(":app:infrastructure:message_queue") } \ No newline at end of file diff --git a/app/infrastructure/message_queue/build.gradle b/app/infrastructure/message_queue/build.gradle new file mode 100644 index 00000000..47d93e97 --- /dev/null +++ b/app/infrastructure/message_queue/build.gradle @@ -0,0 +1,8 @@ +dependencies { + implementation project(":app:api:show-api") + + //redis pub & sub + implementation project(":app:infrastructure:redis") + implementation 'org.springframework.data:spring-data-redis' + implementation 'io.lettuce:lettuce-core:6.3.0.RELEASE' +} diff --git a/app/infrastructure/message_queue/src/main/java/org/example/config/PubSubConfig.java b/app/infrastructure/message_queue/src/main/java/org/example/config/PubSubConfig.java new file mode 100644 index 00000000..49d93fc4 --- /dev/null +++ b/app/infrastructure/message_queue/src/main/java/org/example/config/PubSubConfig.java @@ -0,0 +1,27 @@ +package org.example.config; + +import lombok.RequiredArgsConstructor; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer; +import org.springframework.data.redis.serializer.StringRedisSerializer; + +@Configuration +@ComponentScan(basePackages = "org.example") +@RequiredArgsConstructor +public class PubSubConfig { + + private final RedisConnectionFactory redisConnectionFactory; + + @Bean + public RedisTemplate pubSubTemplate() { + RedisTemplate redisTemplate = new RedisTemplate<>(); + redisTemplate.setConnectionFactory(redisConnectionFactory); + redisTemplate.setKeySerializer(new StringRedisSerializer()); + redisTemplate.setValueSerializer(new Jackson2JsonRedisSerializer<>(Object.class)); + return redisTemplate; + } +} diff --git a/app/infrastructure/message_queue/src/main/java/org/example/message/ArtistSubscriptionInfraMessage.java b/app/infrastructure/message_queue/src/main/java/org/example/message/ArtistSubscriptionInfraMessage.java new file mode 100644 index 00000000..526b611c --- /dev/null +++ b/app/infrastructure/message_queue/src/main/java/org/example/message/ArtistSubscriptionInfraMessage.java @@ -0,0 +1,21 @@ +package org.example.message; + +import com.example.mq.message.ArtistSubscriptionServiceMessage; +import java.util.UUID; +import lombok.Builder; + +@Builder +public record ArtistSubscriptionInfraMessage( + UUID userId, + UUID artistId +) { + + public static ArtistSubscriptionInfraMessage from( + ArtistSubscriptionServiceMessage message + ) { + return ArtistSubscriptionInfraMessage.builder() + .userId(message.userId()) + .artistId(message.artistId()) + .build(); + } +} diff --git a/app/infrastructure/message_queue/src/main/java/org/example/message/GenreSubscriptionInfraMessage.java b/app/infrastructure/message_queue/src/main/java/org/example/message/GenreSubscriptionInfraMessage.java new file mode 100644 index 00000000..5ecc6c6a --- /dev/null +++ b/app/infrastructure/message_queue/src/main/java/org/example/message/GenreSubscriptionInfraMessage.java @@ -0,0 +1,21 @@ +package org.example.message; + +import com.example.mq.message.GenreSubscriptionServiceMessage; +import java.util.UUID; +import lombok.Builder; + +@Builder +public record GenreSubscriptionInfraMessage( + UUID userId, + UUID genreId +) { + + public static GenreSubscriptionInfraMessage from( + GenreSubscriptionServiceMessage message + ) { + return GenreSubscriptionInfraMessage.builder() + .userId(message.userId()) + .genreId(message.genreId()) + .build(); + } +} diff --git a/app/infrastructure/message_queue/src/main/java/org/example/message/ShowRelationArtistAndGenreInfraMessage.java b/app/infrastructure/message_queue/src/main/java/org/example/message/ShowRelationArtistAndGenreInfraMessage.java new file mode 100644 index 00000000..aa7a0bf7 --- /dev/null +++ b/app/infrastructure/message_queue/src/main/java/org/example/message/ShowRelationArtistAndGenreInfraMessage.java @@ -0,0 +1,23 @@ +package org.example.message; + +import com.example.mq.message.ShowRelationArtistAndGenreServiceMessage; +import java.util.List; +import java.util.UUID; +import lombok.Builder; + +@Builder +public record ShowRelationArtistAndGenreInfraMessage( + List artistIds, + List genreIds +) { + + public static ShowRelationArtistAndGenreInfraMessage from( + ShowRelationArtistAndGenreServiceMessage message + ) { + return ShowRelationArtistAndGenreInfraMessage.builder() + .artistIds(message.artistIds()) + .genreIds(message.genreIds()) + .build(); + + } +} diff --git a/app/infrastructure/message_queue/src/main/java/org/example/message/TicketingReservationInfraMessage.java b/app/infrastructure/message_queue/src/main/java/org/example/message/TicketingReservationInfraMessage.java new file mode 100644 index 00000000..dacf155e --- /dev/null +++ b/app/infrastructure/message_queue/src/main/java/org/example/message/TicketingReservationInfraMessage.java @@ -0,0 +1,29 @@ +package org.example.message; + +import com.example.mq.message.TicketingReservationServiceMessage; +import com.example.show.controller.vo.TicketingApiType; +import java.time.LocalDateTime; +import java.util.UUID; +import lombok.Builder; + +@Builder +public record TicketingReservationInfraMessage( + LocalDateTime reserveAt, + String showName, + TicketingApiType type, + UUID userId, + UUID showId +) { + + public static TicketingReservationInfraMessage from( + TicketingReservationServiceMessage message + ) { + return TicketingReservationInfraMessage.builder() + .reserveAt(message.reserveAt()) + .showName(message.showName()) + .type(message.type()) + .userId(message.userId()) + .showId(message.showId()) + .build(); + } +} diff --git a/app/infrastructure/message_queue/src/main/java/org/example/publish/RedisMessagePublisher.java b/app/infrastructure/message_queue/src/main/java/org/example/publish/RedisMessagePublisher.java new file mode 100644 index 00000000..23e529f5 --- /dev/null +++ b/app/infrastructure/message_queue/src/main/java/org/example/publish/RedisMessagePublisher.java @@ -0,0 +1,76 @@ +package org.example.publish; + +import com.example.mq.MessagePublisher; +import com.example.mq.message.ArtistSubscriptionServiceMessage; +import com.example.mq.message.GenreSubscriptionServiceMessage; +import com.example.mq.message.ShowRelationArtistAndGenreServiceMessage; +import com.example.mq.message.TicketingReservationServiceMessage; +import java.util.List; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.example.message.ArtistSubscriptionInfraMessage; +import org.example.message.GenreSubscriptionInfraMessage; +import org.example.message.ShowRelationArtistAndGenreInfraMessage; +import org.example.message.TicketingReservationInfraMessage; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.stereotype.Component; + +@Slf4j +@Component +@RequiredArgsConstructor +public class RedisMessagePublisher implements MessagePublisher { + + private final RedisTemplate template; + + @Override + public void publishShow(String topic, ShowRelationArtistAndGenreServiceMessage message) { + template.convertAndSend(topic, ShowRelationArtistAndGenreInfraMessage.from(message)); + log.info("Message published successfully to topic: {}", topic); + log.info("Message Contents ( artistIds : {}, genreIds : {} )", + message.artistIds(), + message.genreIds() + ); + } + + @Override + public void publishArtistSubscription( + String topic, + List messages + ) { + var infraMessages = messages.stream() + .map(ArtistSubscriptionInfraMessage::from) + .toList(); + + template.convertAndSend(topic, infraMessages); + log.info("Message published successfully to topic: {}", topic); + log.info("Message Contents ( artistSubscriptionMessage : {} )", infraMessages); + } + + @Override + public void publishGenreSubscription( + String topic, + List messages + ) { + var infraMessages = messages.stream() + .map(GenreSubscriptionInfraMessage::from) + .toList(); + + template.convertAndSend(topic, infraMessages); + log.info("Message published successfully to topic: {}", topic); + log.info("Message Contents ( genreSubscriptionMessage : {} )", infraMessages); + } + + @Override + public void publishTicketingReservation( + String topic, + List messages + ) { + var infraMessages = messages.stream() + .map(TicketingReservationInfraMessage::from) + .toList(); + + template.convertAndSend(topic, infraMessages); + log.info("Message published successfully to topic: {}", topic); + log.info("Message Contents ( ticketingReservationMessage : {} )", infraMessages); + } +} diff --git a/app/infrastructure/redis/build.gradle b/app/infrastructure/redis/build.gradle index f901e61e..8cc5dc6e 100644 --- a/app/infrastructure/redis/build.gradle +++ b/app/infrastructure/redis/build.gradle @@ -1,6 +1,3 @@ -bootJar.enabled = false -jar.enabled = true - dependencies { implementation project(":app:api:common-api") diff --git a/app/infrastructure/s3/build.gradle b/app/infrastructure/s3/build.gradle index 512c0fa0..ed0450d5 100644 --- a/app/infrastructure/s3/build.gradle +++ b/app/infrastructure/s3/build.gradle @@ -1,6 +1,3 @@ -bootJar.enabled = false -jar.enabled = true - dependencies { implementation project(":app:api:show-api") diff --git a/app/infrastructure/src/main/java/org/example/config/InfrastructureConfig.java b/app/infrastructure/src/main/java/org/example/config/InfrastructureConfig.java index 9f88ccf4..ee844f20 100644 --- a/app/infrastructure/src/main/java/org/example/config/InfrastructureConfig.java +++ b/app/infrastructure/src/main/java/org/example/config/InfrastructureConfig.java @@ -4,7 +4,7 @@ import org.springframework.context.annotation.Import; @Configuration -@Import({RedisConfig.class, S3Config.class}) +@Import({RedisConfig.class, PubSubConfig.class, S3Config.class}) public class InfrastructureConfig { } diff --git a/settings.gradle b/settings.gradle index b1edc9f1..9ebd3974 100644 --- a/settings.gradle +++ b/settings.gradle @@ -16,4 +16,5 @@ include(":app:api:show-api") include (":app:infrastructure") include (":app:infrastructure:redis") -include (":app:infrastructure:s3") \ No newline at end of file +include (":app:infrastructure:s3") +include (":app:infrastructure:message_queue") \ No newline at end of file