From e76c8fccccc532ab18b6fb734a56f36b1de2b7e4 Mon Sep 17 00:00:00 2001 From: dungbik Date: Fri, 3 Apr 2026 12:48:34 +0900 Subject: [PATCH 1/5] =?UTF-8?q?Refactor:=20=ED=94=84=EB=A1=9C=EC=A0=9D?= =?UTF-8?q?=ED=8A=B8=20=EA=B5=AC=EC=A1=B0=20=EB=A6=AC=ED=8C=A9=ED=86=A0?= =?UTF-8?q?=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../bookmark}/BookmarkReader.java | 25 +++-- .../application/bookmark/BookmarkService.java | 91 +++++++++++++++++ .../like}/LikeReader.java | 25 +++-- .../application/like/LikeService.java | 90 +++++++++++++++++ .../model/response/BookmarkResponse.java | 23 ----- .../response/BookmarkTargetResponse.java | 9 -- .../bookmark/service/BookmarkService.java | 98 ------------------- .../entity => domain/bookmark}/Bookmark.java | 4 +- .../bookmark}/BookmarkErrorCode.java | 4 +- .../domain/bookmark/BookmarkRepository.java | 19 ++++ .../bookmark}/BookmarkTargetType.java | 2 +- .../entity => domain/common}/BaseEntity.java | 2 +- .../common}/BizException.java | 2 +- .../common}/CommonErrorCode.java | 2 +- .../common}/ErrorCode.java | 2 +- .../{like/entity => domain/like}/Like.java | 4 +- .../like}/LikeErrorCode.java | 4 +- .../reaction/domain/like/LikeRepository.java | 19 ++++ .../like}/LikeTargetType.java | 2 +- .../config/AuditingConfig.java | 2 +- .../config/RabbitMqConfig.java | 2 +- .../grpc/CardSetGrpcClient.java | 6 +- .../grpc/GrpcConfig.java | 2 +- .../messaging}/ReactionEventPublisher.java | 5 +- .../messaging}/ReactionMessage.java | 2 +- .../BookmarkRepositoryAdapter.java | 45 +++++++++ .../persistence/LikeRepositoryAdapter.java | 44 +++++++++ .../SpringDataBookmarkRepository.java} | 11 +-- .../SpringDataLikeRepository.java} | 11 +-- .../grpc/GrpcServerConfig.java | 2 +- .../grpc/ReactionGrpcService.java | 10 +- .../http}/BookmarkController.java | 18 ++-- .../http}/LikeController.java | 18 ++-- .../http/common}/ApiResponse.java | 4 +- .../http/common}/ApiResponseAdvice.java | 2 +- .../http/common}/GlobalExceptionHandler.java | 6 +- .../http/common}/HeaderConstants.java | 2 +- .../http/common}/IdResponse.java | 2 +- .../http/common}/PagingRequest.java | 2 +- .../http/common}/PagingResponse.java | 2 +- .../dto}/request/BookmarkSearchRequest.java | 4 +- .../request/BookmarkTargetTypeRequest.java | 8 +- .../http/dto}/request/LikeSearchRequest.java | 4 +- .../dto}/request/LikeTargetTypeRequest.java | 8 +- .../http/dto/response/BookmarkResponse.java | 19 ++++ .../http/dto/response/LikeResponse.java | 19 ++++ .../like/model/response/LikeResponse.java | 23 ----- .../model/response/LikeTargetResponse.java | 9 -- .../reaction/like/service/LikeService.java | 96 ------------------ .../reaction/config/TestGrpcConfig.java | 2 +- 50 files changed, 447 insertions(+), 370 deletions(-) rename src/main/java/flipnote/reaction/{bookmark/service => application/bookmark}/BookmarkReader.java (56%) create mode 100644 src/main/java/flipnote/reaction/application/bookmark/BookmarkService.java rename src/main/java/flipnote/reaction/{like/service => application/like}/LikeReader.java (57%) create mode 100644 src/main/java/flipnote/reaction/application/like/LikeService.java delete mode 100644 src/main/java/flipnote/reaction/bookmark/model/response/BookmarkResponse.java delete mode 100644 src/main/java/flipnote/reaction/bookmark/model/response/BookmarkTargetResponse.java delete mode 100644 src/main/java/flipnote/reaction/bookmark/service/BookmarkService.java rename src/main/java/flipnote/reaction/{bookmark/entity => domain/bookmark}/Bookmark.java (93%) rename src/main/java/flipnote/reaction/{bookmark/exception => domain/bookmark}/BookmarkErrorCode.java (83%) create mode 100644 src/main/java/flipnote/reaction/domain/bookmark/BookmarkRepository.java rename src/main/java/flipnote/reaction/{bookmark/entity => domain/bookmark}/BookmarkTargetType.java (51%) rename src/main/java/flipnote/reaction/{common/entity => domain/common}/BaseEntity.java (93%) rename src/main/java/flipnote/reaction/{common/exception => domain/common}/BizException.java (79%) rename src/main/java/flipnote/reaction/{common/exception => domain/common}/CommonErrorCode.java (92%) rename src/main/java/flipnote/reaction/{common/exception => domain/common}/ErrorCode.java (67%) rename src/main/java/flipnote/reaction/{like/entity => domain/like}/Like.java (93%) rename src/main/java/flipnote/reaction/{like/exception => domain/like}/LikeErrorCode.java (83%) create mode 100644 src/main/java/flipnote/reaction/domain/like/LikeRepository.java rename src/main/java/flipnote/reaction/{like/entity => domain/like}/LikeTargetType.java (51%) rename src/main/java/flipnote/reaction/{common => infrastructure}/config/AuditingConfig.java (80%) rename src/main/java/flipnote/reaction/{common => infrastructure}/config/RabbitMqConfig.java (95%) rename src/main/java/flipnote/reaction/{common => infrastructure}/grpc/CardSetGrpcClient.java (92%) rename src/main/java/flipnote/reaction/{common => infrastructure}/grpc/GrpcConfig.java (91%) rename src/main/java/flipnote/reaction/{common/event => infrastructure/messaging}/ReactionEventPublisher.java (82%) rename src/main/java/flipnote/reaction/{common/model/event => infrastructure/messaging}/ReactionMessage.java (67%) create mode 100644 src/main/java/flipnote/reaction/infrastructure/persistence/BookmarkRepositoryAdapter.java create mode 100644 src/main/java/flipnote/reaction/infrastructure/persistence/LikeRepositoryAdapter.java rename src/main/java/flipnote/reaction/{bookmark/repository/BookmarkRepository.java => infrastructure/persistence/SpringDataBookmarkRepository.java} (57%) rename src/main/java/flipnote/reaction/{like/repository/LikeRepository.java => infrastructure/persistence/SpringDataLikeRepository.java} (59%) rename src/main/java/flipnote/reaction/{common => interfaces}/grpc/GrpcServerConfig.java (96%) rename src/main/java/flipnote/reaction/{common => interfaces}/grpc/ReactionGrpcService.java (90%) rename src/main/java/flipnote/reaction/{bookmark/controller => interfaces/http}/BookmarkController.java (74%) rename src/main/java/flipnote/reaction/{like/controller => interfaces/http}/LikeController.java (74%) rename src/main/java/flipnote/reaction/{common/model/response => interfaces/http/common}/ApiResponse.java (93%) rename src/main/java/flipnote/reaction/{common/model/response => interfaces/http/common}/ApiResponseAdvice.java (96%) rename src/main/java/flipnote/reaction/{common/exception => interfaces/http/common}/GlobalExceptionHandler.java (91%) rename src/main/java/flipnote/reaction/{common/constants => interfaces/http/common}/HeaderConstants.java (80%) rename src/main/java/flipnote/reaction/{common/model/response => interfaces/http/common}/IdResponse.java (69%) rename src/main/java/flipnote/reaction/{common/model/request => interfaces/http/common}/PagingRequest.java (94%) rename src/main/java/flipnote/reaction/{common/model/response => interfaces/http/common}/PagingResponse.java (91%) rename src/main/java/flipnote/reaction/{bookmark/model => interfaces/http/dto}/request/BookmarkSearchRequest.java (51%) rename src/main/java/flipnote/reaction/{bookmark/model => interfaces/http/dto}/request/BookmarkTargetTypeRequest.java (64%) rename src/main/java/flipnote/reaction/{like/model => interfaces/http/dto}/request/LikeSearchRequest.java (50%) rename src/main/java/flipnote/reaction/{like/model => interfaces/http/dto}/request/LikeTargetTypeRequest.java (64%) create mode 100644 src/main/java/flipnote/reaction/interfaces/http/dto/response/BookmarkResponse.java create mode 100644 src/main/java/flipnote/reaction/interfaces/http/dto/response/LikeResponse.java delete mode 100644 src/main/java/flipnote/reaction/like/model/response/LikeResponse.java delete mode 100644 src/main/java/flipnote/reaction/like/model/response/LikeTargetResponse.java delete mode 100644 src/main/java/flipnote/reaction/like/service/LikeService.java diff --git a/src/main/java/flipnote/reaction/bookmark/service/BookmarkReader.java b/src/main/java/flipnote/reaction/application/bookmark/BookmarkReader.java similarity index 56% rename from src/main/java/flipnote/reaction/bookmark/service/BookmarkReader.java rename to src/main/java/flipnote/reaction/application/bookmark/BookmarkReader.java index aa74c36..6c401ac 100644 --- a/src/main/java/flipnote/reaction/bookmark/service/BookmarkReader.java +++ b/src/main/java/flipnote/reaction/application/bookmark/BookmarkReader.java @@ -1,17 +1,16 @@ -package flipnote.reaction.bookmark.service; +package flipnote.reaction.application.bookmark; import java.util.List; import java.util.Map; -import java.util.Set; import java.util.stream.Collectors; import org.springframework.stereotype.Component; -import flipnote.reaction.bookmark.entity.Bookmark; -import flipnote.reaction.bookmark.entity.BookmarkTargetType; -import flipnote.reaction.bookmark.exception.BookmarkErrorCode; -import flipnote.reaction.bookmark.repository.BookmarkRepository; -import flipnote.reaction.common.exception.BizException; +import flipnote.reaction.domain.bookmark.Bookmark; +import flipnote.reaction.domain.bookmark.BookmarkErrorCode; +import flipnote.reaction.domain.bookmark.BookmarkRepository; +import flipnote.reaction.domain.bookmark.BookmarkTargetType; +import flipnote.reaction.domain.common.BizException; import lombok.RequiredArgsConstructor; @Component @@ -29,13 +28,11 @@ public boolean isBookmarked(BookmarkTargetType targetType, Long targetId, Long u return bookmarkRepository.existsByTargetTypeAndTargetIdAndUserId(targetType, targetId, userId); } - public Map areBookmarked(BookmarkTargetType targetType, List targetIds, Long userId) { - Set bookmarkedIds = bookmarkRepository.findByTargetTypeAndTargetIdInAndUserId(targetType, targetIds, userId) - .stream() - .map(Bookmark::getTargetId) - .collect(Collectors.toSet()); - + public Map areBookmarked(BookmarkTargetType targetType, List targetIds, long userId) { return targetIds.stream() - .collect(Collectors.toMap(id -> id, bookmarkedIds::contains)); + .collect(Collectors.toMap( + targetId -> targetId, + targetId -> bookmarkRepository.existsByTargetTypeAndTargetIdAndUserId(targetType, targetId, userId) + )); } } diff --git a/src/main/java/flipnote/reaction/application/bookmark/BookmarkService.java b/src/main/java/flipnote/reaction/application/bookmark/BookmarkService.java new file mode 100644 index 0000000..12a33f9 --- /dev/null +++ b/src/main/java/flipnote/reaction/application/bookmark/BookmarkService.java @@ -0,0 +1,91 @@ +package flipnote.reaction.application.bookmark; + +import org.springframework.amqp.rabbit.core.RabbitTemplate; +import org.springframework.dao.DataIntegrityViolationException; +import org.springframework.data.domain.Page; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import flipnote.reaction.domain.bookmark.Bookmark; +import flipnote.reaction.domain.bookmark.BookmarkErrorCode; +import flipnote.reaction.domain.bookmark.BookmarkRepository; +import flipnote.reaction.domain.bookmark.BookmarkTargetType; +import flipnote.reaction.domain.common.BizException; +import flipnote.reaction.infrastructure.config.RabbitMqConfig; +import flipnote.reaction.infrastructure.messaging.ReactionMessage; +import flipnote.reaction.interfaces.http.common.IdResponse; +import flipnote.reaction.interfaces.http.common.PagingResponse; +import flipnote.reaction.interfaces.http.dto.request.BookmarkSearchRequest; +import flipnote.reaction.interfaces.http.dto.response.BookmarkResponse; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@Service +@RequiredArgsConstructor +@Transactional(readOnly = true) +public class BookmarkService { + + private final BookmarkRepository bookmarkRepository; + private final BookmarkReader bookmarkReader; + private final RabbitTemplate rabbitTemplate; + + @Transactional + public IdResponse addBookmark(BookmarkTargetType targetType, Long targetId, Long userId) { + // TODO: gRPC로 대상 존재 여부 검증 (CardSet/Group 서비스 호출) + + if (bookmarkReader.isBookmarked(targetType, targetId, userId)) { + throw new BizException(BookmarkErrorCode.ALREADY_BOOKMARKED); + } + + Bookmark bookmark = Bookmark.builder() + .targetType(targetType) + .targetId(targetId) + .userId(userId) + .build(); + + try { + bookmarkRepository.save(bookmark); + } catch (DataIntegrityViolationException e) { + throw new BizException(BookmarkErrorCode.ALREADY_BOOKMARKED); + } + + publishEvent(RabbitMqConfig.ROUTING_KEY_BOOKMARK_ADDED, "BOOKMARK_ADDED", targetType, targetId, userId); + + return IdResponse.from(bookmark.getId()); + } + + @Transactional + public void removeBookmark(BookmarkTargetType targetType, Long targetId, Long userId) { + Bookmark bookmark = bookmarkReader.findByTargetAndUserId(targetType, targetId, userId); + bookmarkRepository.delete(bookmark); + + publishEvent(RabbitMqConfig.ROUTING_KEY_BOOKMARK_REMOVED, "BOOKMARK_REMOVED", targetType, targetId, userId); + } + + public PagingResponse getBookmarks(BookmarkTargetType targetType, Long userId, + BookmarkSearchRequest request) { + Page bookmarkPage = bookmarkRepository.findByTargetTypeAndUserId( + targetType, userId, request.getPageRequest() + ); + + // TODO: gRPC로 대상 상세 정보 fetch (CardSet 서비스 호출) + + Page responsePage = bookmarkPage.map(BookmarkResponse::from); + return PagingResponse.from(responsePage); + } + + private void publishEvent(String routingKey, String eventType, + BookmarkTargetType targetType, Long targetId, Long userId) { + try { + rabbitTemplate.convertAndSend( + RabbitMqConfig.EXCHANGE, + routingKey, + new ReactionMessage(eventType, targetType.name(), targetId, userId) + ); + } catch (Exception e) { + log.error("북마크 이벤트 발행 실패: eventType={}, targetType={}, targetId={}, userId={}", + eventType, targetType, targetId, userId, e); + } + } +} diff --git a/src/main/java/flipnote/reaction/like/service/LikeReader.java b/src/main/java/flipnote/reaction/application/like/LikeReader.java similarity index 57% rename from src/main/java/flipnote/reaction/like/service/LikeReader.java rename to src/main/java/flipnote/reaction/application/like/LikeReader.java index fc8c31b..e0501db 100644 --- a/src/main/java/flipnote/reaction/like/service/LikeReader.java +++ b/src/main/java/flipnote/reaction/application/like/LikeReader.java @@ -1,17 +1,16 @@ -package flipnote.reaction.like.service; +package flipnote.reaction.application.like; import java.util.List; import java.util.Map; -import java.util.Set; import java.util.stream.Collectors; import org.springframework.stereotype.Component; -import flipnote.reaction.common.exception.BizException; -import flipnote.reaction.like.entity.Like; -import flipnote.reaction.like.entity.LikeTargetType; -import flipnote.reaction.like.exception.LikeErrorCode; -import flipnote.reaction.like.repository.LikeRepository; +import flipnote.reaction.domain.common.BizException; +import flipnote.reaction.domain.like.Like; +import flipnote.reaction.domain.like.LikeErrorCode; +import flipnote.reaction.domain.like.LikeRepository; +import flipnote.reaction.domain.like.LikeTargetType; import lombok.RequiredArgsConstructor; @Component @@ -29,13 +28,11 @@ public boolean isLiked(LikeTargetType targetType, Long targetId, Long userId) { return likeRepository.existsByTargetTypeAndTargetIdAndUserId(targetType, targetId, userId); } - public Map areLiked(LikeTargetType targetType, List targetIds, Long userId) { - Set likedIds = likeRepository.findByTargetTypeAndTargetIdInAndUserId(targetType, targetIds, userId) - .stream() - .map(Like::getTargetId) - .collect(Collectors.toSet()); - + public Map areLiked(LikeTargetType targetType, List targetIds, long userId) { return targetIds.stream() - .collect(Collectors.toMap(id -> id, likedIds::contains)); + .collect(Collectors.toMap( + targetId -> targetId, + targetId -> likeRepository.existsByTargetTypeAndTargetIdAndUserId(targetType, targetId, userId) + )); } } diff --git a/src/main/java/flipnote/reaction/application/like/LikeService.java b/src/main/java/flipnote/reaction/application/like/LikeService.java new file mode 100644 index 0000000..a896ff9 --- /dev/null +++ b/src/main/java/flipnote/reaction/application/like/LikeService.java @@ -0,0 +1,90 @@ +package flipnote.reaction.application.like; + +import org.springframework.amqp.rabbit.core.RabbitTemplate; +import org.springframework.dao.DataIntegrityViolationException; +import org.springframework.data.domain.Page; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import flipnote.reaction.domain.common.BizException; +import flipnote.reaction.domain.like.Like; +import flipnote.reaction.domain.like.LikeErrorCode; +import flipnote.reaction.domain.like.LikeRepository; +import flipnote.reaction.domain.like.LikeTargetType; +import flipnote.reaction.infrastructure.config.RabbitMqConfig; +import flipnote.reaction.infrastructure.messaging.ReactionMessage; +import flipnote.reaction.interfaces.http.common.IdResponse; +import flipnote.reaction.interfaces.http.common.PagingResponse; +import flipnote.reaction.interfaces.http.dto.request.LikeSearchRequest; +import flipnote.reaction.interfaces.http.dto.response.LikeResponse; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@Service +@RequiredArgsConstructor +@Transactional(readOnly = true) +public class LikeService { + + private final LikeRepository likeRepository; + private final LikeReader likeReader; + private final RabbitTemplate rabbitTemplate; + + @Transactional + public IdResponse addLike(LikeTargetType targetType, Long targetId, Long userId) { + // TODO: gRPC로 대상 존재 여부 검증 (CardSet/Group 서비스 호출) + + if (likeReader.isLiked(targetType, targetId, userId)) { + throw new BizException(LikeErrorCode.ALREADY_LIKED); + } + + Like like = Like.builder() + .targetType(targetType) + .targetId(targetId) + .userId(userId) + .build(); + + try { + likeRepository.save(like); + } catch (DataIntegrityViolationException e) { + throw new BizException(LikeErrorCode.ALREADY_LIKED); + } + + publishEvent(RabbitMqConfig.ROUTING_KEY_LIKE_ADDED, "LIKE_ADDED", targetType, targetId, userId); + + return IdResponse.from(like.getId()); + } + + @Transactional + public void removeLike(LikeTargetType targetType, Long targetId, Long userId) { + Like like = likeReader.findByTargetAndUserId(targetType, targetId, userId); + likeRepository.delete(like); + + publishEvent(RabbitMqConfig.ROUTING_KEY_LIKE_REMOVED, "LIKE_REMOVED", targetType, targetId, userId); + } + + public PagingResponse getLikes(LikeTargetType targetType, Long userId, LikeSearchRequest request) { + Page likePage = likeRepository.findByTargetTypeAndUserId( + targetType, userId, request.getPageRequest() + ); + + // TODO: gRPC로 대상 상세 정보 fetch (CardSet 서비스 호출) + + Page responsePage = likePage.map(LikeResponse::from); + return PagingResponse.from(responsePage); + } + + private void publishEvent(String routingKey, String eventType, + LikeTargetType targetType, Long targetId, Long userId) { + try { + rabbitTemplate.convertAndSend( + RabbitMqConfig.EXCHANGE, + routingKey, + new ReactionMessage(eventType, targetType.name(), targetId, userId) + ); + } catch (Exception e) { + log.error("좋아요 이벤트 발행 실패: eventType={}, targetType={}, targetId={}, userId={}", + eventType, targetType, targetId, userId, e); + } + } +} diff --git a/src/main/java/flipnote/reaction/bookmark/model/response/BookmarkResponse.java b/src/main/java/flipnote/reaction/bookmark/model/response/BookmarkResponse.java deleted file mode 100644 index 5034076..0000000 --- a/src/main/java/flipnote/reaction/bookmark/model/response/BookmarkResponse.java +++ /dev/null @@ -1,23 +0,0 @@ -package flipnote.reaction.bookmark.model.response; - -import java.time.LocalDateTime; - -import cardset.Cardset.CardSetSummary; -import flipnote.reaction.bookmark.entity.Bookmark; - -public record BookmarkResponse( - BookmarkTargetResponse target, - LocalDateTime bookmarkedAt -) { - public static BookmarkResponse from(Bookmark bookmark, CardSetSummary summary) { - return new BookmarkResponse( - new BookmarkTargetResponse( - bookmark.getTargetType().name(), - bookmark.getTargetId(), - summary != null ? summary.getGroupId() : null, - summary != null ? summary.getName() : null - ), - bookmark.getCreatedAt() - ); - } -} diff --git a/src/main/java/flipnote/reaction/bookmark/model/response/BookmarkTargetResponse.java b/src/main/java/flipnote/reaction/bookmark/model/response/BookmarkTargetResponse.java deleted file mode 100644 index b62838f..0000000 --- a/src/main/java/flipnote/reaction/bookmark/model/response/BookmarkTargetResponse.java +++ /dev/null @@ -1,9 +0,0 @@ -package flipnote.reaction.bookmark.model.response; - -public record BookmarkTargetResponse( - String type, - Long id, - Long groupId, - String name -) { -} diff --git a/src/main/java/flipnote/reaction/bookmark/service/BookmarkService.java b/src/main/java/flipnote/reaction/bookmark/service/BookmarkService.java deleted file mode 100644 index ca7f7f7..0000000 --- a/src/main/java/flipnote/reaction/bookmark/service/BookmarkService.java +++ /dev/null @@ -1,98 +0,0 @@ -package flipnote.reaction.bookmark.service; - -import java.util.List; -import java.util.Map; - -import org.springframework.dao.DataIntegrityViolationException; -import org.springframework.data.domain.Page; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -import cardset.Cardset; -import cardset.Cardset.CardSetSummary; -import flipnote.reaction.bookmark.entity.Bookmark; -import flipnote.reaction.bookmark.entity.BookmarkTargetType; -import flipnote.reaction.bookmark.exception.BookmarkErrorCode; -import flipnote.reaction.bookmark.model.request.BookmarkSearchRequest; -import flipnote.reaction.bookmark.model.response.BookmarkResponse; -import flipnote.reaction.bookmark.repository.BookmarkRepository; -import flipnote.reaction.common.config.RabbitMqConfig; -import flipnote.reaction.common.event.ReactionEventPublisher; -import flipnote.reaction.common.exception.BizException; -import flipnote.reaction.common.exception.CommonErrorCode; -import flipnote.reaction.common.grpc.CardSetGrpcClient; -import flipnote.reaction.common.model.response.IdResponse; -import flipnote.reaction.common.model.response.PagingResponse; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; - -@Slf4j -@Service -@RequiredArgsConstructor -@Transactional(readOnly = true) -public class BookmarkService { - - private final BookmarkRepository bookmarkRepository; - private final BookmarkReader bookmarkReader; - private final ReactionEventPublisher eventPublisher; - private final CardSetGrpcClient cardSetGrpcClient; - - @Transactional - public IdResponse addBookmark(BookmarkTargetType targetType, Long targetId, Long userId) { - if (targetType == BookmarkTargetType.CARD_SET) { - if (!cardSetGrpcClient.isCardSetViewable(targetId, userId)) { - throw new BizException(CommonErrorCode.TARGET_NOT_VIEWABLE); - } - } - - if (bookmarkReader.isBookmarked(targetType, targetId, userId)) { - throw new BizException(BookmarkErrorCode.ALREADY_BOOKMARKED); - } - - Bookmark bookmark = Bookmark.builder() - .targetType(targetType) - .targetId(targetId) - .userId(userId) - .build(); - - try { - bookmarkRepository.save(bookmark); - } catch (DataIntegrityViolationException e) { - throw new BizException(BookmarkErrorCode.ALREADY_BOOKMARKED); - } - - eventPublisher.publish(RabbitMqConfig.ROUTING_KEY_BOOKMARK_ADDED, "BOOKMARK_ADDED", - targetType.name(), targetId, userId); - - return IdResponse.from(bookmark.getId()); - } - - @Transactional - public void removeBookmark(BookmarkTargetType targetType, Long targetId, Long userId) { - Bookmark bookmark = bookmarkReader.findByTargetAndUserId(targetType, targetId, userId); - bookmarkRepository.delete(bookmark); - - eventPublisher.publish(RabbitMqConfig.ROUTING_KEY_BOOKMARK_REMOVED, "BOOKMARK_REMOVED", - targetType.name(), targetId, userId); - } - - public PagingResponse getBookmarks(BookmarkTargetType targetType, Long userId, - BookmarkSearchRequest request) { - Page bookmarkPage = bookmarkRepository.findByTargetTypeAndUserId( - targetType, userId, request.getPageRequest() - ); - - List targetIds = bookmarkPage.getContent().stream() - .map(Bookmark::getTargetId) - .toList(); - - Map summaryMap = targetIds.isEmpty() - ? Map.of() - : cardSetGrpcClient.getCardSetsByIds(targetIds, userId); - - Page responsePage = bookmarkPage.map( - bookmark -> BookmarkResponse.from(bookmark, summaryMap.get(bookmark.getTargetId())) - ); - return PagingResponse.from(responsePage); - } -} diff --git a/src/main/java/flipnote/reaction/bookmark/entity/Bookmark.java b/src/main/java/flipnote/reaction/domain/bookmark/Bookmark.java similarity index 93% rename from src/main/java/flipnote/reaction/bookmark/entity/Bookmark.java rename to src/main/java/flipnote/reaction/domain/bookmark/Bookmark.java index cb6f125..eee6a1e 100644 --- a/src/main/java/flipnote/reaction/bookmark/entity/Bookmark.java +++ b/src/main/java/flipnote/reaction/domain/bookmark/Bookmark.java @@ -1,6 +1,6 @@ -package flipnote.reaction.bookmark.entity; +package flipnote.reaction.domain.bookmark; -import flipnote.reaction.common.entity.BaseEntity; +import flipnote.reaction.domain.common.BaseEntity; import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.EnumType; diff --git a/src/main/java/flipnote/reaction/bookmark/exception/BookmarkErrorCode.java b/src/main/java/flipnote/reaction/domain/bookmark/BookmarkErrorCode.java similarity index 83% rename from src/main/java/flipnote/reaction/bookmark/exception/BookmarkErrorCode.java rename to src/main/java/flipnote/reaction/domain/bookmark/BookmarkErrorCode.java index e9eb2e3..f9a0d81 100644 --- a/src/main/java/flipnote/reaction/bookmark/exception/BookmarkErrorCode.java +++ b/src/main/java/flipnote/reaction/domain/bookmark/BookmarkErrorCode.java @@ -1,6 +1,6 @@ -package flipnote.reaction.bookmark.exception; +package flipnote.reaction.domain.bookmark; -import flipnote.reaction.common.exception.ErrorCode; +import flipnote.reaction.domain.common.ErrorCode; import lombok.Getter; import lombok.RequiredArgsConstructor; diff --git a/src/main/java/flipnote/reaction/domain/bookmark/BookmarkRepository.java b/src/main/java/flipnote/reaction/domain/bookmark/BookmarkRepository.java new file mode 100644 index 0000000..ee3769d --- /dev/null +++ b/src/main/java/flipnote/reaction/domain/bookmark/BookmarkRepository.java @@ -0,0 +1,19 @@ +package flipnote.reaction.domain.bookmark; + +import java.util.Optional; + +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; + +public interface BookmarkRepository { + + boolean existsByTargetTypeAndTargetIdAndUserId(BookmarkTargetType targetType, Long targetId, Long userId); + + Optional findByTargetTypeAndTargetIdAndUserId(BookmarkTargetType targetType, Long targetId, Long userId); + + Page findByTargetTypeAndUserId(BookmarkTargetType targetType, Long userId, Pageable pageable); + + Bookmark save(Bookmark bookmark); + + void delete(Bookmark bookmark); +} diff --git a/src/main/java/flipnote/reaction/bookmark/entity/BookmarkTargetType.java b/src/main/java/flipnote/reaction/domain/bookmark/BookmarkTargetType.java similarity index 51% rename from src/main/java/flipnote/reaction/bookmark/entity/BookmarkTargetType.java rename to src/main/java/flipnote/reaction/domain/bookmark/BookmarkTargetType.java index 1dfecab..60b01f9 100644 --- a/src/main/java/flipnote/reaction/bookmark/entity/BookmarkTargetType.java +++ b/src/main/java/flipnote/reaction/domain/bookmark/BookmarkTargetType.java @@ -1,4 +1,4 @@ -package flipnote.reaction.bookmark.entity; +package flipnote.reaction.domain.bookmark; public enum BookmarkTargetType { CARD_SET diff --git a/src/main/java/flipnote/reaction/common/entity/BaseEntity.java b/src/main/java/flipnote/reaction/domain/common/BaseEntity.java similarity index 93% rename from src/main/java/flipnote/reaction/common/entity/BaseEntity.java rename to src/main/java/flipnote/reaction/domain/common/BaseEntity.java index 094ff68..07b0bc6 100644 --- a/src/main/java/flipnote/reaction/common/entity/BaseEntity.java +++ b/src/main/java/flipnote/reaction/domain/common/BaseEntity.java @@ -1,4 +1,4 @@ -package flipnote.reaction.common.entity; +package flipnote.reaction.domain.common; import java.time.LocalDateTime; diff --git a/src/main/java/flipnote/reaction/common/exception/BizException.java b/src/main/java/flipnote/reaction/domain/common/BizException.java similarity index 79% rename from src/main/java/flipnote/reaction/common/exception/BizException.java rename to src/main/java/flipnote/reaction/domain/common/BizException.java index db85b00..7f34bc2 100644 --- a/src/main/java/flipnote/reaction/common/exception/BizException.java +++ b/src/main/java/flipnote/reaction/domain/common/BizException.java @@ -1,4 +1,4 @@ -package flipnote.reaction.common.exception; +package flipnote.reaction.domain.common; import lombok.AllArgsConstructor; import lombok.Getter; diff --git a/src/main/java/flipnote/reaction/common/exception/CommonErrorCode.java b/src/main/java/flipnote/reaction/domain/common/CommonErrorCode.java similarity index 92% rename from src/main/java/flipnote/reaction/common/exception/CommonErrorCode.java rename to src/main/java/flipnote/reaction/domain/common/CommonErrorCode.java index 349b6f4..c3e21ff 100644 --- a/src/main/java/flipnote/reaction/common/exception/CommonErrorCode.java +++ b/src/main/java/flipnote/reaction/domain/common/CommonErrorCode.java @@ -1,4 +1,4 @@ -package flipnote.reaction.common.exception; +package flipnote.reaction.domain.common; import lombok.Getter; import lombok.RequiredArgsConstructor; diff --git a/src/main/java/flipnote/reaction/common/exception/ErrorCode.java b/src/main/java/flipnote/reaction/domain/common/ErrorCode.java similarity index 67% rename from src/main/java/flipnote/reaction/common/exception/ErrorCode.java rename to src/main/java/flipnote/reaction/domain/common/ErrorCode.java index 5841bc8..5bde461 100644 --- a/src/main/java/flipnote/reaction/common/exception/ErrorCode.java +++ b/src/main/java/flipnote/reaction/domain/common/ErrorCode.java @@ -1,4 +1,4 @@ -package flipnote.reaction.common.exception; +package flipnote.reaction.domain.common; public interface ErrorCode { int getStatus(); diff --git a/src/main/java/flipnote/reaction/like/entity/Like.java b/src/main/java/flipnote/reaction/domain/like/Like.java similarity index 93% rename from src/main/java/flipnote/reaction/like/entity/Like.java rename to src/main/java/flipnote/reaction/domain/like/Like.java index 446662c..3863da9 100644 --- a/src/main/java/flipnote/reaction/like/entity/Like.java +++ b/src/main/java/flipnote/reaction/domain/like/Like.java @@ -1,6 +1,6 @@ -package flipnote.reaction.like.entity; +package flipnote.reaction.domain.like; -import flipnote.reaction.common.entity.BaseEntity; +import flipnote.reaction.domain.common.BaseEntity; import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.EnumType; diff --git a/src/main/java/flipnote/reaction/like/exception/LikeErrorCode.java b/src/main/java/flipnote/reaction/domain/like/LikeErrorCode.java similarity index 83% rename from src/main/java/flipnote/reaction/like/exception/LikeErrorCode.java rename to src/main/java/flipnote/reaction/domain/like/LikeErrorCode.java index d69903b..f7733f3 100644 --- a/src/main/java/flipnote/reaction/like/exception/LikeErrorCode.java +++ b/src/main/java/flipnote/reaction/domain/like/LikeErrorCode.java @@ -1,6 +1,6 @@ -package flipnote.reaction.like.exception; +package flipnote.reaction.domain.like; -import flipnote.reaction.common.exception.ErrorCode; +import flipnote.reaction.domain.common.ErrorCode; import lombok.Getter; import lombok.RequiredArgsConstructor; diff --git a/src/main/java/flipnote/reaction/domain/like/LikeRepository.java b/src/main/java/flipnote/reaction/domain/like/LikeRepository.java new file mode 100644 index 0000000..8d32323 --- /dev/null +++ b/src/main/java/flipnote/reaction/domain/like/LikeRepository.java @@ -0,0 +1,19 @@ +package flipnote.reaction.domain.like; + +import java.util.Optional; + +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; + +public interface LikeRepository { + + boolean existsByTargetTypeAndTargetIdAndUserId(LikeTargetType targetType, Long targetId, Long userId); + + Optional findByTargetTypeAndTargetIdAndUserId(LikeTargetType targetType, Long targetId, Long userId); + + Page findByTargetTypeAndUserId(LikeTargetType targetType, Long userId, Pageable pageable); + + Like save(Like like); + + void delete(Like like); +} diff --git a/src/main/java/flipnote/reaction/like/entity/LikeTargetType.java b/src/main/java/flipnote/reaction/domain/like/LikeTargetType.java similarity index 51% rename from src/main/java/flipnote/reaction/like/entity/LikeTargetType.java rename to src/main/java/flipnote/reaction/domain/like/LikeTargetType.java index b23ce70..45e81ba 100644 --- a/src/main/java/flipnote/reaction/like/entity/LikeTargetType.java +++ b/src/main/java/flipnote/reaction/domain/like/LikeTargetType.java @@ -1,4 +1,4 @@ -package flipnote.reaction.like.entity; +package flipnote.reaction.domain.like; public enum LikeTargetType { CARD_SET diff --git a/src/main/java/flipnote/reaction/common/config/AuditingConfig.java b/src/main/java/flipnote/reaction/infrastructure/config/AuditingConfig.java similarity index 80% rename from src/main/java/flipnote/reaction/common/config/AuditingConfig.java rename to src/main/java/flipnote/reaction/infrastructure/config/AuditingConfig.java index e930b9a..f41d87f 100644 --- a/src/main/java/flipnote/reaction/common/config/AuditingConfig.java +++ b/src/main/java/flipnote/reaction/infrastructure/config/AuditingConfig.java @@ -1,4 +1,4 @@ -package flipnote.reaction.common.config; +package flipnote.reaction.infrastructure.config; import org.springframework.context.annotation.Configuration; import org.springframework.data.jpa.repository.config.EnableJpaAuditing; diff --git a/src/main/java/flipnote/reaction/common/config/RabbitMqConfig.java b/src/main/java/flipnote/reaction/infrastructure/config/RabbitMqConfig.java similarity index 95% rename from src/main/java/flipnote/reaction/common/config/RabbitMqConfig.java rename to src/main/java/flipnote/reaction/infrastructure/config/RabbitMqConfig.java index 563ee22..b7f16b0 100644 --- a/src/main/java/flipnote/reaction/common/config/RabbitMqConfig.java +++ b/src/main/java/flipnote/reaction/infrastructure/config/RabbitMqConfig.java @@ -1,4 +1,4 @@ -package flipnote.reaction.common.config; +package flipnote.reaction.infrastructure.config; import org.springframework.amqp.core.TopicExchange; import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter; diff --git a/src/main/java/flipnote/reaction/common/grpc/CardSetGrpcClient.java b/src/main/java/flipnote/reaction/infrastructure/grpc/CardSetGrpcClient.java similarity index 92% rename from src/main/java/flipnote/reaction/common/grpc/CardSetGrpcClient.java rename to src/main/java/flipnote/reaction/infrastructure/grpc/CardSetGrpcClient.java index ab5104c..68166b4 100644 --- a/src/main/java/flipnote/reaction/common/grpc/CardSetGrpcClient.java +++ b/src/main/java/flipnote/reaction/infrastructure/grpc/CardSetGrpcClient.java @@ -1,4 +1,4 @@ -package flipnote.reaction.common.grpc; +package flipnote.reaction.infrastructure.grpc; import java.util.List; import java.util.Map; @@ -13,8 +13,8 @@ import cardset.Cardset.IsCardSetViewableRequest; import cardset.Cardset.IsCardSetViewableResponse; import cardset.CardsetServiceGrpc; -import flipnote.reaction.common.exception.BizException; -import flipnote.reaction.common.exception.CommonErrorCode; +import flipnote.reaction.domain.common.BizException; +import flipnote.reaction.domain.common.CommonErrorCode; import io.grpc.ManagedChannel; import io.grpc.StatusRuntimeException; import lombok.extern.slf4j.Slf4j; diff --git a/src/main/java/flipnote/reaction/common/grpc/GrpcConfig.java b/src/main/java/flipnote/reaction/infrastructure/grpc/GrpcConfig.java similarity index 91% rename from src/main/java/flipnote/reaction/common/grpc/GrpcConfig.java rename to src/main/java/flipnote/reaction/infrastructure/grpc/GrpcConfig.java index d131067..823c404 100644 --- a/src/main/java/flipnote/reaction/common/grpc/GrpcConfig.java +++ b/src/main/java/flipnote/reaction/infrastructure/grpc/GrpcConfig.java @@ -1,4 +1,4 @@ -package flipnote.reaction.common.grpc; +package flipnote.reaction.infrastructure.grpc; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; diff --git a/src/main/java/flipnote/reaction/common/event/ReactionEventPublisher.java b/src/main/java/flipnote/reaction/infrastructure/messaging/ReactionEventPublisher.java similarity index 82% rename from src/main/java/flipnote/reaction/common/event/ReactionEventPublisher.java rename to src/main/java/flipnote/reaction/infrastructure/messaging/ReactionEventPublisher.java index 92729c0..4c2cb5f 100644 --- a/src/main/java/flipnote/reaction/common/event/ReactionEventPublisher.java +++ b/src/main/java/flipnote/reaction/infrastructure/messaging/ReactionEventPublisher.java @@ -1,10 +1,9 @@ -package flipnote.reaction.common.event; +package flipnote.reaction.infrastructure.messaging; import org.springframework.amqp.rabbit.core.RabbitTemplate; import org.springframework.stereotype.Component; -import flipnote.reaction.common.config.RabbitMqConfig; -import flipnote.reaction.common.model.event.ReactionMessage; +import flipnote.reaction.infrastructure.config.RabbitMqConfig; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; diff --git a/src/main/java/flipnote/reaction/common/model/event/ReactionMessage.java b/src/main/java/flipnote/reaction/infrastructure/messaging/ReactionMessage.java similarity index 67% rename from src/main/java/flipnote/reaction/common/model/event/ReactionMessage.java rename to src/main/java/flipnote/reaction/infrastructure/messaging/ReactionMessage.java index e55b025..015059d 100644 --- a/src/main/java/flipnote/reaction/common/model/event/ReactionMessage.java +++ b/src/main/java/flipnote/reaction/infrastructure/messaging/ReactionMessage.java @@ -1,4 +1,4 @@ -package flipnote.reaction.common.model.event; +package flipnote.reaction.infrastructure.messaging; public record ReactionMessage( String eventType, diff --git a/src/main/java/flipnote/reaction/infrastructure/persistence/BookmarkRepositoryAdapter.java b/src/main/java/flipnote/reaction/infrastructure/persistence/BookmarkRepositoryAdapter.java new file mode 100644 index 0000000..bc63c24 --- /dev/null +++ b/src/main/java/flipnote/reaction/infrastructure/persistence/BookmarkRepositoryAdapter.java @@ -0,0 +1,45 @@ +package flipnote.reaction.infrastructure.persistence; + +import java.util.Optional; + +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Repository; + +import flipnote.reaction.domain.bookmark.Bookmark; +import flipnote.reaction.domain.bookmark.BookmarkRepository; +import flipnote.reaction.domain.bookmark.BookmarkTargetType; +import lombok.RequiredArgsConstructor; + +@Repository +@RequiredArgsConstructor +public class BookmarkRepositoryAdapter implements BookmarkRepository { + + private final SpringDataBookmarkRepository springDataBookmarkRepository; + + @Override + public boolean existsByTargetTypeAndTargetIdAndUserId(BookmarkTargetType targetType, Long targetId, Long userId) { + return springDataBookmarkRepository.existsByTargetTypeAndTargetIdAndUserId(targetType, targetId, userId); + } + + @Override + public Optional findByTargetTypeAndTargetIdAndUserId(BookmarkTargetType targetType, Long targetId, + Long userId) { + return springDataBookmarkRepository.findByTargetTypeAndTargetIdAndUserId(targetType, targetId, userId); + } + + @Override + public Page findByTargetTypeAndUserId(BookmarkTargetType targetType, Long userId, Pageable pageable) { + return springDataBookmarkRepository.findByTargetTypeAndUserId(targetType, userId, pageable); + } + + @Override + public Bookmark save(Bookmark bookmark) { + return springDataBookmarkRepository.save(bookmark); + } + + @Override + public void delete(Bookmark bookmark) { + springDataBookmarkRepository.delete(bookmark); + } +} diff --git a/src/main/java/flipnote/reaction/infrastructure/persistence/LikeRepositoryAdapter.java b/src/main/java/flipnote/reaction/infrastructure/persistence/LikeRepositoryAdapter.java new file mode 100644 index 0000000..ec643f0 --- /dev/null +++ b/src/main/java/flipnote/reaction/infrastructure/persistence/LikeRepositoryAdapter.java @@ -0,0 +1,44 @@ +package flipnote.reaction.infrastructure.persistence; + +import java.util.Optional; + +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Repository; + +import flipnote.reaction.domain.like.Like; +import flipnote.reaction.domain.like.LikeRepository; +import flipnote.reaction.domain.like.LikeTargetType; +import lombok.RequiredArgsConstructor; + +@Repository +@RequiredArgsConstructor +public class LikeRepositoryAdapter implements LikeRepository { + + private final SpringDataLikeRepository springDataLikeRepository; + + @Override + public boolean existsByTargetTypeAndTargetIdAndUserId(LikeTargetType targetType, Long targetId, Long userId) { + return springDataLikeRepository.existsByTargetTypeAndTargetIdAndUserId(targetType, targetId, userId); + } + + @Override + public Optional findByTargetTypeAndTargetIdAndUserId(LikeTargetType targetType, Long targetId, Long userId) { + return springDataLikeRepository.findByTargetTypeAndTargetIdAndUserId(targetType, targetId, userId); + } + + @Override + public Page findByTargetTypeAndUserId(LikeTargetType targetType, Long userId, Pageable pageable) { + return springDataLikeRepository.findByTargetTypeAndUserId(targetType, userId, pageable); + } + + @Override + public Like save(Like like) { + return springDataLikeRepository.save(like); + } + + @Override + public void delete(Like like) { + springDataLikeRepository.delete(like); + } +} diff --git a/src/main/java/flipnote/reaction/bookmark/repository/BookmarkRepository.java b/src/main/java/flipnote/reaction/infrastructure/persistence/SpringDataBookmarkRepository.java similarity index 57% rename from src/main/java/flipnote/reaction/bookmark/repository/BookmarkRepository.java rename to src/main/java/flipnote/reaction/infrastructure/persistence/SpringDataBookmarkRepository.java index 2ad22f3..769ef96 100644 --- a/src/main/java/flipnote/reaction/bookmark/repository/BookmarkRepository.java +++ b/src/main/java/flipnote/reaction/infrastructure/persistence/SpringDataBookmarkRepository.java @@ -1,22 +1,19 @@ -package flipnote.reaction.bookmark.repository; +package flipnote.reaction.infrastructure.persistence; -import java.util.List; import java.util.Optional; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; -import flipnote.reaction.bookmark.entity.Bookmark; -import flipnote.reaction.bookmark.entity.BookmarkTargetType; +import flipnote.reaction.domain.bookmark.Bookmark; +import flipnote.reaction.domain.bookmark.BookmarkTargetType; -public interface BookmarkRepository extends JpaRepository { +interface SpringDataBookmarkRepository extends JpaRepository { boolean existsByTargetTypeAndTargetIdAndUserId(BookmarkTargetType targetType, Long targetId, Long userId); Optional findByTargetTypeAndTargetIdAndUserId(BookmarkTargetType targetType, Long targetId, Long userId); Page findByTargetTypeAndUserId(BookmarkTargetType targetType, Long userId, Pageable pageable); - - List findByTargetTypeAndTargetIdInAndUserId(BookmarkTargetType targetType, List targetIds, Long userId); } diff --git a/src/main/java/flipnote/reaction/like/repository/LikeRepository.java b/src/main/java/flipnote/reaction/infrastructure/persistence/SpringDataLikeRepository.java similarity index 59% rename from src/main/java/flipnote/reaction/like/repository/LikeRepository.java rename to src/main/java/flipnote/reaction/infrastructure/persistence/SpringDataLikeRepository.java index 6003798..b189cb1 100644 --- a/src/main/java/flipnote/reaction/like/repository/LikeRepository.java +++ b/src/main/java/flipnote/reaction/infrastructure/persistence/SpringDataLikeRepository.java @@ -1,22 +1,19 @@ -package flipnote.reaction.like.repository; +package flipnote.reaction.infrastructure.persistence; -import java.util.List; import java.util.Optional; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; -import flipnote.reaction.like.entity.Like; -import flipnote.reaction.like.entity.LikeTargetType; +import flipnote.reaction.domain.like.Like; +import flipnote.reaction.domain.like.LikeTargetType; -public interface LikeRepository extends JpaRepository { +interface SpringDataLikeRepository extends JpaRepository { boolean existsByTargetTypeAndTargetIdAndUserId(LikeTargetType targetType, Long targetId, Long userId); Optional findByTargetTypeAndTargetIdAndUserId(LikeTargetType targetType, Long targetId, Long userId); Page findByTargetTypeAndUserId(LikeTargetType targetType, Long userId, Pageable pageable); - - List findByTargetTypeAndTargetIdInAndUserId(LikeTargetType targetType, List targetIds, Long userId); } diff --git a/src/main/java/flipnote/reaction/common/grpc/GrpcServerConfig.java b/src/main/java/flipnote/reaction/interfaces/grpc/GrpcServerConfig.java similarity index 96% rename from src/main/java/flipnote/reaction/common/grpc/GrpcServerConfig.java rename to src/main/java/flipnote/reaction/interfaces/grpc/GrpcServerConfig.java index 9fd49cc..de45214 100644 --- a/src/main/java/flipnote/reaction/common/grpc/GrpcServerConfig.java +++ b/src/main/java/flipnote/reaction/interfaces/grpc/GrpcServerConfig.java @@ -1,4 +1,4 @@ -package flipnote.reaction.common.grpc; +package flipnote.reaction.interfaces.grpc; import java.io.IOException; diff --git a/src/main/java/flipnote/reaction/common/grpc/ReactionGrpcService.java b/src/main/java/flipnote/reaction/interfaces/grpc/ReactionGrpcService.java similarity index 90% rename from src/main/java/flipnote/reaction/common/grpc/ReactionGrpcService.java rename to src/main/java/flipnote/reaction/interfaces/grpc/ReactionGrpcService.java index 0be2c88..cea1975 100644 --- a/src/main/java/flipnote/reaction/common/grpc/ReactionGrpcService.java +++ b/src/main/java/flipnote/reaction/interfaces/grpc/ReactionGrpcService.java @@ -1,14 +1,14 @@ -package flipnote.reaction.common.grpc; +package flipnote.reaction.interfaces.grpc; import java.util.List; import java.util.Map; import org.springframework.stereotype.Component; -import flipnote.reaction.bookmark.entity.BookmarkTargetType; -import flipnote.reaction.bookmark.service.BookmarkReader; -import flipnote.reaction.like.entity.LikeTargetType; -import flipnote.reaction.like.service.LikeReader; +import flipnote.reaction.application.bookmark.BookmarkReader; +import flipnote.reaction.application.like.LikeReader; +import flipnote.reaction.domain.bookmark.BookmarkTargetType; +import flipnote.reaction.domain.like.LikeTargetType; import io.grpc.stub.StreamObserver; import reaction.Reaction.AreReactedRequest; import reaction.Reaction.AreReactedResponse; diff --git a/src/main/java/flipnote/reaction/bookmark/controller/BookmarkController.java b/src/main/java/flipnote/reaction/interfaces/http/BookmarkController.java similarity index 74% rename from src/main/java/flipnote/reaction/bookmark/controller/BookmarkController.java rename to src/main/java/flipnote/reaction/interfaces/http/BookmarkController.java index 599657f..6b8d200 100644 --- a/src/main/java/flipnote/reaction/bookmark/controller/BookmarkController.java +++ b/src/main/java/flipnote/reaction/interfaces/http/BookmarkController.java @@ -1,6 +1,6 @@ -package flipnote.reaction.bookmark.controller; +package flipnote.reaction.interfaces.http; -import static flipnote.reaction.common.constants.HeaderConstants.USER_ID; +import static flipnote.reaction.interfaces.http.common.HeaderConstants.USER_ID; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; @@ -11,13 +11,13 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; -import flipnote.reaction.bookmark.entity.BookmarkTargetType; -import flipnote.reaction.bookmark.model.request.BookmarkSearchRequest; -import flipnote.reaction.bookmark.model.request.BookmarkTargetTypeRequest; -import flipnote.reaction.bookmark.model.response.BookmarkResponse; -import flipnote.reaction.bookmark.service.BookmarkService; -import flipnote.reaction.common.model.response.IdResponse; -import flipnote.reaction.common.model.response.PagingResponse; +import flipnote.reaction.application.bookmark.BookmarkService; +import flipnote.reaction.domain.bookmark.BookmarkTargetType; +import flipnote.reaction.interfaces.http.common.IdResponse; +import flipnote.reaction.interfaces.http.common.PagingResponse; +import flipnote.reaction.interfaces.http.dto.request.BookmarkSearchRequest; +import flipnote.reaction.interfaces.http.dto.request.BookmarkTargetTypeRequest; +import flipnote.reaction.interfaces.http.dto.response.BookmarkResponse; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; diff --git a/src/main/java/flipnote/reaction/like/controller/LikeController.java b/src/main/java/flipnote/reaction/interfaces/http/LikeController.java similarity index 74% rename from src/main/java/flipnote/reaction/like/controller/LikeController.java rename to src/main/java/flipnote/reaction/interfaces/http/LikeController.java index f14fc5d..49ce2b7 100644 --- a/src/main/java/flipnote/reaction/like/controller/LikeController.java +++ b/src/main/java/flipnote/reaction/interfaces/http/LikeController.java @@ -1,6 +1,6 @@ -package flipnote.reaction.like.controller; +package flipnote.reaction.interfaces.http; -import static flipnote.reaction.common.constants.HeaderConstants.USER_ID; +import static flipnote.reaction.interfaces.http.common.HeaderConstants.USER_ID; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; @@ -11,13 +11,13 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; -import flipnote.reaction.common.model.response.IdResponse; -import flipnote.reaction.common.model.response.PagingResponse; -import flipnote.reaction.like.entity.LikeTargetType; -import flipnote.reaction.like.model.request.LikeSearchRequest; -import flipnote.reaction.like.model.request.LikeTargetTypeRequest; -import flipnote.reaction.like.model.response.LikeResponse; -import flipnote.reaction.like.service.LikeService; +import flipnote.reaction.application.like.LikeService; +import flipnote.reaction.domain.like.LikeTargetType; +import flipnote.reaction.interfaces.http.common.IdResponse; +import flipnote.reaction.interfaces.http.common.PagingResponse; +import flipnote.reaction.interfaces.http.dto.request.LikeSearchRequest; +import flipnote.reaction.interfaces.http.dto.request.LikeTargetTypeRequest; +import flipnote.reaction.interfaces.http.dto.response.LikeResponse; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; diff --git a/src/main/java/flipnote/reaction/common/model/response/ApiResponse.java b/src/main/java/flipnote/reaction/interfaces/http/common/ApiResponse.java similarity index 93% rename from src/main/java/flipnote/reaction/common/model/response/ApiResponse.java rename to src/main/java/flipnote/reaction/interfaces/http/common/ApiResponse.java index b8de86d..bd5cf0f 100644 --- a/src/main/java/flipnote/reaction/common/model/response/ApiResponse.java +++ b/src/main/java/flipnote/reaction/interfaces/http/common/ApiResponse.java @@ -1,11 +1,11 @@ -package flipnote.reaction.common.model.response; +package flipnote.reaction.interfaces.http.common; import java.util.List; import java.util.stream.Collectors; import org.springframework.validation.BindingResult; -import flipnote.reaction.common.exception.ErrorCode; +import flipnote.reaction.domain.common.ErrorCode; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; diff --git a/src/main/java/flipnote/reaction/common/model/response/ApiResponseAdvice.java b/src/main/java/flipnote/reaction/interfaces/http/common/ApiResponseAdvice.java similarity index 96% rename from src/main/java/flipnote/reaction/common/model/response/ApiResponseAdvice.java rename to src/main/java/flipnote/reaction/interfaces/http/common/ApiResponseAdvice.java index 67d337b..1275e8d 100644 --- a/src/main/java/flipnote/reaction/common/model/response/ApiResponseAdvice.java +++ b/src/main/java/flipnote/reaction/interfaces/http/common/ApiResponseAdvice.java @@ -1,4 +1,4 @@ -package flipnote.reaction.common.model.response; +package flipnote.reaction.interfaces.http.common; import org.springframework.core.MethodParameter; import org.springframework.http.MediaType; diff --git a/src/main/java/flipnote/reaction/common/exception/GlobalExceptionHandler.java b/src/main/java/flipnote/reaction/interfaces/http/common/GlobalExceptionHandler.java similarity index 91% rename from src/main/java/flipnote/reaction/common/exception/GlobalExceptionHandler.java rename to src/main/java/flipnote/reaction/interfaces/http/common/GlobalExceptionHandler.java index 72cdc8c..8955786 100644 --- a/src/main/java/flipnote/reaction/common/exception/GlobalExceptionHandler.java +++ b/src/main/java/flipnote/reaction/interfaces/http/common/GlobalExceptionHandler.java @@ -1,4 +1,4 @@ -package flipnote.reaction.common.exception; +package flipnote.reaction.interfaces.http.common; import java.util.List; @@ -9,7 +9,9 @@ import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; -import flipnote.reaction.common.model.response.ApiResponse; +import flipnote.reaction.domain.common.BizException; +import flipnote.reaction.domain.common.CommonErrorCode; +import flipnote.reaction.domain.common.ErrorCode; import lombok.extern.slf4j.Slf4j; @Slf4j diff --git a/src/main/java/flipnote/reaction/common/constants/HeaderConstants.java b/src/main/java/flipnote/reaction/interfaces/http/common/HeaderConstants.java similarity index 80% rename from src/main/java/flipnote/reaction/common/constants/HeaderConstants.java rename to src/main/java/flipnote/reaction/interfaces/http/common/HeaderConstants.java index d9a6331..3f979d9 100644 --- a/src/main/java/flipnote/reaction/common/constants/HeaderConstants.java +++ b/src/main/java/flipnote/reaction/interfaces/http/common/HeaderConstants.java @@ -1,4 +1,4 @@ -package flipnote.reaction.common.constants; +package flipnote.reaction.interfaces.http.common; import lombok.AccessLevel; import lombok.NoArgsConstructor; diff --git a/src/main/java/flipnote/reaction/common/model/response/IdResponse.java b/src/main/java/flipnote/reaction/interfaces/http/common/IdResponse.java similarity index 69% rename from src/main/java/flipnote/reaction/common/model/response/IdResponse.java rename to src/main/java/flipnote/reaction/interfaces/http/common/IdResponse.java index 6240e46..4f3df70 100644 --- a/src/main/java/flipnote/reaction/common/model/response/IdResponse.java +++ b/src/main/java/flipnote/reaction/interfaces/http/common/IdResponse.java @@ -1,4 +1,4 @@ -package flipnote.reaction.common.model.response; +package flipnote.reaction.interfaces.http.common; public record IdResponse( Long id diff --git a/src/main/java/flipnote/reaction/common/model/request/PagingRequest.java b/src/main/java/flipnote/reaction/interfaces/http/common/PagingRequest.java similarity index 94% rename from src/main/java/flipnote/reaction/common/model/request/PagingRequest.java rename to src/main/java/flipnote/reaction/interfaces/http/common/PagingRequest.java index dfe36c4..1f4970b 100644 --- a/src/main/java/flipnote/reaction/common/model/request/PagingRequest.java +++ b/src/main/java/flipnote/reaction/interfaces/http/common/PagingRequest.java @@ -1,4 +1,4 @@ -package flipnote.reaction.common.model.request; +package flipnote.reaction.interfaces.http.common; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Sort; diff --git a/src/main/java/flipnote/reaction/common/model/response/PagingResponse.java b/src/main/java/flipnote/reaction/interfaces/http/common/PagingResponse.java similarity index 91% rename from src/main/java/flipnote/reaction/common/model/response/PagingResponse.java rename to src/main/java/flipnote/reaction/interfaces/http/common/PagingResponse.java index 304f4df..14c652d 100644 --- a/src/main/java/flipnote/reaction/common/model/response/PagingResponse.java +++ b/src/main/java/flipnote/reaction/interfaces/http/common/PagingResponse.java @@ -1,4 +1,4 @@ -package flipnote.reaction.common.model.response; +package flipnote.reaction.interfaces.http.common; import java.util.List; diff --git a/src/main/java/flipnote/reaction/bookmark/model/request/BookmarkSearchRequest.java b/src/main/java/flipnote/reaction/interfaces/http/dto/request/BookmarkSearchRequest.java similarity index 51% rename from src/main/java/flipnote/reaction/bookmark/model/request/BookmarkSearchRequest.java rename to src/main/java/flipnote/reaction/interfaces/http/dto/request/BookmarkSearchRequest.java index 7b0d9b3..6942060 100644 --- a/src/main/java/flipnote/reaction/bookmark/model/request/BookmarkSearchRequest.java +++ b/src/main/java/flipnote/reaction/interfaces/http/dto/request/BookmarkSearchRequest.java @@ -1,6 +1,6 @@ -package flipnote.reaction.bookmark.model.request; +package flipnote.reaction.interfaces.http.dto.request; -import flipnote.reaction.common.model.request.PagingRequest; +import flipnote.reaction.interfaces.http.common.PagingRequest; import lombok.Getter; import lombok.Setter; diff --git a/src/main/java/flipnote/reaction/bookmark/model/request/BookmarkTargetTypeRequest.java b/src/main/java/flipnote/reaction/interfaces/http/dto/request/BookmarkTargetTypeRequest.java similarity index 64% rename from src/main/java/flipnote/reaction/bookmark/model/request/BookmarkTargetTypeRequest.java rename to src/main/java/flipnote/reaction/interfaces/http/dto/request/BookmarkTargetTypeRequest.java index fba492d..93b9ec9 100644 --- a/src/main/java/flipnote/reaction/bookmark/model/request/BookmarkTargetTypeRequest.java +++ b/src/main/java/flipnote/reaction/interfaces/http/dto/request/BookmarkTargetTypeRequest.java @@ -1,8 +1,8 @@ -package flipnote.reaction.bookmark.model.request; +package flipnote.reaction.interfaces.http.dto.request; -import flipnote.reaction.bookmark.entity.BookmarkTargetType; -import flipnote.reaction.bookmark.exception.BookmarkErrorCode; -import flipnote.reaction.common.exception.BizException; +import flipnote.reaction.domain.bookmark.BookmarkErrorCode; +import flipnote.reaction.domain.bookmark.BookmarkTargetType; +import flipnote.reaction.domain.common.BizException; public enum BookmarkTargetTypeRequest { card_set; diff --git a/src/main/java/flipnote/reaction/like/model/request/LikeSearchRequest.java b/src/main/java/flipnote/reaction/interfaces/http/dto/request/LikeSearchRequest.java similarity index 50% rename from src/main/java/flipnote/reaction/like/model/request/LikeSearchRequest.java rename to src/main/java/flipnote/reaction/interfaces/http/dto/request/LikeSearchRequest.java index 491bde9..229dc48 100644 --- a/src/main/java/flipnote/reaction/like/model/request/LikeSearchRequest.java +++ b/src/main/java/flipnote/reaction/interfaces/http/dto/request/LikeSearchRequest.java @@ -1,6 +1,6 @@ -package flipnote.reaction.like.model.request; +package flipnote.reaction.interfaces.http.dto.request; -import flipnote.reaction.common.model.request.PagingRequest; +import flipnote.reaction.interfaces.http.common.PagingRequest; import lombok.Getter; import lombok.Setter; diff --git a/src/main/java/flipnote/reaction/like/model/request/LikeTargetTypeRequest.java b/src/main/java/flipnote/reaction/interfaces/http/dto/request/LikeTargetTypeRequest.java similarity index 64% rename from src/main/java/flipnote/reaction/like/model/request/LikeTargetTypeRequest.java rename to src/main/java/flipnote/reaction/interfaces/http/dto/request/LikeTargetTypeRequest.java index d5d459d..0e02093 100644 --- a/src/main/java/flipnote/reaction/like/model/request/LikeTargetTypeRequest.java +++ b/src/main/java/flipnote/reaction/interfaces/http/dto/request/LikeTargetTypeRequest.java @@ -1,8 +1,8 @@ -package flipnote.reaction.like.model.request; +package flipnote.reaction.interfaces.http.dto.request; -import flipnote.reaction.common.exception.BizException; -import flipnote.reaction.like.entity.LikeTargetType; -import flipnote.reaction.like.exception.LikeErrorCode; +import flipnote.reaction.domain.common.BizException; +import flipnote.reaction.domain.like.LikeErrorCode; +import flipnote.reaction.domain.like.LikeTargetType; public enum LikeTargetTypeRequest { card_set; diff --git a/src/main/java/flipnote/reaction/interfaces/http/dto/response/BookmarkResponse.java b/src/main/java/flipnote/reaction/interfaces/http/dto/response/BookmarkResponse.java new file mode 100644 index 0000000..a397780 --- /dev/null +++ b/src/main/java/flipnote/reaction/interfaces/http/dto/response/BookmarkResponse.java @@ -0,0 +1,19 @@ +package flipnote.reaction.interfaces.http.dto.response; + +import java.time.LocalDateTime; + +import flipnote.reaction.domain.bookmark.Bookmark; + +public record BookmarkResponse( + String targetType, + Long targetId, + LocalDateTime bookmarkedAt +) { + public static BookmarkResponse from(Bookmark bookmark) { + return new BookmarkResponse( + bookmark.getTargetType().name(), + bookmark.getTargetId(), + bookmark.getCreatedAt() + ); + } +} diff --git a/src/main/java/flipnote/reaction/interfaces/http/dto/response/LikeResponse.java b/src/main/java/flipnote/reaction/interfaces/http/dto/response/LikeResponse.java new file mode 100644 index 0000000..5f39745 --- /dev/null +++ b/src/main/java/flipnote/reaction/interfaces/http/dto/response/LikeResponse.java @@ -0,0 +1,19 @@ +package flipnote.reaction.interfaces.http.dto.response; + +import java.time.LocalDateTime; + +import flipnote.reaction.domain.like.Like; + +public record LikeResponse( + String targetType, + Long targetId, + LocalDateTime likedAt +) { + public static LikeResponse from(Like like) { + return new LikeResponse( + like.getTargetType().name(), + like.getTargetId(), + like.getCreatedAt() + ); + } +} diff --git a/src/main/java/flipnote/reaction/like/model/response/LikeResponse.java b/src/main/java/flipnote/reaction/like/model/response/LikeResponse.java deleted file mode 100644 index c656567..0000000 --- a/src/main/java/flipnote/reaction/like/model/response/LikeResponse.java +++ /dev/null @@ -1,23 +0,0 @@ -package flipnote.reaction.like.model.response; - -import java.time.LocalDateTime; - -import cardset.Cardset.CardSetSummary; -import flipnote.reaction.like.entity.Like; - -public record LikeResponse( - LikeTargetResponse target, - LocalDateTime likedAt -) { - public static LikeResponse from(Like like, CardSetSummary summary) { - return new LikeResponse( - new LikeTargetResponse( - like.getTargetType().name(), - like.getTargetId(), - summary != null ? summary.getGroupId() : null, - summary != null ? summary.getName() : null - ), - like.getCreatedAt() - ); - } -} diff --git a/src/main/java/flipnote/reaction/like/model/response/LikeTargetResponse.java b/src/main/java/flipnote/reaction/like/model/response/LikeTargetResponse.java deleted file mode 100644 index d3419dc..0000000 --- a/src/main/java/flipnote/reaction/like/model/response/LikeTargetResponse.java +++ /dev/null @@ -1,9 +0,0 @@ -package flipnote.reaction.like.model.response; - -public record LikeTargetResponse( - String type, - Long id, - Long groupId, - String name -) { -} diff --git a/src/main/java/flipnote/reaction/like/service/LikeService.java b/src/main/java/flipnote/reaction/like/service/LikeService.java deleted file mode 100644 index ed46aba..0000000 --- a/src/main/java/flipnote/reaction/like/service/LikeService.java +++ /dev/null @@ -1,96 +0,0 @@ -package flipnote.reaction.like.service; - -import java.util.List; -import java.util.Map; - -import org.springframework.dao.DataIntegrityViolationException; -import org.springframework.data.domain.Page; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -import cardset.Cardset.CardSetSummary; -import flipnote.reaction.common.config.RabbitMqConfig; -import flipnote.reaction.common.event.ReactionEventPublisher; -import flipnote.reaction.common.exception.BizException; -import flipnote.reaction.common.exception.CommonErrorCode; -import flipnote.reaction.common.grpc.CardSetGrpcClient; -import flipnote.reaction.common.model.response.IdResponse; -import flipnote.reaction.common.model.response.PagingResponse; -import flipnote.reaction.like.entity.Like; -import flipnote.reaction.like.entity.LikeTargetType; -import flipnote.reaction.like.exception.LikeErrorCode; -import flipnote.reaction.like.model.request.LikeSearchRequest; -import flipnote.reaction.like.model.response.LikeResponse; -import flipnote.reaction.like.repository.LikeRepository; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; - -@Slf4j -@Service -@RequiredArgsConstructor -@Transactional(readOnly = true) -public class LikeService { - - private final LikeRepository likeRepository; - private final LikeReader likeReader; - private final ReactionEventPublisher eventPublisher; - private final CardSetGrpcClient cardSetGrpcClient; - - @Transactional - public IdResponse addLike(LikeTargetType targetType, Long targetId, Long userId) { - if (targetType == LikeTargetType.CARD_SET) { - if (!cardSetGrpcClient.isCardSetViewable(targetId, userId)) { - throw new BizException(CommonErrorCode.TARGET_NOT_VIEWABLE); - } - } - - if (likeReader.isLiked(targetType, targetId, userId)) { - throw new BizException(LikeErrorCode.ALREADY_LIKED); - } - - Like like = Like.builder() - .targetType(targetType) - .targetId(targetId) - .userId(userId) - .build(); - - try { - likeRepository.save(like); - } catch (DataIntegrityViolationException e) { - throw new BizException(LikeErrorCode.ALREADY_LIKED); - } - - eventPublisher.publish(RabbitMqConfig.ROUTING_KEY_LIKE_ADDED, "LIKE_ADDED", - targetType.name(), targetId, userId); - - return IdResponse.from(like.getId()); - } - - @Transactional - public void removeLike(LikeTargetType targetType, Long targetId, Long userId) { - Like like = likeReader.findByTargetAndUserId(targetType, targetId, userId); - likeRepository.delete(like); - - eventPublisher.publish(RabbitMqConfig.ROUTING_KEY_LIKE_REMOVED, "LIKE_REMOVED", - targetType.name(), targetId, userId); - } - - public PagingResponse getLikes(LikeTargetType targetType, Long userId, LikeSearchRequest request) { - Page likePage = likeRepository.findByTargetTypeAndUserId( - targetType, userId, request.getPageRequest() - ); - - List targetIds = likePage.getContent().stream() - .map(Like::getTargetId) - .toList(); - - Map summaryMap = targetIds.isEmpty() - ? Map.of() - : cardSetGrpcClient.getCardSetsByIds(targetIds, userId); - - Page responsePage = likePage.map( - like -> LikeResponse.from(like, summaryMap.get(like.getTargetId())) - ); - return PagingResponse.from(responsePage); - } -} diff --git a/src/test/java/flipnote/reaction/config/TestGrpcConfig.java b/src/test/java/flipnote/reaction/config/TestGrpcConfig.java index f9846df..e0ab496 100644 --- a/src/test/java/flipnote/reaction/config/TestGrpcConfig.java +++ b/src/test/java/flipnote/reaction/config/TestGrpcConfig.java @@ -3,7 +3,7 @@ import org.springframework.boot.test.context.TestConfiguration; import org.springframework.context.annotation.Bean; -import flipnote.reaction.common.grpc.CardSetGrpcClient; +import flipnote.reaction.infrastructure.grpc.CardSetGrpcClient; import io.grpc.ManagedChannel; import static org.mockito.Mockito.mock; From a02212f45c4f1617a8b1346ffc88ef510b05735a Mon Sep 17 00:00:00 2001 From: dungbik Date: Fri, 3 Apr 2026 13:45:56 +0900 Subject: [PATCH 2/5] =?UTF-8?q?Refactor:=20=ED=94=84=EB=A1=9C=EC=A0=9D?= =?UTF-8?q?=ED=8A=B8=20=EA=B5=AC=EC=A1=B0=20=EB=A6=AC=ED=8C=A9=ED=86=A0?= =?UTF-8?q?=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle.kts | 39 +++++------ .../application/bookmark/BookmarkResult.java | 23 ++++++ .../application/bookmark/BookmarkService.java | 70 ++++++++++--------- .../common/CardSetSummaryResult.java | 27 +++++++ .../reaction/application/like/LikeResult.java | 23 ++++++ .../application/like/LikeService.java | 70 ++++++++++--------- .../grpc/CardSetGrpcClient.java | 11 ++- .../infrastructure/grpc/GrpcConfig.java | 14 ++-- .../{config => messaging}/RabbitMqConfig.java | 2 +- .../messaging/ReactionEventPublisher.java | 1 - .../AuditingConfig.java | 2 +- .../grpc/GrpcExceptionHandlerImpl.java | 52 ++++++++++++++ .../interfaces/grpc/GrpcServerConfig.java | 53 -------------- .../interfaces/grpc/ReactionGrpcService.java | 34 +++++---- .../interfaces/http/BookmarkController.java | 8 +-- .../interfaces/http/LikeController.java | 8 +-- .../interfaces/http/common/PagingRequest.java | 2 +- .../interfaces/http/dto/response/.gitkeep | 0 .../http/dto/response/BookmarkResponse.java | 19 ----- .../http/dto/response/LikeResponse.java | 19 ----- src/main/resources/application.yml | 17 ++--- .../reaction/config/TestGrpcConfig.java | 6 -- src/test/resources/application.yml | 15 ++-- 23 files changed, 276 insertions(+), 239 deletions(-) create mode 100644 src/main/java/flipnote/reaction/application/bookmark/BookmarkResult.java create mode 100644 src/main/java/flipnote/reaction/application/common/CardSetSummaryResult.java create mode 100644 src/main/java/flipnote/reaction/application/like/LikeResult.java rename src/main/java/flipnote/reaction/infrastructure/{config => messaging}/RabbitMqConfig.java (94%) rename src/main/java/flipnote/reaction/infrastructure/{config => persistence}/AuditingConfig.java (78%) create mode 100644 src/main/java/flipnote/reaction/interfaces/grpc/GrpcExceptionHandlerImpl.java delete mode 100644 src/main/java/flipnote/reaction/interfaces/grpc/GrpcServerConfig.java create mode 100644 src/main/java/flipnote/reaction/interfaces/http/dto/response/.gitkeep delete mode 100644 src/main/java/flipnote/reaction/interfaces/http/dto/response/BookmarkResponse.java delete mode 100644 src/main/java/flipnote/reaction/interfaces/http/dto/response/LikeResponse.java diff --git a/build.gradle.kts b/build.gradle.kts index 3a4f7ed..cdedd82 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -2,9 +2,9 @@ import com.google.protobuf.gradle.id plugins { java - id("org.springframework.boot") version "3.5.10" + id("org.springframework.boot") version "4.0.2" id("io.spring.dependency-management") version "1.1.7" - id("com.google.protobuf") version "0.9.4" + id("com.google.protobuf") version "0.9.5" } group = "flipnote" @@ -17,18 +17,11 @@ java { } } -configurations { - compileOnly { - extendsFrom(configurations.annotationProcessor.get()) - } -} - repositories { mavenCentral() } -val grpcVersion = "1.68.0" -val protocVersion = "4.28.2" +extra["springGrpcVersion"] = "1.0.2" dependencies { implementation("org.springframework.boot:spring-boot-starter-actuator") @@ -38,33 +31,39 @@ dependencies { implementation("org.springframework.boot:spring-boot-starter-amqp") // gRPC - implementation("io.grpc:grpc-netty-shaded:$grpcVersion") - implementation("io.grpc:grpc-protobuf:$grpcVersion") - implementation("io.grpc:grpc-stub:$grpcVersion") - implementation("com.google.protobuf:protobuf-java:$protocVersion") - implementation("javax.annotation:javax.annotation-api:1.3.2") + implementation("io.grpc:grpc-services") + implementation("org.springframework.grpc:spring-grpc-spring-boot-starter") compileOnly("org.projectlombok:lombok") runtimeOnly("com.mysql:mysql-connector-j") annotationProcessor("org.projectlombok:lombok") testImplementation("org.springframework.boot:spring-boot-starter-test") + testImplementation("org.springframework.grpc:spring-grpc-test") testRuntimeOnly("com.h2database:h2") testRuntimeOnly("org.junit.platform:junit-platform-launcher") } +dependencyManagement { + imports { + mavenBom("org.springframework.grpc:spring-grpc-dependencies:${property("springGrpcVersion")}") + } +} + protobuf { protoc { - artifact = "com.google.protobuf:protoc:$protocVersion" + artifact = "com.google.protobuf:protoc" } plugins { id("grpc") { - artifact = "io.grpc:protoc-gen-grpc-java:$grpcVersion" + artifact = "io.grpc:protoc-gen-grpc-java" } } generateProtoTasks { - all().forEach { task -> - task.plugins { - id("grpc") + all().forEach { + it.plugins { + id("grpc") { + option("@generated=omit") + } } } } diff --git a/src/main/java/flipnote/reaction/application/bookmark/BookmarkResult.java b/src/main/java/flipnote/reaction/application/bookmark/BookmarkResult.java new file mode 100644 index 0000000..d4c7964 --- /dev/null +++ b/src/main/java/flipnote/reaction/application/bookmark/BookmarkResult.java @@ -0,0 +1,23 @@ +package flipnote.reaction.application.bookmark; + +import java.time.LocalDateTime; + +import cardset.Cardset.CardSetSummary; +import flipnote.reaction.application.common.CardSetSummaryResult; +import flipnote.reaction.domain.bookmark.Bookmark; + +public record BookmarkResult( + String targetType, + Long targetId, + LocalDateTime bookmarkedAt, + CardSetSummaryResult cardSet +) { + public static BookmarkResult from(Bookmark bookmark, CardSetSummary cardSet) { + return new BookmarkResult( + bookmark.getTargetType().name(), + bookmark.getTargetId(), + bookmark.getCreatedAt(), + cardSet != null ? CardSetSummaryResult.from(cardSet) : null + ); + } +} diff --git a/src/main/java/flipnote/reaction/application/bookmark/BookmarkService.java b/src/main/java/flipnote/reaction/application/bookmark/BookmarkService.java index 12a33f9..534e1bc 100644 --- a/src/main/java/flipnote/reaction/application/bookmark/BookmarkService.java +++ b/src/main/java/flipnote/reaction/application/bookmark/BookmarkService.java @@ -1,26 +1,26 @@ package flipnote.reaction.application.bookmark; -import org.springframework.amqp.rabbit.core.RabbitTemplate; +import java.util.List; +import java.util.Map; + import org.springframework.dao.DataIntegrityViolationException; import org.springframework.data.domain.Page; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import cardset.Cardset.CardSetSummary; import flipnote.reaction.domain.bookmark.Bookmark; import flipnote.reaction.domain.bookmark.BookmarkErrorCode; import flipnote.reaction.domain.bookmark.BookmarkRepository; import flipnote.reaction.domain.bookmark.BookmarkTargetType; import flipnote.reaction.domain.common.BizException; -import flipnote.reaction.infrastructure.config.RabbitMqConfig; -import flipnote.reaction.infrastructure.messaging.ReactionMessage; -import flipnote.reaction.interfaces.http.common.IdResponse; -import flipnote.reaction.interfaces.http.common.PagingResponse; +import flipnote.reaction.domain.common.CommonErrorCode; +import flipnote.reaction.infrastructure.messaging.RabbitMqConfig; +import flipnote.reaction.infrastructure.grpc.CardSetGrpcClient; +import flipnote.reaction.infrastructure.messaging.ReactionEventPublisher; import flipnote.reaction.interfaces.http.dto.request.BookmarkSearchRequest; -import flipnote.reaction.interfaces.http.dto.response.BookmarkResponse; import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -@Slf4j @Service @RequiredArgsConstructor @Transactional(readOnly = true) @@ -28,11 +28,14 @@ public class BookmarkService { private final BookmarkRepository bookmarkRepository; private final BookmarkReader bookmarkReader; - private final RabbitTemplate rabbitTemplate; + private final ReactionEventPublisher eventPublisher; + private final CardSetGrpcClient cardSetGrpcClient; - @Transactional - public IdResponse addBookmark(BookmarkTargetType targetType, Long targetId, Long userId) { - // TODO: gRPC로 대상 존재 여부 검증 (CardSet/Group 서비스 호출) + @Transactional(noRollbackFor = DataIntegrityViolationException.class) + public Long addBookmark(BookmarkTargetType targetType, Long targetId, Long userId) { + if (!cardSetGrpcClient.isCardSetViewable(targetId, userId)) { + throw new BizException(CommonErrorCode.TARGET_NOT_VIEWABLE); + } if (bookmarkReader.isBookmarked(targetType, targetId, userId)) { throw new BizException(BookmarkErrorCode.ALREADY_BOOKMARKED); @@ -50,9 +53,15 @@ public IdResponse addBookmark(BookmarkTargetType targetType, Long targetId, Long throw new BizException(BookmarkErrorCode.ALREADY_BOOKMARKED); } - publishEvent(RabbitMqConfig.ROUTING_KEY_BOOKMARK_ADDED, "BOOKMARK_ADDED", targetType, targetId, userId); + eventPublisher.publish( + RabbitMqConfig.ROUTING_KEY_BOOKMARK_ADDED, + "BOOKMARK_ADDED", + targetType.name(), + targetId, + userId + ); - return IdResponse.from(bookmark.getId()); + return bookmark.getId(); } @Transactional @@ -60,32 +69,29 @@ public void removeBookmark(BookmarkTargetType targetType, Long targetId, Long us Bookmark bookmark = bookmarkReader.findByTargetAndUserId(targetType, targetId, userId); bookmarkRepository.delete(bookmark); - publishEvent(RabbitMqConfig.ROUTING_KEY_BOOKMARK_REMOVED, "BOOKMARK_REMOVED", targetType, targetId, userId); + eventPublisher.publish( + RabbitMqConfig.ROUTING_KEY_BOOKMARK_REMOVED, + "BOOKMARK_REMOVED", + targetType.name(), + targetId, + userId + ); } - public PagingResponse getBookmarks(BookmarkTargetType targetType, Long userId, + public Page getBookmarks(BookmarkTargetType targetType, Long userId, BookmarkSearchRequest request) { Page bookmarkPage = bookmarkRepository.findByTargetTypeAndUserId( targetType, userId, request.getPageRequest() ); - // TODO: gRPC로 대상 상세 정보 fetch (CardSet 서비스 호출) + List targetIds = bookmarkPage.getContent().stream() + .map(Bookmark::getTargetId) + .toList(); - Page responsePage = bookmarkPage.map(BookmarkResponse::from); - return PagingResponse.from(responsePage); - } + Map cardSetMap = targetIds.isEmpty() + ? Map.of() + : cardSetGrpcClient.getCardSetsByIds(targetIds, userId); - private void publishEvent(String routingKey, String eventType, - BookmarkTargetType targetType, Long targetId, Long userId) { - try { - rabbitTemplate.convertAndSend( - RabbitMqConfig.EXCHANGE, - routingKey, - new ReactionMessage(eventType, targetType.name(), targetId, userId) - ); - } catch (Exception e) { - log.error("북마크 이벤트 발행 실패: eventType={}, targetType={}, targetId={}, userId={}", - eventType, targetType, targetId, userId, e); - } + return bookmarkPage.map(b -> BookmarkResult.from(b, cardSetMap.get(b.getTargetId()))); } } diff --git a/src/main/java/flipnote/reaction/application/common/CardSetSummaryResult.java b/src/main/java/flipnote/reaction/application/common/CardSetSummaryResult.java new file mode 100644 index 0000000..adcdf36 --- /dev/null +++ b/src/main/java/flipnote/reaction/application/common/CardSetSummaryResult.java @@ -0,0 +1,27 @@ +package flipnote.reaction.application.common; + +import cardset.Cardset.CardSetSummary; + +public record CardSetSummaryResult( + Long id, + String name, + Long groupId, + String visibility, + String category, + String hashtag, + Long imageRefId, + Long cardCount +) { + public static CardSetSummaryResult from(CardSetSummary summary) { + return new CardSetSummaryResult( + summary.getId(), + summary.getName(), + summary.getGroupId(), + summary.getVisibility(), + summary.getCategory(), + summary.getHashtag(), + summary.hasImageRefId() ? summary.getImageRefId() : null, + summary.getCardCount() + ); + } +} diff --git a/src/main/java/flipnote/reaction/application/like/LikeResult.java b/src/main/java/flipnote/reaction/application/like/LikeResult.java new file mode 100644 index 0000000..b51e7b0 --- /dev/null +++ b/src/main/java/flipnote/reaction/application/like/LikeResult.java @@ -0,0 +1,23 @@ +package flipnote.reaction.application.like; + +import java.time.LocalDateTime; + +import cardset.Cardset.CardSetSummary; +import flipnote.reaction.application.common.CardSetSummaryResult; +import flipnote.reaction.domain.like.Like; + +public record LikeResult( + String targetType, + Long targetId, + LocalDateTime likedAt, + CardSetSummaryResult cardSet +) { + public static LikeResult from(Like like, CardSetSummary cardSet) { + return new LikeResult( + like.getTargetType().name(), + like.getTargetId(), + like.getCreatedAt(), + cardSet != null ? CardSetSummaryResult.from(cardSet) : null + ); + } +} diff --git a/src/main/java/flipnote/reaction/application/like/LikeService.java b/src/main/java/flipnote/reaction/application/like/LikeService.java index a896ff9..0101dd2 100644 --- a/src/main/java/flipnote/reaction/application/like/LikeService.java +++ b/src/main/java/flipnote/reaction/application/like/LikeService.java @@ -1,26 +1,26 @@ package flipnote.reaction.application.like; -import org.springframework.amqp.rabbit.core.RabbitTemplate; +import java.util.List; +import java.util.Map; + import org.springframework.dao.DataIntegrityViolationException; import org.springframework.data.domain.Page; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import cardset.Cardset.CardSetSummary; import flipnote.reaction.domain.common.BizException; +import flipnote.reaction.domain.common.CommonErrorCode; import flipnote.reaction.domain.like.Like; import flipnote.reaction.domain.like.LikeErrorCode; import flipnote.reaction.domain.like.LikeRepository; import flipnote.reaction.domain.like.LikeTargetType; -import flipnote.reaction.infrastructure.config.RabbitMqConfig; -import flipnote.reaction.infrastructure.messaging.ReactionMessage; -import flipnote.reaction.interfaces.http.common.IdResponse; -import flipnote.reaction.interfaces.http.common.PagingResponse; +import flipnote.reaction.infrastructure.messaging.RabbitMqConfig; +import flipnote.reaction.infrastructure.grpc.CardSetGrpcClient; +import flipnote.reaction.infrastructure.messaging.ReactionEventPublisher; import flipnote.reaction.interfaces.http.dto.request.LikeSearchRequest; -import flipnote.reaction.interfaces.http.dto.response.LikeResponse; import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -@Slf4j @Service @RequiredArgsConstructor @Transactional(readOnly = true) @@ -28,11 +28,14 @@ public class LikeService { private final LikeRepository likeRepository; private final LikeReader likeReader; - private final RabbitTemplate rabbitTemplate; + private final ReactionEventPublisher eventPublisher; + private final CardSetGrpcClient cardSetGrpcClient; - @Transactional - public IdResponse addLike(LikeTargetType targetType, Long targetId, Long userId) { - // TODO: gRPC로 대상 존재 여부 검증 (CardSet/Group 서비스 호출) + @Transactional(noRollbackFor = DataIntegrityViolationException.class) + public Long addLike(LikeTargetType targetType, Long targetId, Long userId) { + if (!cardSetGrpcClient.isCardSetViewable(targetId, userId)) { + throw new BizException(CommonErrorCode.TARGET_NOT_VIEWABLE); + } if (likeReader.isLiked(targetType, targetId, userId)) { throw new BizException(LikeErrorCode.ALREADY_LIKED); @@ -50,9 +53,15 @@ public IdResponse addLike(LikeTargetType targetType, Long targetId, Long userId) throw new BizException(LikeErrorCode.ALREADY_LIKED); } - publishEvent(RabbitMqConfig.ROUTING_KEY_LIKE_ADDED, "LIKE_ADDED", targetType, targetId, userId); + eventPublisher.publish( + RabbitMqConfig.ROUTING_KEY_LIKE_ADDED, + "LIKE_ADDED", + targetType.name(), + targetId, + userId + ); - return IdResponse.from(like.getId()); + return like.getId(); } @Transactional @@ -60,31 +69,28 @@ public void removeLike(LikeTargetType targetType, Long targetId, Long userId) { Like like = likeReader.findByTargetAndUserId(targetType, targetId, userId); likeRepository.delete(like); - publishEvent(RabbitMqConfig.ROUTING_KEY_LIKE_REMOVED, "LIKE_REMOVED", targetType, targetId, userId); + eventPublisher.publish( + RabbitMqConfig.ROUTING_KEY_LIKE_REMOVED, + "LIKE_REMOVED", + targetType.name(), + targetId, + userId + ); } - public PagingResponse getLikes(LikeTargetType targetType, Long userId, LikeSearchRequest request) { + public Page getLikes(LikeTargetType targetType, Long userId, LikeSearchRequest request) { Page likePage = likeRepository.findByTargetTypeAndUserId( targetType, userId, request.getPageRequest() ); - // TODO: gRPC로 대상 상세 정보 fetch (CardSet 서비스 호출) + List targetIds = likePage.getContent().stream() + .map(Like::getTargetId) + .toList(); - Page responsePage = likePage.map(LikeResponse::from); - return PagingResponse.from(responsePage); - } + Map cardSetMap = targetIds.isEmpty() + ? Map.of() + : cardSetGrpcClient.getCardSetsByIds(targetIds, userId); - private void publishEvent(String routingKey, String eventType, - LikeTargetType targetType, Long targetId, Long userId) { - try { - rabbitTemplate.convertAndSend( - RabbitMqConfig.EXCHANGE, - routingKey, - new ReactionMessage(eventType, targetType.name(), targetId, userId) - ); - } catch (Exception e) { - log.error("좋아요 이벤트 발행 실패: eventType={}, targetType={}, targetId={}, userId={}", - eventType, targetType, targetId, userId, e); - } + return likePage.map(l -> LikeResult.from(l, cardSetMap.get(l.getTargetId()))); } } diff --git a/src/main/java/flipnote/reaction/infrastructure/grpc/CardSetGrpcClient.java b/src/main/java/flipnote/reaction/infrastructure/grpc/CardSetGrpcClient.java index 68166b4..dc73b79 100644 --- a/src/main/java/flipnote/reaction/infrastructure/grpc/CardSetGrpcClient.java +++ b/src/main/java/flipnote/reaction/infrastructure/grpc/CardSetGrpcClient.java @@ -15,7 +15,6 @@ import cardset.CardsetServiceGrpc; import flipnote.reaction.domain.common.BizException; import flipnote.reaction.domain.common.CommonErrorCode; -import io.grpc.ManagedChannel; import io.grpc.StatusRuntimeException; import lombok.extern.slf4j.Slf4j; @@ -25,15 +24,15 @@ public class CardSetGrpcClient { private final CardsetServiceGrpc.CardsetServiceBlockingStub stub; - public CardSetGrpcClient(ManagedChannel cardSetChannel) { - this.stub = CardsetServiceGrpc.newBlockingStub(cardSetChannel); + public CardSetGrpcClient(CardsetServiceGrpc.CardsetServiceBlockingStub cardSetStub) { + this.stub = cardSetStub; } public boolean isCardSetViewable(Long cardSetId, Long userId) { try { IsCardSetViewableRequest request = IsCardSetViewableRequest.newBuilder() - .setCardSetId(cardSetId.intValue()) - .setUserId(userId.intValue()) + .setCardSetId(cardSetId) + .setUserId(userId) .build(); IsCardSetViewableResponse response = stub.isCardSetViewable(request); @@ -48,7 +47,7 @@ public Map getCardSetsByIds(List cardSetIds, Long us try { GetCardSetsByIdsRequest request = GetCardSetsByIdsRequest.newBuilder() .addAllCardSetIds(cardSetIds) - .setUserId(userId.intValue()) + .setUserId(userId) .build(); GetCardSetsByIdsResponse response = stub.getCardSetsByIds(request); diff --git a/src/main/java/flipnote/reaction/infrastructure/grpc/GrpcConfig.java b/src/main/java/flipnote/reaction/infrastructure/grpc/GrpcConfig.java index 823c404..643448a 100644 --- a/src/main/java/flipnote/reaction/infrastructure/grpc/GrpcConfig.java +++ b/src/main/java/flipnote/reaction/infrastructure/grpc/GrpcConfig.java @@ -1,22 +1,16 @@ package flipnote.reaction.infrastructure.grpc; -import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.grpc.client.GrpcChannelFactory; -import io.grpc.ManagedChannel; -import io.grpc.ManagedChannelBuilder; +import cardset.CardsetServiceGrpc; @Configuration public class GrpcConfig { @Bean - public ManagedChannel cardSetChannel( - @Value("${grpc.cardset.host}") String host, - @Value("${grpc.cardset.port}") int port - ) { - return ManagedChannelBuilder.forAddress(host, port) - .usePlaintext() - .build(); + public CardsetServiceGrpc.CardsetServiceBlockingStub cardSetStub(GrpcChannelFactory grpcChannelFactory) { + return CardsetServiceGrpc.newBlockingStub(grpcChannelFactory.createChannel("cardset")); } } diff --git a/src/main/java/flipnote/reaction/infrastructure/config/RabbitMqConfig.java b/src/main/java/flipnote/reaction/infrastructure/messaging/RabbitMqConfig.java similarity index 94% rename from src/main/java/flipnote/reaction/infrastructure/config/RabbitMqConfig.java rename to src/main/java/flipnote/reaction/infrastructure/messaging/RabbitMqConfig.java index b7f16b0..c1b8375 100644 --- a/src/main/java/flipnote/reaction/infrastructure/config/RabbitMqConfig.java +++ b/src/main/java/flipnote/reaction/infrastructure/messaging/RabbitMqConfig.java @@ -1,4 +1,4 @@ -package flipnote.reaction.infrastructure.config; +package flipnote.reaction.infrastructure.messaging; import org.springframework.amqp.core.TopicExchange; import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter; diff --git a/src/main/java/flipnote/reaction/infrastructure/messaging/ReactionEventPublisher.java b/src/main/java/flipnote/reaction/infrastructure/messaging/ReactionEventPublisher.java index 4c2cb5f..d8b76e2 100644 --- a/src/main/java/flipnote/reaction/infrastructure/messaging/ReactionEventPublisher.java +++ b/src/main/java/flipnote/reaction/infrastructure/messaging/ReactionEventPublisher.java @@ -3,7 +3,6 @@ import org.springframework.amqp.rabbit.core.RabbitTemplate; import org.springframework.stereotype.Component; -import flipnote.reaction.infrastructure.config.RabbitMqConfig; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; diff --git a/src/main/java/flipnote/reaction/infrastructure/config/AuditingConfig.java b/src/main/java/flipnote/reaction/infrastructure/persistence/AuditingConfig.java similarity index 78% rename from src/main/java/flipnote/reaction/infrastructure/config/AuditingConfig.java rename to src/main/java/flipnote/reaction/infrastructure/persistence/AuditingConfig.java index f41d87f..87a5431 100644 --- a/src/main/java/flipnote/reaction/infrastructure/config/AuditingConfig.java +++ b/src/main/java/flipnote/reaction/infrastructure/persistence/AuditingConfig.java @@ -1,4 +1,4 @@ -package flipnote.reaction.infrastructure.config; +package flipnote.reaction.infrastructure.persistence; import org.springframework.context.annotation.Configuration; import org.springframework.data.jpa.repository.config.EnableJpaAuditing; diff --git a/src/main/java/flipnote/reaction/interfaces/grpc/GrpcExceptionHandlerImpl.java b/src/main/java/flipnote/reaction/interfaces/grpc/GrpcExceptionHandlerImpl.java new file mode 100644 index 0000000..8f78899 --- /dev/null +++ b/src/main/java/flipnote/reaction/interfaces/grpc/GrpcExceptionHandlerImpl.java @@ -0,0 +1,52 @@ +package flipnote.reaction.interfaces.grpc; + +import org.springframework.grpc.server.exception.GrpcExceptionHandler; +import org.springframework.stereotype.Component; + +import flipnote.reaction.domain.common.BizException; +import flipnote.reaction.domain.common.ErrorCode; +import io.grpc.Status; +import io.grpc.StatusException; +import io.grpc.StatusRuntimeException; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@Component +public class GrpcExceptionHandlerImpl implements GrpcExceptionHandler { + + @Override + public StatusException handleException(Throwable t) { + if (t instanceof BizException e) { + ErrorCode errorCode = e.getErrorCode(); + log.warn("gRPC BizException: code={}, status={}, message={}", + errorCode.getCode(), errorCode.getStatus(), errorCode.getMessage()); + return toGrpcStatus(errorCode) + .withDescription(errorCode.getMessage()) + .asException(); + } + if (t instanceof StatusException e) { + log.warn("gRPC StatusException: status={}, description={}", + e.getStatus().getCode(), e.getStatus().getDescription()); + return e; + } + if (t instanceof StatusRuntimeException e) { + log.warn("gRPC StatusRuntimeException: status={}, description={}", + e.getStatus().getCode(), e.getStatus().getDescription()); + return e.getStatus().asException(e.getTrailers()); + } + log.error("gRPC Unhandled exception", t); + return Status.INTERNAL.withDescription("Internal server error").asException(); + } + + private Status toGrpcStatus(ErrorCode errorCode) { + return switch (errorCode.getStatus()) { + case 400 -> Status.INVALID_ARGUMENT; + case 401 -> Status.UNAUTHENTICATED; + case 403 -> Status.PERMISSION_DENIED; + case 404 -> Status.NOT_FOUND; + case 409 -> Status.ALREADY_EXISTS; + case 429 -> Status.RESOURCE_EXHAUSTED; + default -> Status.INTERNAL; + }; + } +} diff --git a/src/main/java/flipnote/reaction/interfaces/grpc/GrpcServerConfig.java b/src/main/java/flipnote/reaction/interfaces/grpc/GrpcServerConfig.java deleted file mode 100644 index de45214..0000000 --- a/src/main/java/flipnote/reaction/interfaces/grpc/GrpcServerConfig.java +++ /dev/null @@ -1,53 +0,0 @@ -package flipnote.reaction.interfaces.grpc; - -import java.io.IOException; - -import org.springframework.beans.factory.annotation.Value; -import org.springframework.context.SmartLifecycle; -import org.springframework.stereotype.Component; - -import io.grpc.Server; -import io.grpc.ServerBuilder; -import lombok.extern.slf4j.Slf4j; - -@Slf4j -@Component -public class GrpcServerConfig implements SmartLifecycle { - - private final Server server; - private boolean running; - - public GrpcServerConfig( - @Value("${grpc.server.port}") int port, - ReactionGrpcService reactionGrpcService - ) { - this.server = ServerBuilder.forPort(port) - .addService(reactionGrpcService) - .build(); - } - - @Override - public void start() { - try { - server.start(); - running = true; - log.info("gRPC server started on port {}", server.getPort()); - } catch (IOException e) { - throw new RuntimeException("Failed to start gRPC server", e); - } - } - - @Override - public void stop() { - if (server != null) { - server.shutdown(); - running = false; - log.info("gRPC server stopped"); - } - } - - @Override - public boolean isRunning() { - return running; - } -} diff --git a/src/main/java/flipnote/reaction/interfaces/grpc/ReactionGrpcService.java b/src/main/java/flipnote/reaction/interfaces/grpc/ReactionGrpcService.java index cea1975..e599b2d 100644 --- a/src/main/java/flipnote/reaction/interfaces/grpc/ReactionGrpcService.java +++ b/src/main/java/flipnote/reaction/interfaces/grpc/ReactionGrpcService.java @@ -3,65 +3,69 @@ import java.util.List; import java.util.Map; -import org.springframework.stereotype.Component; +import org.springframework.grpc.server.service.GrpcService; import flipnote.reaction.application.bookmark.BookmarkReader; import flipnote.reaction.application.like.LikeReader; import flipnote.reaction.domain.bookmark.BookmarkTargetType; import flipnote.reaction.domain.like.LikeTargetType; +import io.grpc.Status; import io.grpc.stub.StreamObserver; +import lombok.RequiredArgsConstructor; import reaction.Reaction.AreReactedRequest; import reaction.Reaction.AreReactedResponse; import reaction.Reaction.IsReactedRequest; import reaction.Reaction.IsReactedResponse; import reaction.ReactionServiceGrpc; -@Component +@GrpcService +@RequiredArgsConstructor public class ReactionGrpcService extends ReactionServiceGrpc.ReactionServiceImplBase { private final LikeReader likeReader; private final BookmarkReader bookmarkReader; - public ReactionGrpcService(LikeReader likeReader, BookmarkReader bookmarkReader) { - this.likeReader = likeReader; - this.bookmarkReader = bookmarkReader; - } - @Override public void isLiked(IsReactedRequest request, StreamObserver responseObserver) { - LikeTargetType targetType = LikeTargetType.valueOf(request.getTargetType()); + LikeTargetType targetType = parseTargetType(request.getTargetType(), LikeTargetType.class); boolean reacted = likeReader.isLiked(targetType, request.getTargetId(), request.getUserId()); - responseObserver.onNext(IsReactedResponse.newBuilder().setReacted(reacted).build()); responseObserver.onCompleted(); } @Override public void areLiked(AreReactedRequest request, StreamObserver responseObserver) { - LikeTargetType targetType = LikeTargetType.valueOf(request.getTargetType()); + LikeTargetType targetType = parseTargetType(request.getTargetType(), LikeTargetType.class); List targetIds = request.getTargetIdsList(); Map results = likeReader.areLiked(targetType, targetIds, request.getUserId()); - responseObserver.onNext(AreReactedResponse.newBuilder().putAllResults(results).build()); responseObserver.onCompleted(); } @Override public void isBookmarked(IsReactedRequest request, StreamObserver responseObserver) { - BookmarkTargetType targetType = BookmarkTargetType.valueOf(request.getTargetType()); + BookmarkTargetType targetType = parseTargetType(request.getTargetType(), BookmarkTargetType.class); boolean reacted = bookmarkReader.isBookmarked(targetType, request.getTargetId(), request.getUserId()); - responseObserver.onNext(IsReactedResponse.newBuilder().setReacted(reacted).build()); responseObserver.onCompleted(); } @Override public void areBookmarked(AreReactedRequest request, StreamObserver responseObserver) { - BookmarkTargetType targetType = BookmarkTargetType.valueOf(request.getTargetType()); + BookmarkTargetType targetType = parseTargetType(request.getTargetType(), BookmarkTargetType.class); List targetIds = request.getTargetIdsList(); Map results = bookmarkReader.areBookmarked(targetType, targetIds, request.getUserId()); - responseObserver.onNext(AreReactedResponse.newBuilder().putAllResults(results).build()); responseObserver.onCompleted(); } + + private > T parseTargetType(String value, Class enumClass) { + try { + return Enum.valueOf(enumClass, value); + } catch (IllegalArgumentException e) { + throw Status.INVALID_ARGUMENT + .withDescription("Invalid target type: " + value) + .asRuntimeException(); + } + } } diff --git a/src/main/java/flipnote/reaction/interfaces/http/BookmarkController.java b/src/main/java/flipnote/reaction/interfaces/http/BookmarkController.java index 6b8d200..f47e6c3 100644 --- a/src/main/java/flipnote/reaction/interfaces/http/BookmarkController.java +++ b/src/main/java/flipnote/reaction/interfaces/http/BookmarkController.java @@ -11,13 +11,13 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; +import flipnote.reaction.application.bookmark.BookmarkResult; import flipnote.reaction.application.bookmark.BookmarkService; import flipnote.reaction.domain.bookmark.BookmarkTargetType; import flipnote.reaction.interfaces.http.common.IdResponse; import flipnote.reaction.interfaces.http.common.PagingResponse; import flipnote.reaction.interfaces.http.dto.request.BookmarkSearchRequest; import flipnote.reaction.interfaces.http.dto.request.BookmarkTargetTypeRequest; -import flipnote.reaction.interfaces.http.dto.response.BookmarkResponse; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; @@ -35,7 +35,7 @@ public IdResponse addBookmark( @PathVariable Long targetId ) { BookmarkTargetType type = BookmarkTargetTypeRequest.from(targetType).toEntity(); - return bookmarkService.addBookmark(type, targetId, userId); + return IdResponse.from(bookmarkService.addBookmark(type, targetId, userId)); } @DeleteMapping("/{targetType}/{targetId}") @@ -49,12 +49,12 @@ public void removeBookmark( } @GetMapping("/{targetType}") - public PagingResponse getBookmarks( + public PagingResponse getBookmarks( @RequestHeader(USER_ID) Long userId, @PathVariable String targetType, @Valid @ModelAttribute BookmarkSearchRequest request ) { BookmarkTargetType type = BookmarkTargetTypeRequest.from(targetType).toEntity(); - return bookmarkService.getBookmarks(type, userId, request); + return PagingResponse.from(bookmarkService.getBookmarks(type, userId, request)); } } diff --git a/src/main/java/flipnote/reaction/interfaces/http/LikeController.java b/src/main/java/flipnote/reaction/interfaces/http/LikeController.java index 49ce2b7..d999545 100644 --- a/src/main/java/flipnote/reaction/interfaces/http/LikeController.java +++ b/src/main/java/flipnote/reaction/interfaces/http/LikeController.java @@ -11,13 +11,13 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; +import flipnote.reaction.application.like.LikeResult; import flipnote.reaction.application.like.LikeService; import flipnote.reaction.domain.like.LikeTargetType; import flipnote.reaction.interfaces.http.common.IdResponse; import flipnote.reaction.interfaces.http.common.PagingResponse; import flipnote.reaction.interfaces.http.dto.request.LikeSearchRequest; import flipnote.reaction.interfaces.http.dto.request.LikeTargetTypeRequest; -import flipnote.reaction.interfaces.http.dto.response.LikeResponse; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; @@ -35,7 +35,7 @@ public IdResponse addLike( @PathVariable Long targetId ) { LikeTargetType type = LikeTargetTypeRequest.from(targetType).toEntity(); - return likeService.addLike(type, targetId, userId); + return IdResponse.from(likeService.addLike(type, targetId, userId)); } @DeleteMapping("/{targetType}/{targetId}") @@ -49,12 +49,12 @@ public void removeLike( } @GetMapping("/{targetType}") - public PagingResponse getLikes( + public PagingResponse getLikes( @RequestHeader(USER_ID) Long userId, @PathVariable String targetType, @Valid @ModelAttribute LikeSearchRequest request ) { LikeTargetType type = LikeTargetTypeRequest.from(targetType).toEntity(); - return likeService.getLikes(type, userId, request); + return PagingResponse.from(likeService.getLikes(type, userId, request)); } } diff --git a/src/main/java/flipnote/reaction/interfaces/http/common/PagingRequest.java b/src/main/java/flipnote/reaction/interfaces/http/common/PagingRequest.java index 1f4970b..2dadcb4 100644 --- a/src/main/java/flipnote/reaction/interfaces/http/common/PagingRequest.java +++ b/src/main/java/flipnote/reaction/interfaces/http/common/PagingRequest.java @@ -42,6 +42,6 @@ public Sort.Direction getOrder() { } public String getSortBy() { - return sortBy != null ? sortBy.toUpperCase() : null; + return sortBy; } } diff --git a/src/main/java/flipnote/reaction/interfaces/http/dto/response/.gitkeep b/src/main/java/flipnote/reaction/interfaces/http/dto/response/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/src/main/java/flipnote/reaction/interfaces/http/dto/response/BookmarkResponse.java b/src/main/java/flipnote/reaction/interfaces/http/dto/response/BookmarkResponse.java deleted file mode 100644 index a397780..0000000 --- a/src/main/java/flipnote/reaction/interfaces/http/dto/response/BookmarkResponse.java +++ /dev/null @@ -1,19 +0,0 @@ -package flipnote.reaction.interfaces.http.dto.response; - -import java.time.LocalDateTime; - -import flipnote.reaction.domain.bookmark.Bookmark; - -public record BookmarkResponse( - String targetType, - Long targetId, - LocalDateTime bookmarkedAt -) { - public static BookmarkResponse from(Bookmark bookmark) { - return new BookmarkResponse( - bookmark.getTargetType().name(), - bookmark.getTargetId(), - bookmark.getCreatedAt() - ); - } -} diff --git a/src/main/java/flipnote/reaction/interfaces/http/dto/response/LikeResponse.java b/src/main/java/flipnote/reaction/interfaces/http/dto/response/LikeResponse.java deleted file mode 100644 index 5f39745..0000000 --- a/src/main/java/flipnote/reaction/interfaces/http/dto/response/LikeResponse.java +++ /dev/null @@ -1,19 +0,0 @@ -package flipnote.reaction.interfaces.http.dto.response; - -import java.time.LocalDateTime; - -import flipnote.reaction.domain.like.Like; - -public record LikeResponse( - String targetType, - Long targetId, - LocalDateTime likedAt -) { - public static LikeResponse from(Like like) { - return new LikeResponse( - like.getTargetType().name(), - like.getTargetId(), - like.getCreatedAt() - ); - } -} diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 9984939..1e2a6c6 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -14,17 +14,18 @@ spring: jpa: open-in-view: false hibernate: - ddl-auto: update + ddl-auto: ${DDL_AUTO:validate} properties: hibernate: dialect: org.hibernate.dialect.MySQLDialect - -grpc: - server: - port: ${GRPC_SERVER_PORT:9093} - cardset: - host: ${GRPC_CARDSET_HOST:cardset-service} - port: ${GRPC_CARDSET_PORT:9095} + grpc: + server: + port: ${GRPC_SERVER_PORT:9093} + client: + channels: + cardset: + address: ${GRPC_CARDSET_ADDRESS:static://cardset-service:9095} + negotiation-type: PLAINTEXT server: port: 8083 diff --git a/src/test/java/flipnote/reaction/config/TestGrpcConfig.java b/src/test/java/flipnote/reaction/config/TestGrpcConfig.java index e0ab496..eaedda7 100644 --- a/src/test/java/flipnote/reaction/config/TestGrpcConfig.java +++ b/src/test/java/flipnote/reaction/config/TestGrpcConfig.java @@ -4,18 +4,12 @@ import org.springframework.context.annotation.Bean; import flipnote.reaction.infrastructure.grpc.CardSetGrpcClient; -import io.grpc.ManagedChannel; import static org.mockito.Mockito.mock; @TestConfiguration public class TestGrpcConfig { - @Bean - public ManagedChannel cardSetChannel() { - return mock(ManagedChannel.class); - } - @Bean public CardSetGrpcClient cardSetGrpcClient() { return mock(CardSetGrpcClient.class); diff --git a/src/test/resources/application.yml b/src/test/resources/application.yml index 0e1effb..62a9de7 100644 --- a/src/test/resources/application.yml +++ b/src/test/resources/application.yml @@ -15,10 +15,11 @@ spring: properties: hibernate: dialect: org.hibernate.dialect.H2Dialect - -grpc: - server: - port: 0 - cardset: - host: localhost - port: 9090 + grpc: + server: + port: 0 + client: + channels: + cardset: + address: static://localhost:9090 + negotiation-type: PLAINTEXT From b77afaa5f505b9fda0f98be2f63370ae699135af Mon Sep 17 00:00:00 2001 From: dungbik Date: Fri, 3 Apr 2026 14:09:48 +0900 Subject: [PATCH 3/5] =?UTF-8?q?Refactor:=20=ED=94=84=EB=A1=9C=EC=A0=9D?= =?UTF-8?q?=ED=8A=B8=20=EA=B5=AC=EC=A1=B0=20=EB=A6=AC=ED=8C=A9=ED=86=A0?= =?UTF-8?q?=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/bookmark/BookmarkResult.java | 5 +-- .../application/bookmark/BookmarkService.java | 43 ++++++++++--------- .../common/CardSetSummaryResult.java | 14 ------ .../reaction/application/like/LikeResult.java | 5 +-- .../application/like/LikeService.java | 43 ++++++++++--------- .../bookmark/event/BookmarkAddedEvent.java | 8 ++++ .../bookmark/event/BookmarkRemovedEvent.java | 8 ++++ .../domain/like/event/LikeAddedEvent.java | 8 ++++ .../domain/like/event/LikeRemovedEvent.java | 8 ++++ .../grpc/CardSetGrpcClient.java | 16 +++++-- .../messaging/ReactionEventListener.java | 38 ++++++++++++++++ .../messaging/ReactionEventPublisher.java | 19 +++++++- 12 files changed, 147 insertions(+), 68 deletions(-) create mode 100644 src/main/java/flipnote/reaction/domain/bookmark/event/BookmarkAddedEvent.java create mode 100644 src/main/java/flipnote/reaction/domain/bookmark/event/BookmarkRemovedEvent.java create mode 100644 src/main/java/flipnote/reaction/domain/like/event/LikeAddedEvent.java create mode 100644 src/main/java/flipnote/reaction/domain/like/event/LikeRemovedEvent.java create mode 100644 src/main/java/flipnote/reaction/infrastructure/messaging/ReactionEventListener.java diff --git a/src/main/java/flipnote/reaction/application/bookmark/BookmarkResult.java b/src/main/java/flipnote/reaction/application/bookmark/BookmarkResult.java index d4c7964..bbd9551 100644 --- a/src/main/java/flipnote/reaction/application/bookmark/BookmarkResult.java +++ b/src/main/java/flipnote/reaction/application/bookmark/BookmarkResult.java @@ -2,7 +2,6 @@ import java.time.LocalDateTime; -import cardset.Cardset.CardSetSummary; import flipnote.reaction.application.common.CardSetSummaryResult; import flipnote.reaction.domain.bookmark.Bookmark; @@ -12,12 +11,12 @@ public record BookmarkResult( LocalDateTime bookmarkedAt, CardSetSummaryResult cardSet ) { - public static BookmarkResult from(Bookmark bookmark, CardSetSummary cardSet) { + public static BookmarkResult from(Bookmark bookmark, CardSetSummaryResult cardSet) { return new BookmarkResult( bookmark.getTargetType().name(), bookmark.getTargetId(), bookmark.getCreatedAt(), - cardSet != null ? CardSetSummaryResult.from(cardSet) : null + cardSet ); } } diff --git a/src/main/java/flipnote/reaction/application/bookmark/BookmarkService.java b/src/main/java/flipnote/reaction/application/bookmark/BookmarkService.java index 534e1bc..dfa0bf4 100644 --- a/src/main/java/flipnote/reaction/application/bookmark/BookmarkService.java +++ b/src/main/java/flipnote/reaction/application/bookmark/BookmarkService.java @@ -3,24 +3,27 @@ import java.util.List; import java.util.Map; +import org.springframework.context.ApplicationEventPublisher; import org.springframework.dao.DataIntegrityViolationException; import org.springframework.data.domain.Page; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import cardset.Cardset.CardSetSummary; +import flipnote.reaction.application.common.CardSetSummaryResult; import flipnote.reaction.domain.bookmark.Bookmark; import flipnote.reaction.domain.bookmark.BookmarkErrorCode; import flipnote.reaction.domain.bookmark.BookmarkRepository; import flipnote.reaction.domain.bookmark.BookmarkTargetType; +import flipnote.reaction.domain.bookmark.event.BookmarkAddedEvent; +import flipnote.reaction.domain.bookmark.event.BookmarkRemovedEvent; import flipnote.reaction.domain.common.BizException; import flipnote.reaction.domain.common.CommonErrorCode; -import flipnote.reaction.infrastructure.messaging.RabbitMqConfig; import flipnote.reaction.infrastructure.grpc.CardSetGrpcClient; -import flipnote.reaction.infrastructure.messaging.ReactionEventPublisher; import flipnote.reaction.interfaces.http.dto.request.BookmarkSearchRequest; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +@Slf4j @Service @RequiredArgsConstructor @Transactional(readOnly = true) @@ -28,7 +31,7 @@ public class BookmarkService { private final BookmarkRepository bookmarkRepository; private final BookmarkReader bookmarkReader; - private final ReactionEventPublisher eventPublisher; + private final ApplicationEventPublisher eventPublisher; private final CardSetGrpcClient cardSetGrpcClient; @Transactional(noRollbackFor = DataIntegrityViolationException.class) @@ -53,13 +56,7 @@ public Long addBookmark(BookmarkTargetType targetType, Long targetId, Long userI throw new BizException(BookmarkErrorCode.ALREADY_BOOKMARKED); } - eventPublisher.publish( - RabbitMqConfig.ROUTING_KEY_BOOKMARK_ADDED, - "BOOKMARK_ADDED", - targetType.name(), - targetId, - userId - ); + eventPublisher.publishEvent(new BookmarkAddedEvent(targetType.name(), targetId, userId)); return bookmark.getId(); } @@ -69,13 +66,7 @@ public void removeBookmark(BookmarkTargetType targetType, Long targetId, Long us Bookmark bookmark = bookmarkReader.findByTargetAndUserId(targetType, targetId, userId); bookmarkRepository.delete(bookmark); - eventPublisher.publish( - RabbitMqConfig.ROUTING_KEY_BOOKMARK_REMOVED, - "BOOKMARK_REMOVED", - targetType.name(), - targetId, - userId - ); + eventPublisher.publishEvent(new BookmarkRemovedEvent(targetType.name(), targetId, userId)); } public Page getBookmarks(BookmarkTargetType targetType, Long userId, @@ -88,10 +79,20 @@ public Page getBookmarks(BookmarkTargetType targetType, Long use .map(Bookmark::getTargetId) .toList(); - Map cardSetMap = targetIds.isEmpty() - ? Map.of() - : cardSetGrpcClient.getCardSetsByIds(targetIds, userId); + Map cardSetMap = fetchCardSets(targetIds, userId); return bookmarkPage.map(b -> BookmarkResult.from(b, cardSetMap.get(b.getTargetId()))); } + + private Map fetchCardSets(List targetIds, Long userId) { + if (targetIds.isEmpty()) { + return Map.of(); + } + try { + return cardSetGrpcClient.getCardSetsByIds(targetIds, userId); + } catch (BizException e) { + log.warn("CardSet 조회 실패 — cardSet 없이 반환합니다: {}", e.getMessage()); + return Map.of(); + } + } } diff --git a/src/main/java/flipnote/reaction/application/common/CardSetSummaryResult.java b/src/main/java/flipnote/reaction/application/common/CardSetSummaryResult.java index adcdf36..42ff7d9 100644 --- a/src/main/java/flipnote/reaction/application/common/CardSetSummaryResult.java +++ b/src/main/java/flipnote/reaction/application/common/CardSetSummaryResult.java @@ -1,7 +1,5 @@ package flipnote.reaction.application.common; -import cardset.Cardset.CardSetSummary; - public record CardSetSummaryResult( Long id, String name, @@ -12,16 +10,4 @@ public record CardSetSummaryResult( Long imageRefId, Long cardCount ) { - public static CardSetSummaryResult from(CardSetSummary summary) { - return new CardSetSummaryResult( - summary.getId(), - summary.getName(), - summary.getGroupId(), - summary.getVisibility(), - summary.getCategory(), - summary.getHashtag(), - summary.hasImageRefId() ? summary.getImageRefId() : null, - summary.getCardCount() - ); - } } diff --git a/src/main/java/flipnote/reaction/application/like/LikeResult.java b/src/main/java/flipnote/reaction/application/like/LikeResult.java index b51e7b0..d0fc708 100644 --- a/src/main/java/flipnote/reaction/application/like/LikeResult.java +++ b/src/main/java/flipnote/reaction/application/like/LikeResult.java @@ -2,7 +2,6 @@ import java.time.LocalDateTime; -import cardset.Cardset.CardSetSummary; import flipnote.reaction.application.common.CardSetSummaryResult; import flipnote.reaction.domain.like.Like; @@ -12,12 +11,12 @@ public record LikeResult( LocalDateTime likedAt, CardSetSummaryResult cardSet ) { - public static LikeResult from(Like like, CardSetSummary cardSet) { + public static LikeResult from(Like like, CardSetSummaryResult cardSet) { return new LikeResult( like.getTargetType().name(), like.getTargetId(), like.getCreatedAt(), - cardSet != null ? CardSetSummaryResult.from(cardSet) : null + cardSet ); } } diff --git a/src/main/java/flipnote/reaction/application/like/LikeService.java b/src/main/java/flipnote/reaction/application/like/LikeService.java index 0101dd2..df94047 100644 --- a/src/main/java/flipnote/reaction/application/like/LikeService.java +++ b/src/main/java/flipnote/reaction/application/like/LikeService.java @@ -3,24 +3,27 @@ import java.util.List; import java.util.Map; +import org.springframework.context.ApplicationEventPublisher; import org.springframework.dao.DataIntegrityViolationException; import org.springframework.data.domain.Page; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import cardset.Cardset.CardSetSummary; +import flipnote.reaction.application.common.CardSetSummaryResult; import flipnote.reaction.domain.common.BizException; import flipnote.reaction.domain.common.CommonErrorCode; import flipnote.reaction.domain.like.Like; import flipnote.reaction.domain.like.LikeErrorCode; import flipnote.reaction.domain.like.LikeRepository; import flipnote.reaction.domain.like.LikeTargetType; -import flipnote.reaction.infrastructure.messaging.RabbitMqConfig; +import flipnote.reaction.domain.like.event.LikeAddedEvent; +import flipnote.reaction.domain.like.event.LikeRemovedEvent; import flipnote.reaction.infrastructure.grpc.CardSetGrpcClient; -import flipnote.reaction.infrastructure.messaging.ReactionEventPublisher; import flipnote.reaction.interfaces.http.dto.request.LikeSearchRequest; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +@Slf4j @Service @RequiredArgsConstructor @Transactional(readOnly = true) @@ -28,7 +31,7 @@ public class LikeService { private final LikeRepository likeRepository; private final LikeReader likeReader; - private final ReactionEventPublisher eventPublisher; + private final ApplicationEventPublisher eventPublisher; private final CardSetGrpcClient cardSetGrpcClient; @Transactional(noRollbackFor = DataIntegrityViolationException.class) @@ -53,13 +56,7 @@ public Long addLike(LikeTargetType targetType, Long targetId, Long userId) { throw new BizException(LikeErrorCode.ALREADY_LIKED); } - eventPublisher.publish( - RabbitMqConfig.ROUTING_KEY_LIKE_ADDED, - "LIKE_ADDED", - targetType.name(), - targetId, - userId - ); + eventPublisher.publishEvent(new LikeAddedEvent(targetType.name(), targetId, userId)); return like.getId(); } @@ -69,13 +66,7 @@ public void removeLike(LikeTargetType targetType, Long targetId, Long userId) { Like like = likeReader.findByTargetAndUserId(targetType, targetId, userId); likeRepository.delete(like); - eventPublisher.publish( - RabbitMqConfig.ROUTING_KEY_LIKE_REMOVED, - "LIKE_REMOVED", - targetType.name(), - targetId, - userId - ); + eventPublisher.publishEvent(new LikeRemovedEvent(targetType.name(), targetId, userId)); } public Page getLikes(LikeTargetType targetType, Long userId, LikeSearchRequest request) { @@ -87,10 +78,20 @@ public Page getLikes(LikeTargetType targetType, Long userId, LikeSea .map(Like::getTargetId) .toList(); - Map cardSetMap = targetIds.isEmpty() - ? Map.of() - : cardSetGrpcClient.getCardSetsByIds(targetIds, userId); + Map cardSetMap = fetchCardSets(targetIds, userId); return likePage.map(l -> LikeResult.from(l, cardSetMap.get(l.getTargetId()))); } + + private Map fetchCardSets(List targetIds, Long userId) { + if (targetIds.isEmpty()) { + return Map.of(); + } + try { + return cardSetGrpcClient.getCardSetsByIds(targetIds, userId); + } catch (BizException e) { + log.warn("CardSet 조회 실패 — cardSet 없이 반환합니다: {}", e.getMessage()); + return Map.of(); + } + } } diff --git a/src/main/java/flipnote/reaction/domain/bookmark/event/BookmarkAddedEvent.java b/src/main/java/flipnote/reaction/domain/bookmark/event/BookmarkAddedEvent.java new file mode 100644 index 0000000..87421e2 --- /dev/null +++ b/src/main/java/flipnote/reaction/domain/bookmark/event/BookmarkAddedEvent.java @@ -0,0 +1,8 @@ +package flipnote.reaction.domain.bookmark.event; + +public record BookmarkAddedEvent( + String targetType, + Long targetId, + Long userId +) { +} diff --git a/src/main/java/flipnote/reaction/domain/bookmark/event/BookmarkRemovedEvent.java b/src/main/java/flipnote/reaction/domain/bookmark/event/BookmarkRemovedEvent.java new file mode 100644 index 0000000..34915b4 --- /dev/null +++ b/src/main/java/flipnote/reaction/domain/bookmark/event/BookmarkRemovedEvent.java @@ -0,0 +1,8 @@ +package flipnote.reaction.domain.bookmark.event; + +public record BookmarkRemovedEvent( + String targetType, + Long targetId, + Long userId +) { +} diff --git a/src/main/java/flipnote/reaction/domain/like/event/LikeAddedEvent.java b/src/main/java/flipnote/reaction/domain/like/event/LikeAddedEvent.java new file mode 100644 index 0000000..01e0876 --- /dev/null +++ b/src/main/java/flipnote/reaction/domain/like/event/LikeAddedEvent.java @@ -0,0 +1,8 @@ +package flipnote.reaction.domain.like.event; + +public record LikeAddedEvent( + String targetType, + Long targetId, + Long userId +) { +} diff --git a/src/main/java/flipnote/reaction/domain/like/event/LikeRemovedEvent.java b/src/main/java/flipnote/reaction/domain/like/event/LikeRemovedEvent.java new file mode 100644 index 0000000..2ac8e54 --- /dev/null +++ b/src/main/java/flipnote/reaction/domain/like/event/LikeRemovedEvent.java @@ -0,0 +1,8 @@ +package flipnote.reaction.domain.like.event; + +public record LikeRemovedEvent( + String targetType, + Long targetId, + Long userId +) { +} diff --git a/src/main/java/flipnote/reaction/infrastructure/grpc/CardSetGrpcClient.java b/src/main/java/flipnote/reaction/infrastructure/grpc/CardSetGrpcClient.java index dc73b79..3749a79 100644 --- a/src/main/java/flipnote/reaction/infrastructure/grpc/CardSetGrpcClient.java +++ b/src/main/java/flipnote/reaction/infrastructure/grpc/CardSetGrpcClient.java @@ -2,17 +2,16 @@ import java.util.List; import java.util.Map; -import java.util.function.Function; import java.util.stream.Collectors; import org.springframework.stereotype.Component; -import cardset.Cardset.CardSetSummary; import cardset.Cardset.GetCardSetsByIdsRequest; import cardset.Cardset.GetCardSetsByIdsResponse; import cardset.Cardset.IsCardSetViewableRequest; import cardset.Cardset.IsCardSetViewableResponse; import cardset.CardsetServiceGrpc; +import flipnote.reaction.application.common.CardSetSummaryResult; import flipnote.reaction.domain.common.BizException; import flipnote.reaction.domain.common.CommonErrorCode; import io.grpc.StatusRuntimeException; @@ -43,7 +42,7 @@ public boolean isCardSetViewable(Long cardSetId, Long userId) { } } - public Map getCardSetsByIds(List cardSetIds, Long userId) { + public Map getCardSetsByIds(List cardSetIds, Long userId) { try { GetCardSetsByIdsRequest request = GetCardSetsByIdsRequest.newBuilder() .addAllCardSetIds(cardSetIds) @@ -54,7 +53,16 @@ public Map getCardSetsByIds(List cardSetIds, Long us return response.getCardSetsList().stream() .collect(Collectors.toMap( cs -> (long) cs.getId(), - Function.identity() + cs -> new CardSetSummaryResult( + (long) cs.getId(), + cs.getName(), + (long) cs.getGroupId(), + cs.getVisibility(), + cs.getCategory(), + cs.getHashtag(), + (long) cs.getImageRefId(), + (long) cs.getCardCount() + ) )); } catch (StatusRuntimeException e) { log.error("gRPC call failed: GetCardSetsByIds, cardSetIds={}, userId={}", cardSetIds, userId, e); diff --git a/src/main/java/flipnote/reaction/infrastructure/messaging/ReactionEventListener.java b/src/main/java/flipnote/reaction/infrastructure/messaging/ReactionEventListener.java new file mode 100644 index 0000000..c6b0fb8 --- /dev/null +++ b/src/main/java/flipnote/reaction/infrastructure/messaging/ReactionEventListener.java @@ -0,0 +1,38 @@ +package flipnote.reaction.infrastructure.messaging; + +import org.springframework.stereotype.Component; +import org.springframework.transaction.event.TransactionPhase; +import org.springframework.transaction.event.TransactionalEventListener; + +import flipnote.reaction.domain.bookmark.event.BookmarkAddedEvent; +import flipnote.reaction.domain.bookmark.event.BookmarkRemovedEvent; +import flipnote.reaction.domain.like.event.LikeAddedEvent; +import flipnote.reaction.domain.like.event.LikeRemovedEvent; +import lombok.RequiredArgsConstructor; + +@Component +@RequiredArgsConstructor +public class ReactionEventListener { + + private final ReactionEventPublisher eventPublisher; + + @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT) + public void onBookmarkAdded(BookmarkAddedEvent event) { + eventPublisher.bookmarkAdded(event.targetType(), event.targetId(), event.userId()); + } + + @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT) + public void onBookmarkRemoved(BookmarkRemovedEvent event) { + eventPublisher.bookmarkRemoved(event.targetType(), event.targetId(), event.userId()); + } + + @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT) + public void onLikeAdded(LikeAddedEvent event) { + eventPublisher.likeAdded(event.targetType(), event.targetId(), event.userId()); + } + + @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT) + public void onLikeRemoved(LikeRemovedEvent event) { + eventPublisher.likeRemoved(event.targetType(), event.targetId(), event.userId()); + } +} diff --git a/src/main/java/flipnote/reaction/infrastructure/messaging/ReactionEventPublisher.java b/src/main/java/flipnote/reaction/infrastructure/messaging/ReactionEventPublisher.java index d8b76e2..a41670a 100644 --- a/src/main/java/flipnote/reaction/infrastructure/messaging/ReactionEventPublisher.java +++ b/src/main/java/flipnote/reaction/infrastructure/messaging/ReactionEventPublisher.java @@ -13,8 +13,23 @@ public class ReactionEventPublisher { private final RabbitTemplate rabbitTemplate; - public void publish(String routingKey, String eventType, - String targetType, Long targetId, Long userId) { + public void bookmarkAdded(String targetType, Long targetId, Long userId) { + publish(RabbitMqConfig.ROUTING_KEY_BOOKMARK_ADDED, "BOOKMARK_ADDED", targetType, targetId, userId); + } + + public void bookmarkRemoved(String targetType, Long targetId, Long userId) { + publish(RabbitMqConfig.ROUTING_KEY_BOOKMARK_REMOVED, "BOOKMARK_REMOVED", targetType, targetId, userId); + } + + public void likeAdded(String targetType, Long targetId, Long userId) { + publish(RabbitMqConfig.ROUTING_KEY_LIKE_ADDED, "LIKE_ADDED", targetType, targetId, userId); + } + + public void likeRemoved(String targetType, Long targetId, Long userId) { + publish(RabbitMqConfig.ROUTING_KEY_LIKE_REMOVED, "LIKE_REMOVED", targetType, targetId, userId); + } + + private void publish(String routingKey, String eventType, String targetType, Long targetId, Long userId) { try { rabbitTemplate.convertAndSend( RabbitMqConfig.EXCHANGE, From fa6cd9569bd8babf7c0a4df65b513faf5bd37356 Mon Sep 17 00:00:00 2001 From: dungbik Date: Fri, 3 Apr 2026 14:11:08 +0900 Subject: [PATCH 4/5] =?UTF-8?q?Chore:=20README=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 234 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 234 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..8bef5f2 --- /dev/null +++ b/README.md @@ -0,0 +1,234 @@ +# 📒 FlipNote — Reaction Service + +**FlipNote 서비스의 반응(좋아요·북마크) 도메인 백엔드 레포지토리입니다.** + +![Spring Boot](https://img.shields.io/badge/Spring_Boot_4-6DB33F?logo=springboot&logoColor=white) +![Java](https://img.shields.io/badge/Java_21-007396?logo=openjdk&logoColor=white) +![MySQL](https://img.shields.io/badge/MySQL-4479A1?logo=mysql&logoColor=white) +![RabbitMQ](https://img.shields.io/badge/RabbitMQ-FF6600?logo=rabbitmq&logoColor=white) +![Deploy](https://img.shields.io/badge/Deploy-GHCR%20%2B%20Docker-2496ED?logo=docker&logoColor=white) + +--- + +## 📑 목차 + +- [시작하기](#시작하기) +- [환경 변수](#환경-변수) +- [실행 및 배포](#실행-및-배포) +- [프로젝트 구조](#프로젝트-구조) + +--- + + + +## 🚀 시작하기 + +### 사전 요구사항 + +- **Java** 21 이상 +- **Gradle** 8 이상 +- **MySQL** 8 이상 +- **RabbitMQ** 3 이상 + +### 설치 + +```bash +# 의존성 설치 및 빌드 +./gradlew build -x test +``` + +--- + + + +## 🔐 환경 변수 + +`application.yml`에서 참조하는 환경 변수 목록입니다. 로컬 실행 시 `.env` 또는 IDE 실행 구성에 아래 변수를 설정합니다. + +```text +# ─── Database ─────────────────────────────────────────── +DB_URL=jdbc:mysql://localhost:3306/flipnote_reaction +DB_USERNAME= +DB_PASSWORD= +# create | create-drop | update | validate | none +DDL_AUTO=validate + +# ─── RabbitMQ ─────────────────────────────────────────── +RABBITMQ_HOST=localhost +RABBITMQ_PORT=5672 +RABBITMQ_USERNAME=guest +RABBITMQ_PASSWORD=guest + +# ─── gRPC ─────────────────────────────────────────────── +# Reaction 서비스 gRPC 서버 포트 (기본값 9093) +GRPC_SERVER_PORT=9093 +# CardSet 서비스 gRPC 클라이언트 주소 (기본값 static://cardset-service:9095) +GRPC_CARDSET_ADDRESS=static://localhost:9095 +``` + +> **⚠️ 주의**: 환경 변수 파일은 절대 git에 커밋하지 마세요. `.gitignore`에 포함되어 있는지 반드시 확인하세요. + +--- + + + +## 🖥️ 실행 및 배포 + +### 로컬 개발 서버 실행 + +```bash +./gradlew bootRun +``` + +기본적으로 `http://localhost:8083`에서 실행됩니다. +gRPC 서버는 기본적으로 `9093` 포트에서 실행됩니다. + +### 프로덕션 빌드 + +```bash +./gradlew bootJar +``` + +`build/libs/reaction-0.0.1-SNAPSHOT.jar` 파일이 생성됩니다. + +### 테스트 실행 + +```bash +./gradlew test +``` + +### Docker 이미지 빌드 및 실행 + +```bash +# 이미지 빌드 +docker build -t flipnote-reaction . + +# 컨테이너 실행 +docker run -p 8083:8083 -p 9093:9093 \ + -e DB_URL=... \ + -e RABBITMQ_HOST=... \ + flipnote-reaction +``` + +### 배포 (GitHub Actions) + +`main` 브랜치에 push 시 GitHub Actions가 자동으로 아래 과정을 실행합니다. + +**CI** (`push` / `pull_request` → `main`) +1. JDK 21 설치 +2. `./gradlew build -x test` — 빌드 검증 +3. `./gradlew test` — 테스트 실행 +4. Dependency-Check — 취약점 분석 리포트 생성 + +**CD** (`push` → `main`) +1. GitHub Container Registry(GHCR) 로그인 +2. Docker 이미지 빌드 +3. `ghcr.io/dungbik/flipnote-reaction` 이미지 Push + +> 배포에 필요한 시크릿(`ORG_PAT`)은 GitHub Repository → Settings → Secrets and variables → Actions에 등록해야 합니다. + +--- + + + +## 📁 프로젝트 구조 + +- 간략화 버전 + + ```text + src/main/java/flipnote/reaction/ + ├── domain/ # 도메인 레이어 (엔티티, 레포지토리, 도메인 이벤트, 에러코드) + ├── application/ # 애플리케이션 레이어 (서비스, 조회 컴포넌트, Result 객체) + ├── infrastructure/ # 인프라 레이어 (영속성, 메시징, gRPC 클라이언트) + └── interfaces/ # 인터페이스 레이어 (HTTP, gRPC 진입점) + ``` + +```text +FlipNote-Reaction/ +├── src/ +│ ├── main/ +│ │ ├── java/flipnote/reaction/ +│ │ │ ├── ReactionApplication.java +│ │ │ │ +│ │ │ ├── domain/ # 도메인 레이어 +│ │ │ │ ├── common/ # 도메인 공통 +│ │ │ │ │ ├── ErrorCode.java +│ │ │ │ │ ├── BizException.java +│ │ │ │ │ ├── CommonErrorCode.java +│ │ │ │ │ └── BaseEntity.java +│ │ │ │ ├── bookmark/ # 북마크 도메인 +│ │ │ │ │ ├── Bookmark.java +│ │ │ │ │ ├── BookmarkTargetType.java +│ │ │ │ │ ├── BookmarkRepository.java +│ │ │ │ │ ├── BookmarkErrorCode.java +│ │ │ │ │ └── event/ # 북마크 도메인 이벤트 +│ │ │ │ │ ├── BookmarkAddedEvent.java +│ │ │ │ │ └── BookmarkRemovedEvent.java +│ │ │ │ └── like/ # 좋아요 도메인 +│ │ │ │ ├── Like.java +│ │ │ │ ├── LikeTargetType.java +│ │ │ │ ├── LikeRepository.java +│ │ │ │ ├── LikeErrorCode.java +│ │ │ │ └── event/ # 좋아요 도메인 이벤트 +│ │ │ │ ├── LikeAddedEvent.java +│ │ │ │ └── LikeRemovedEvent.java +│ │ │ │ +│ │ │ ├── application/ # 애플리케이션 레이어 +│ │ │ │ ├── common/ +│ │ │ │ │ └── CardSetSummaryResult.java # 공유 Result 객체 +│ │ │ │ ├── bookmark/ +│ │ │ │ │ ├── BookmarkService.java +│ │ │ │ │ ├── BookmarkReader.java +│ │ │ │ │ └── BookmarkResult.java # 서비스 반환 Result +│ │ │ │ └── like/ +│ │ │ │ ├── LikeService.java +│ │ │ │ ├── LikeReader.java +│ │ │ │ └── LikeResult.java # 서비스 반환 Result +│ │ │ │ +│ │ │ ├── infrastructure/ # 인프라 레이어 +│ │ │ │ ├── grpc/ # gRPC 클라이언트 +│ │ │ │ │ ├── GrpcConfig.java +│ │ │ │ │ └── CardSetGrpcClient.java +│ │ │ │ ├── messaging/ # 메시지 발행 + 설정 +│ │ │ │ │ ├── RabbitMqConfig.java +│ │ │ │ │ ├── ReactionMessage.java +│ │ │ │ │ ├── ReactionEventPublisher.java +│ │ │ │ │ └── ReactionEventListener.java # @TransactionalEventListener +│ │ │ │ └── persistence/ # 영속성 어댑터 + JPA 설정 +│ │ │ │ ├── AuditingConfig.java +│ │ │ │ ├── BookmarkRepositoryAdapter.java +│ │ │ │ ├── SpringDataBookmarkRepository.java +│ │ │ │ ├── LikeRepositoryAdapter.java +│ │ │ │ └── SpringDataLikeRepository.java +│ │ │ │ +│ │ │ └── interfaces/ # 인터페이스 레이어 +│ │ │ ├── http/ # HTTP 진입점 +│ │ │ │ ├── BookmarkController.java +│ │ │ │ ├── LikeController.java +│ │ │ │ ├── common/ # 공통 응답, 예외 처리 +│ │ │ │ │ ├── ApiResponse.java +│ │ │ │ │ ├── ApiResponseAdvice.java +│ │ │ │ │ ├── GlobalExceptionHandler.java +│ │ │ │ │ ├── HeaderConstants.java +│ │ │ │ │ ├── IdResponse.java +│ │ │ │ │ ├── PagingRequest.java +│ │ │ │ │ └── PagingResponse.java +│ │ │ │ └── dto/request/ +│ │ │ │ ├── BookmarkSearchRequest.java +│ │ │ │ ├── BookmarkTargetTypeRequest.java +│ │ │ │ ├── LikeSearchRequest.java +│ │ │ │ └── LikeTargetTypeRequest.java +│ │ │ └── grpc/ # gRPC 서버 진입점 +│ │ │ ├── ReactionGrpcService.java +│ │ │ └── GrpcExceptionHandlerImpl.java +│ │ │ +│ │ └── resources/ +│ │ └── application.yml +│ │ +│ └── test/ +│ └── java/flipnote/reaction/ +│ +├── Dockerfile +├── build.gradle.kts +└── settings.gradle.kts +``` From 1c8302774c970685cdbbf26b58a610db85399c57 Mon Sep 17 00:00:00 2001 From: dungbik Date: Fri, 3 Apr 2026 14:19:16 +0900 Subject: [PATCH 5/5] =?UTF-8?q?Fix:=20RabbitMQ=20=EC=84=A4=EC=A0=95=20?= =?UTF-8?q?=EC=98=A4=EB=A5=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../reaction/infrastructure/messaging/RabbitMqConfig.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/flipnote/reaction/infrastructure/messaging/RabbitMqConfig.java b/src/main/java/flipnote/reaction/infrastructure/messaging/RabbitMqConfig.java index c1b8375..6c8cf81 100644 --- a/src/main/java/flipnote/reaction/infrastructure/messaging/RabbitMqConfig.java +++ b/src/main/java/flipnote/reaction/infrastructure/messaging/RabbitMqConfig.java @@ -1,7 +1,7 @@ package flipnote.reaction.infrastructure.messaging; import org.springframework.amqp.core.TopicExchange; -import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter; +import org.springframework.amqp.support.converter.JacksonJsonMessageConverter; import org.springframework.amqp.support.converter.MessageConverter; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -23,6 +23,6 @@ public TopicExchange reactionExchange() { @Bean public MessageConverter jackson2JsonMessageConverter() { - return new Jackson2JsonMessageConverter(); + return new JacksonJsonMessageConverter(); } }