From d8345fa3a52729acf1cd7312b09a96ece227578c Mon Sep 17 00:00:00 2001 From: jbh010204 Date: Wed, 3 Dec 2025 14:05:39 +0900 Subject: [PATCH 01/29] =?UTF-8?q?refactor:=20Feed=20=ED=8F=B4=EB=8D=94=20?= =?UTF-8?q?=EA=B5=AC=EC=A1=B0=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- gradlew | 0 .../infra/command/CommunityCommandAdaptor.java | 3 ++- .../example/bak/feed/application/FeedService.java | 8 ++++---- .../application/command/FeedCommandService.java | 13 +++++++++++++ .../{ => command}/port/CommunityCommandPort.java | 2 +- .../application/command/port/FeedCommandPort.java | 5 +++++ .../application/{ => query}/dto/FeedDetail.java | 2 +- .../application/{ => query}/dto/FeedResult.java | 2 +- .../application/{ => query}/dto/FeedSummary.java | 2 +- .../bak/feed/infra/command/FeedCommandAdapter.java | 11 +++++++++++ .../{persistence => command}/FeedJpaRepository.java | 2 +- .../bak/feed/infra/query/FeedQueryAdapter.java | 5 +++++ .../bak/feed/presentation/FeedController.java | 6 +++--- .../feedcomment/application/FeedCommentService.java | 2 +- .../application/{ => query}/dto/CommentInfo.java | 2 +- .../presentation/FeedCommentController.java | 2 +- .../community/domain/CommunityRepositoryStub.java | 3 ++- .../bak/feed/application/FeedServiceUnitTest.java | 6 +++--- 18 files changed, 56 insertions(+), 20 deletions(-) mode change 100644 => 100755 gradlew create mode 100644 src/main/java/com/example/bak/feed/application/command/FeedCommandService.java rename src/main/java/com/example/bak/feed/application/{ => command}/port/CommunityCommandPort.java (76%) create mode 100644 src/main/java/com/example/bak/feed/application/command/port/FeedCommandPort.java rename src/main/java/com/example/bak/feed/application/{ => query}/dto/FeedDetail.java (94%) rename src/main/java/com/example/bak/feed/application/{ => query}/dto/FeedResult.java (72%) rename src/main/java/com/example/bak/feed/application/{ => query}/dto/FeedSummary.java (95%) create mode 100644 src/main/java/com/example/bak/feed/infra/command/FeedCommandAdapter.java rename src/main/java/com/example/bak/feed/infra/{persistence => command}/FeedJpaRepository.java (92%) create mode 100644 src/main/java/com/example/bak/feed/infra/query/FeedQueryAdapter.java rename src/main/java/com/example/bak/feedcomment/application/{ => query}/dto/CommentInfo.java (90%) diff --git a/gradlew b/gradlew old mode 100644 new mode 100755 diff --git a/src/main/java/com/example/bak/community/infra/command/CommunityCommandAdaptor.java b/src/main/java/com/example/bak/community/infra/command/CommunityCommandAdaptor.java index f2ab41a..d419cd2 100644 --- a/src/main/java/com/example/bak/community/infra/command/CommunityCommandAdaptor.java +++ b/src/main/java/com/example/bak/community/infra/command/CommunityCommandAdaptor.java @@ -9,7 +9,8 @@ @Repository @RequiredArgsConstructor public class CommunityCommandAdaptor - implements CommunityCommandPort, com.example.bak.feed.application.port.CommunityCommandPort { + implements CommunityCommandPort, + com.example.bak.feed.application.command.port.CommunityCommandPort { private final CommunityJpaRepository communityJpaRepository; diff --git a/src/main/java/com/example/bak/feed/application/FeedService.java b/src/main/java/com/example/bak/feed/application/FeedService.java index 41771bb..08d756d 100644 --- a/src/main/java/com/example/bak/feed/application/FeedService.java +++ b/src/main/java/com/example/bak/feed/application/FeedService.java @@ -1,10 +1,10 @@ package com.example.bak.feed.application; import com.example.bak.community.domain.Community; -import com.example.bak.feed.application.dto.FeedDetail; -import com.example.bak.feed.application.dto.FeedResult; -import com.example.bak.feed.application.dto.FeedSummary; -import com.example.bak.feed.application.port.CommunityCommandPort; +import com.example.bak.feed.application.query.dto.FeedDetail; +import com.example.bak.feed.application.query.dto.FeedResult; +import com.example.bak.feed.application.query.dto.FeedSummary; +import com.example.bak.feed.application.command.port.CommunityCommandPort; import com.example.bak.feed.domain.Feed; import com.example.bak.feed.domain.FeedRepository; import com.example.bak.global.exception.BusinessException; diff --git a/src/main/java/com/example/bak/feed/application/command/FeedCommandService.java b/src/main/java/com/example/bak/feed/application/command/FeedCommandService.java new file mode 100644 index 0000000..dfa111b --- /dev/null +++ b/src/main/java/com/example/bak/feed/application/command/FeedCommandService.java @@ -0,0 +1,13 @@ +package com.example.bak.feed.application.command; + +import com.example.bak.feed.application.command.port.FeedCommandPort; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +@Transactional +public class FeedCommandService { + private final FeedCommandPort feedCommandPort; +} diff --git a/src/main/java/com/example/bak/feed/application/port/CommunityCommandPort.java b/src/main/java/com/example/bak/feed/application/command/port/CommunityCommandPort.java similarity index 76% rename from src/main/java/com/example/bak/feed/application/port/CommunityCommandPort.java rename to src/main/java/com/example/bak/feed/application/command/port/CommunityCommandPort.java index ca91c4b..da1c1bd 100644 --- a/src/main/java/com/example/bak/feed/application/port/CommunityCommandPort.java +++ b/src/main/java/com/example/bak/feed/application/command/port/CommunityCommandPort.java @@ -1,4 +1,4 @@ -package com.example.bak.feed.application.port; +package com.example.bak.feed.application.command.port; import com.example.bak.community.domain.Community; import java.util.Optional; diff --git a/src/main/java/com/example/bak/feed/application/command/port/FeedCommandPort.java b/src/main/java/com/example/bak/feed/application/command/port/FeedCommandPort.java new file mode 100644 index 0000000..75fef7b --- /dev/null +++ b/src/main/java/com/example/bak/feed/application/command/port/FeedCommandPort.java @@ -0,0 +1,5 @@ +package com.example.bak.feed.application.command.port; + +public interface FeedCommandPort { + +} diff --git a/src/main/java/com/example/bak/feed/application/dto/FeedDetail.java b/src/main/java/com/example/bak/feed/application/query/dto/FeedDetail.java similarity index 94% rename from src/main/java/com/example/bak/feed/application/dto/FeedDetail.java rename to src/main/java/com/example/bak/feed/application/query/dto/FeedDetail.java index ae71517..2250b74 100644 --- a/src/main/java/com/example/bak/feed/application/dto/FeedDetail.java +++ b/src/main/java/com/example/bak/feed/application/query/dto/FeedDetail.java @@ -1,4 +1,4 @@ -package com.example.bak.feed.application.dto; +package com.example.bak.feed.application.query.dto; import com.example.bak.community.application.query.dto.CommunityResult; import com.example.bak.feed.domain.Feed; diff --git a/src/main/java/com/example/bak/feed/application/dto/FeedResult.java b/src/main/java/com/example/bak/feed/application/query/dto/FeedResult.java similarity index 72% rename from src/main/java/com/example/bak/feed/application/dto/FeedResult.java rename to src/main/java/com/example/bak/feed/application/query/dto/FeedResult.java index c154625..5c39b37 100644 --- a/src/main/java/com/example/bak/feed/application/dto/FeedResult.java +++ b/src/main/java/com/example/bak/feed/application/query/dto/FeedResult.java @@ -1,4 +1,4 @@ -package com.example.bak.feed.application.dto; +package com.example.bak.feed.application.query.dto; public record FeedResult( Long id diff --git a/src/main/java/com/example/bak/feed/application/dto/FeedSummary.java b/src/main/java/com/example/bak/feed/application/query/dto/FeedSummary.java similarity index 95% rename from src/main/java/com/example/bak/feed/application/dto/FeedSummary.java rename to src/main/java/com/example/bak/feed/application/query/dto/FeedSummary.java index 5499913..77be6bd 100644 --- a/src/main/java/com/example/bak/feed/application/dto/FeedSummary.java +++ b/src/main/java/com/example/bak/feed/application/query/dto/FeedSummary.java @@ -1,4 +1,4 @@ -package com.example.bak.feed.application.dto; +package com.example.bak.feed.application.query.dto; import com.example.bak.community.application.query.dto.CommunityResult; import com.example.bak.feed.domain.Feed; diff --git a/src/main/java/com/example/bak/feed/infra/command/FeedCommandAdapter.java b/src/main/java/com/example/bak/feed/infra/command/FeedCommandAdapter.java new file mode 100644 index 0000000..84e314a --- /dev/null +++ b/src/main/java/com/example/bak/feed/infra/command/FeedCommandAdapter.java @@ -0,0 +1,11 @@ +package com.example.bak.feed.infra.command; + +import com.example.bak.feed.application.command.port.FeedCommandPort; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Repository; + +@Repository +@RequiredArgsConstructor +public class FeedCommandAdapter implements FeedCommandPort { + private final FeedJpaRepository feedJpaRepository; +} diff --git a/src/main/java/com/example/bak/feed/infra/persistence/FeedJpaRepository.java b/src/main/java/com/example/bak/feed/infra/command/FeedJpaRepository.java similarity index 92% rename from src/main/java/com/example/bak/feed/infra/persistence/FeedJpaRepository.java rename to src/main/java/com/example/bak/feed/infra/command/FeedJpaRepository.java index d18fd9d..22f4959 100644 --- a/src/main/java/com/example/bak/feed/infra/persistence/FeedJpaRepository.java +++ b/src/main/java/com/example/bak/feed/infra/command/FeedJpaRepository.java @@ -1,4 +1,4 @@ -package com.example.bak.feed.infra.persistence; +package com.example.bak.feed.infra.command; import com.example.bak.feed.domain.Feed; import com.example.bak.feed.domain.FeedRepository; diff --git a/src/main/java/com/example/bak/feed/infra/query/FeedQueryAdapter.java b/src/main/java/com/example/bak/feed/infra/query/FeedQueryAdapter.java new file mode 100644 index 0000000..08e7be1 --- /dev/null +++ b/src/main/java/com/example/bak/feed/infra/query/FeedQueryAdapter.java @@ -0,0 +1,5 @@ +package com.example.bak.feed.infra.query; + +public class FeedQueryAdapter { + +} diff --git a/src/main/java/com/example/bak/feed/presentation/FeedController.java b/src/main/java/com/example/bak/feed/presentation/FeedController.java index 70076bd..3bfe5a0 100644 --- a/src/main/java/com/example/bak/feed/presentation/FeedController.java +++ b/src/main/java/com/example/bak/feed/presentation/FeedController.java @@ -1,9 +1,9 @@ package com.example.bak.feed.presentation; import com.example.bak.feed.application.FeedService; -import com.example.bak.feed.application.dto.FeedDetail; -import com.example.bak.feed.application.dto.FeedResult; -import com.example.bak.feed.application.dto.FeedSummary; +import com.example.bak.feed.application.query.dto.FeedDetail; +import com.example.bak.feed.application.query.dto.FeedResult; +import com.example.bak.feed.application.query.dto.FeedSummary; import com.example.bak.feed.presentation.dto.FeedRequest; import com.example.bak.global.common.response.ApiResponse; import com.example.bak.global.common.response.ApiResponseFactory; diff --git a/src/main/java/com/example/bak/feedcomment/application/FeedCommentService.java b/src/main/java/com/example/bak/feedcomment/application/FeedCommentService.java index 37789fb..acab0b2 100644 --- a/src/main/java/com/example/bak/feedcomment/application/FeedCommentService.java +++ b/src/main/java/com/example/bak/feedcomment/application/FeedCommentService.java @@ -2,7 +2,7 @@ import com.example.bak.feed.domain.Feed; import com.example.bak.feed.domain.FeedRepository; -import com.example.bak.feedcomment.application.dto.CommentInfo; +import com.example.bak.feedcomment.application.query.dto.CommentInfo; import com.example.bak.feedcomment.domain.FeedComment; import com.example.bak.feedcomment.domain.FeedCommentRepository; import com.example.bak.global.exception.BusinessException; diff --git a/src/main/java/com/example/bak/feedcomment/application/dto/CommentInfo.java b/src/main/java/com/example/bak/feedcomment/application/query/dto/CommentInfo.java similarity index 90% rename from src/main/java/com/example/bak/feedcomment/application/dto/CommentInfo.java rename to src/main/java/com/example/bak/feedcomment/application/query/dto/CommentInfo.java index d4432ec..2cf8921 100644 --- a/src/main/java/com/example/bak/feedcomment/application/dto/CommentInfo.java +++ b/src/main/java/com/example/bak/feedcomment/application/query/dto/CommentInfo.java @@ -1,4 +1,4 @@ -package com.example.bak.feedcomment.application.dto; +package com.example.bak.feedcomment.application.query.dto; import com.example.bak.feedcomment.domain.FeedComment; diff --git a/src/main/java/com/example/bak/feedcomment/presentation/FeedCommentController.java b/src/main/java/com/example/bak/feedcomment/presentation/FeedCommentController.java index c5fe873..63e501b 100644 --- a/src/main/java/com/example/bak/feedcomment/presentation/FeedCommentController.java +++ b/src/main/java/com/example/bak/feedcomment/presentation/FeedCommentController.java @@ -1,7 +1,7 @@ package com.example.bak.feedcomment.presentation; import com.example.bak.feedcomment.application.FeedCommentService; -import com.example.bak.feedcomment.application.dto.CommentInfo; +import com.example.bak.feedcomment.application.query.dto.CommentInfo; import com.example.bak.feedcomment.presentation.dto.CommentRequest; import com.example.bak.global.common.response.ApiResponse; import com.example.bak.global.common.response.ApiResponseFactory; diff --git a/src/test/java/com/example/bak/community/domain/CommunityRepositoryStub.java b/src/test/java/com/example/bak/community/domain/CommunityRepositoryStub.java index de75bdb..b047e12 100644 --- a/src/test/java/com/example/bak/community/domain/CommunityRepositoryStub.java +++ b/src/test/java/com/example/bak/community/domain/CommunityRepositoryStub.java @@ -6,7 +6,8 @@ public class CommunityRepositoryStub extends AbstractStubRepository - implements CommunityCommandPort, com.example.bak.feed.application.port.CommunityCommandPort { + implements CommunityCommandPort, + com.example.bak.feed.application.command.port.CommunityCommandPort { @Override protected Long getId(Community community) { diff --git a/src/test/java/com/example/bak/feed/application/FeedServiceUnitTest.java b/src/test/java/com/example/bak/feed/application/FeedServiceUnitTest.java index 3921432..3748e19 100644 --- a/src/test/java/com/example/bak/feed/application/FeedServiceUnitTest.java +++ b/src/test/java/com/example/bak/feed/application/FeedServiceUnitTest.java @@ -6,9 +6,9 @@ import com.example.bak.community.domain.Community; import com.example.bak.community.domain.CommunityRepositoryStub; import com.example.bak.company.domain.Company; -import com.example.bak.feed.application.dto.FeedDetail; -import com.example.bak.feed.application.dto.FeedResult; -import com.example.bak.feed.application.dto.FeedSummary; +import com.example.bak.feed.application.query.dto.FeedDetail; +import com.example.bak.feed.application.query.dto.FeedResult; +import com.example.bak.feed.application.query.dto.FeedSummary; import com.example.bak.feed.domain.Feed; import com.example.bak.feed.domain.FeedRepositoryStub; import com.example.bak.global.exception.ErrorCode; From d75eae272ebedbecec1dbafce6da5036c428545f Mon Sep 17 00:00:00 2001 From: jbh010204 Date: Wed, 3 Dec 2025 19:30:38 +0900 Subject: [PATCH 02/29] =?UTF-8?q?feat:=20FeedService=EB=A5=BC=20command?= =?UTF-8?q?=EC=99=80=20query=EB=A1=9C=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../bak/feed/application/FeedService.java | 22 --- .../command/FeedCommandService.java | 24 +++ .../command/port/FeedCommandPort.java | 3 + .../application/query/FeedQueryService.java | 40 +++++ .../application/query/port/FeedQueryPort.java | 16 ++ .../infra/command/FeedCommandAdapter.java | 7 + .../feed/infra/command/FeedJpaRepository.java | 12 -- .../feed/infra/query/FeedQueryAdapter.java | 30 +++- .../infra/query/jdbc/FeedJdbcRepository.java | 16 ++ .../query/jdbc/FeedJdbcRepositoryImpl.java | 137 ++++++++++++++++++ .../jdbc/mapper/FeedDetailRowMapper.java | 33 +++++ .../jdbc/mapper/FeedSummaryRowMapper.java | 33 +++++ .../bak/feed/presentation/FeedController.java | 14 +- 13 files changed, 346 insertions(+), 41 deletions(-) create mode 100644 src/main/java/com/example/bak/feed/application/query/FeedQueryService.java create mode 100644 src/main/java/com/example/bak/feed/application/query/port/FeedQueryPort.java create mode 100644 src/main/java/com/example/bak/feed/infra/query/jdbc/FeedJdbcRepository.java create mode 100644 src/main/java/com/example/bak/feed/infra/query/jdbc/FeedJdbcRepositoryImpl.java create mode 100644 src/main/java/com/example/bak/feed/infra/query/jdbc/mapper/FeedDetailRowMapper.java create mode 100644 src/main/java/com/example/bak/feed/infra/query/jdbc/mapper/FeedSummaryRowMapper.java diff --git a/src/main/java/com/example/bak/feed/application/FeedService.java b/src/main/java/com/example/bak/feed/application/FeedService.java index 08d756d..637fc1b 100644 --- a/src/main/java/com/example/bak/feed/application/FeedService.java +++ b/src/main/java/com/example/bak/feed/application/FeedService.java @@ -43,26 +43,4 @@ public FeedResult createFeed(String title, String content, Long communityId, Lon return FeedResult.of(savedFeed.getId()); } - @Transactional(readOnly = true) - public FeedDetail getFeedDetail(Long feedId) { - Feed feed = feedRepository.findById(feedId) - .orElseThrow(() -> new BusinessException(ErrorCode.FEED_NOT_FOUND)); - - return FeedDetail.from(feed); - } - - @Transactional(readOnly = true) - public FeedSummary getFeedSummary(Long feedId) { - Feed feed = feedRepository.findById(feedId) - .orElseThrow(() -> new BusinessException(ErrorCode.FEED_NOT_FOUND)); - - return FeedSummary.from(feed); - } - - @Transactional(readOnly = true) - public List getFeeds(int page, int size) { - Pageable pageable = PageRequest.of(page, size); - Page feedPage = feedRepository.findAll(pageable); - return FeedSummary.listFrom(feedPage); - } } diff --git a/src/main/java/com/example/bak/feed/application/command/FeedCommandService.java b/src/main/java/com/example/bak/feed/application/command/FeedCommandService.java index dfa111b..c79ee1e 100644 --- a/src/main/java/com/example/bak/feed/application/command/FeedCommandService.java +++ b/src/main/java/com/example/bak/feed/application/command/FeedCommandService.java @@ -1,6 +1,14 @@ package com.example.bak.feed.application.command; +import com.example.bak.community.domain.Community; +import com.example.bak.feed.application.command.port.CommunityCommandPort; import com.example.bak.feed.application.command.port.FeedCommandPort; +import com.example.bak.feed.application.query.dto.FeedResult; +import com.example.bak.feed.domain.Feed; +import com.example.bak.global.exception.BusinessException; +import com.example.bak.global.exception.ErrorCode; +import com.example.bak.user.domain.User; +import com.example.bak.user.domain.UserRepository; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -9,5 +17,21 @@ @RequiredArgsConstructor @Transactional public class FeedCommandService { + private final FeedCommandPort feedCommandPort; + private final UserRepository userRepository; + private final CommunityCommandPort communityCommandPort; + + public FeedResult createFeed(String title, String content, Long communityId, Long userId) { + User user = userRepository.findById(userId) + .orElseThrow(() -> new BusinessException(ErrorCode.USER_NOT_FOUND)); + + Community community = communityCommandPort.findById(communityId) + .orElseThrow(() -> new BusinessException(ErrorCode.COMMUNITY_NOT_FOUND)); + + Feed newFeed = Feed.create(title, content, community, user); + Feed savedFeed = feedCommandPort.save(newFeed); + + return FeedResult.of(savedFeed.getId()); + } } diff --git a/src/main/java/com/example/bak/feed/application/command/port/FeedCommandPort.java b/src/main/java/com/example/bak/feed/application/command/port/FeedCommandPort.java index 75fef7b..898fae0 100644 --- a/src/main/java/com/example/bak/feed/application/command/port/FeedCommandPort.java +++ b/src/main/java/com/example/bak/feed/application/command/port/FeedCommandPort.java @@ -1,5 +1,8 @@ package com.example.bak.feed.application.command.port; +import com.example.bak.feed.domain.Feed; + public interface FeedCommandPort { + public Feed save(Feed feed); } diff --git a/src/main/java/com/example/bak/feed/application/query/FeedQueryService.java b/src/main/java/com/example/bak/feed/application/query/FeedQueryService.java new file mode 100644 index 0000000..9885037 --- /dev/null +++ b/src/main/java/com/example/bak/feed/application/query/FeedQueryService.java @@ -0,0 +1,40 @@ +package com.example.bak.feed.application.query; + +import com.example.bak.feed.application.query.dto.FeedDetail; +import com.example.bak.feed.application.query.dto.FeedSummary; +import com.example.bak.feed.application.query.port.FeedQueryPort; +import com.example.bak.global.exception.BusinessException; +import com.example.bak.global.exception.ErrorCode; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +public class FeedQueryService { + + private final FeedQueryPort feedQueryPort; + + @Transactional(readOnly = true) + public FeedDetail getFeedDetail(Long feedId) { + return feedQueryPort.findDetailById(feedId) + .orElseThrow(() -> new BusinessException(ErrorCode.FEED_NOT_FOUND)); + } + + @Transactional(readOnly = true) + public FeedSummary getFeedSummary(Long feedId) { + return feedQueryPort.findSummaryById(feedId) + .orElseThrow(() -> new BusinessException(ErrorCode.FEED_NOT_FOUND)); + } + + @Transactional(readOnly = true) + public List getFeeds(int page, int size) { + Pageable pageable = PageRequest.of(page, size); + Page feedPage = feedQueryPort.findAll(pageable); + return feedPage.getContent(); + } +} diff --git a/src/main/java/com/example/bak/feed/application/query/port/FeedQueryPort.java b/src/main/java/com/example/bak/feed/application/query/port/FeedQueryPort.java new file mode 100644 index 0000000..a119765 --- /dev/null +++ b/src/main/java/com/example/bak/feed/application/query/port/FeedQueryPort.java @@ -0,0 +1,16 @@ +package com.example.bak.feed.application.query.port; + +import com.example.bak.feed.application.query.dto.FeedDetail; +import com.example.bak.feed.application.query.dto.FeedSummary; +import java.util.Optional; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; + +public interface FeedQueryPort { + + Page findAll(Pageable pageable); + + Optional findSummaryById(Long feedId); + + Optional findDetailById(Long feedId); +} diff --git a/src/main/java/com/example/bak/feed/infra/command/FeedCommandAdapter.java b/src/main/java/com/example/bak/feed/infra/command/FeedCommandAdapter.java index 84e314a..eac312c 100644 --- a/src/main/java/com/example/bak/feed/infra/command/FeedCommandAdapter.java +++ b/src/main/java/com/example/bak/feed/infra/command/FeedCommandAdapter.java @@ -1,11 +1,18 @@ package com.example.bak.feed.infra.command; import com.example.bak.feed.application.command.port.FeedCommandPort; +import com.example.bak.feed.domain.Feed; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Repository; @Repository @RequiredArgsConstructor public class FeedCommandAdapter implements FeedCommandPort { + private final FeedJpaRepository feedJpaRepository; + + @Override + public Feed save(Feed feed) { + return feedJpaRepository.save(feed); + } } diff --git a/src/main/java/com/example/bak/feed/infra/command/FeedJpaRepository.java b/src/main/java/com/example/bak/feed/infra/command/FeedJpaRepository.java index 22f4959..2eb5aa1 100644 --- a/src/main/java/com/example/bak/feed/infra/command/FeedJpaRepository.java +++ b/src/main/java/com/example/bak/feed/infra/command/FeedJpaRepository.java @@ -2,23 +2,11 @@ import com.example.bak.feed.domain.Feed; import com.example.bak.feed.domain.FeedRepository; -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; public interface FeedJpaRepository extends JpaRepository, FeedRepository { - @Override - List findAll(); - - @Override - Page findAll(Pageable pageable); - @Override Feed save(Feed feed); - @Override - Optional findById(Long id); } diff --git a/src/main/java/com/example/bak/feed/infra/query/FeedQueryAdapter.java b/src/main/java/com/example/bak/feed/infra/query/FeedQueryAdapter.java index 08e7be1..f5f3af0 100644 --- a/src/main/java/com/example/bak/feed/infra/query/FeedQueryAdapter.java +++ b/src/main/java/com/example/bak/feed/infra/query/FeedQueryAdapter.java @@ -1,5 +1,33 @@ package com.example.bak.feed.infra.query; -public class FeedQueryAdapter { +import com.example.bak.feed.application.query.dto.FeedDetail; +import com.example.bak.feed.application.query.dto.FeedSummary; +import com.example.bak.feed.application.query.port.FeedQueryPort; +import com.example.bak.feed.infra.query.jdbc.FeedJdbcRepository; +import java.util.Optional; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Repository; +@Repository +@RequiredArgsConstructor +public class FeedQueryAdapter implements FeedQueryPort { + + private final FeedJdbcRepository feedJdbcRepository; + + @Override + public Page findAll(Pageable pageable) { + return feedJdbcRepository.findAll(pageable); + } + + @Override + public Optional findSummaryById(Long feedId) { + return feedJdbcRepository.findSummaryById(feedId); + } + + @Override + public Optional findDetailById(Long feedId) { + return feedJdbcRepository.findDetailById(feedId); + } } diff --git a/src/main/java/com/example/bak/feed/infra/query/jdbc/FeedJdbcRepository.java b/src/main/java/com/example/bak/feed/infra/query/jdbc/FeedJdbcRepository.java new file mode 100644 index 0000000..1357342 --- /dev/null +++ b/src/main/java/com/example/bak/feed/infra/query/jdbc/FeedJdbcRepository.java @@ -0,0 +1,16 @@ +package com.example.bak.feed.infra.query.jdbc; + +import com.example.bak.feed.application.query.dto.FeedDetail; +import com.example.bak.feed.application.query.dto.FeedSummary; +import java.util.Optional; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; + +public interface FeedJdbcRepository { + + Page findAll(Pageable pageable); + + Optional findSummaryById(Long feedId); + + Optional findDetailById(Long feedId); +} diff --git a/src/main/java/com/example/bak/feed/infra/query/jdbc/FeedJdbcRepositoryImpl.java b/src/main/java/com/example/bak/feed/infra/query/jdbc/FeedJdbcRepositoryImpl.java new file mode 100644 index 0000000..579d0bd --- /dev/null +++ b/src/main/java/com/example/bak/feed/infra/query/jdbc/FeedJdbcRepositoryImpl.java @@ -0,0 +1,137 @@ +package com.example.bak.feed.infra.query.jdbc; + +import com.example.bak.feed.application.query.dto.FeedDetail; +import com.example.bak.feed.application.query.dto.FeedSummary; +import com.example.bak.feed.infra.query.jdbc.mapper.FeedDetailRowMapper; +import com.example.bak.feed.infra.query.jdbc.mapper.FeedSummaryRowMapper; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.jdbc.core.namedparam.MapSqlParameterSource; +import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; +import org.springframework.stereotype.Repository; + +@Repository +@RequiredArgsConstructor +public class FeedJdbcRepositoryImpl implements FeedJdbcRepository { + + private static final FeedSummaryRowMapper FEED_SUMMARY_MAPPER = new FeedSummaryRowMapper(); + private static final FeedDetailRowMapper FEED_DETAIL_MAPPER = new FeedDetailRowMapper(); + private static final String SUMMARY_SELECT = """ + SELECT + f.id AS id, + f.title AS title, + u.id AS author_id, + p.nickname AS author_nickname, + c.id AS community_id, + c.name AS community_name, + c.job_group AS community_job_group, + COUNT(fc.id) AS comment_count + FROM feeds f + JOIN users u ON f.author_id = u.id + JOIN profiles p ON p.user_id = u.id + JOIN communities c ON f.community_id = c.id + LEFT JOIN feed_comments fc ON fc.feed_id = f.id + """; + + private static final String SUMMARY_GROUP_BY = """ + GROUP BY f.id, f.title, u.id, p.nickname, c.id, c.name, c.job_group + """; + + private static final String DETAIL_SELECT = """ + SELECT + f.id AS id, + f.title AS title, + f.content AS content, + u.id AS author_id, + p.nickname AS author_nickname, + c.id AS community_id, + c.name AS community_name, + c.job_group AS community_job_group + FROM feeds f + JOIN users u ON f.author_id = u.id + JOIN profiles p ON p.user_id = u.id + JOIN communities c ON f.community_id = c.id + WHERE f.id = :feedId + LIMIT 1 + """; + + private static final String DEFAULT_ORDER_BY = "ORDER BY f.id DESC"; + + private final NamedParameterJdbcTemplate jdbc; + + @Override + public Page findAll(Pageable pageable) { + MapSqlParameterSource params = new MapSqlParameterSource() + .addValue("limit", pageable.getPageSize()) + .addValue("offset", pageable.getOffset()); + + String sql = SUMMARY_SELECT + '\n' + SUMMARY_GROUP_BY + '\n' + + buildOrderByClause(pageable) + '\n' + + "LIMIT :limit OFFSET :offset"; + + List content = jdbc.query(sql, params, FEED_SUMMARY_MAPPER); + long total = countFeeds(); + + return new PageImpl<>(content, pageable, total); + } + + @Override + public Optional findSummaryById(Long feedId) { + MapSqlParameterSource params = new MapSqlParameterSource() + .addValue("feedId", feedId); + + String sql = SUMMARY_SELECT + '\n' + + "WHERE f.id = :feedId\n" + + SUMMARY_GROUP_BY; + + return jdbc.query(sql, params, FEED_SUMMARY_MAPPER).stream().findFirst(); + } + + @Override + public Optional findDetailById(Long feedId) { + MapSqlParameterSource params = new MapSqlParameterSource() + .addValue("feedId", feedId); + + return jdbc.query(DETAIL_SELECT, params, FEED_DETAIL_MAPPER).stream().findFirst(); + } + + private long countFeeds() { + Long total = jdbc.queryForObject("SELECT COUNT(*) FROM feeds", new MapSqlParameterSource(), + Long.class); + return total == null ? 0L : total; + } + + private String buildOrderByClause(Pageable pageable) { + if (pageable.getSort().isUnsorted()) { + return DEFAULT_ORDER_BY; + } + + List orderParts = new ArrayList<>(); + for (Sort.Order order : pageable.getSort()) { + String column = mapPropertyToColumn(order.getProperty()); + if (column != null) { + orderParts.add(column + " " + order.getDirection().name()); + } + } + + if (orderParts.isEmpty()) { + return DEFAULT_ORDER_BY; + } + + return "ORDER BY " + String.join(", ", orderParts); + } + + private String mapPropertyToColumn(String property) { + return switch (property) { + case "id" -> "f.id"; + case "title" -> "f.title"; + default -> null; + }; + } +} diff --git a/src/main/java/com/example/bak/feed/infra/query/jdbc/mapper/FeedDetailRowMapper.java b/src/main/java/com/example/bak/feed/infra/query/jdbc/mapper/FeedDetailRowMapper.java new file mode 100644 index 0000000..56877c5 --- /dev/null +++ b/src/main/java/com/example/bak/feed/infra/query/jdbc/mapper/FeedDetailRowMapper.java @@ -0,0 +1,33 @@ +package com.example.bak.feed.infra.query.jdbc.mapper; + +import com.example.bak.community.application.query.dto.CommunityResult; +import com.example.bak.feed.application.query.dto.FeedDetail; +import com.example.bak.user.application.dto.UserInfo; +import java.sql.ResultSet; +import java.sql.SQLException; +import org.springframework.jdbc.core.RowMapper; + +public class FeedDetailRowMapper implements RowMapper { + + @Override + public FeedDetail mapRow(ResultSet rs, int rowNum) throws SQLException { + UserInfo author = new UserInfo( + rs.getLong("author_id"), + rs.getString("author_nickname") + ); + + CommunityResult.Detail community = new CommunityResult.Detail( + rs.getLong("community_id"), + rs.getString("community_name"), + rs.getString("community_job_group") + ); + + return new FeedDetail( + rs.getLong("id"), + rs.getString("title"), + rs.getString("content"), + author, + community + ); + } +} diff --git a/src/main/java/com/example/bak/feed/infra/query/jdbc/mapper/FeedSummaryRowMapper.java b/src/main/java/com/example/bak/feed/infra/query/jdbc/mapper/FeedSummaryRowMapper.java new file mode 100644 index 0000000..5f34f79 --- /dev/null +++ b/src/main/java/com/example/bak/feed/infra/query/jdbc/mapper/FeedSummaryRowMapper.java @@ -0,0 +1,33 @@ +package com.example.bak.feed.infra.query.jdbc.mapper; + +import com.example.bak.community.application.query.dto.CommunityResult; +import com.example.bak.feed.application.query.dto.FeedSummary; +import com.example.bak.user.application.dto.UserInfo; +import java.sql.ResultSet; +import java.sql.SQLException; +import org.springframework.jdbc.core.RowMapper; + +public class FeedSummaryRowMapper implements RowMapper { + + @Override + public FeedSummary mapRow(ResultSet rs, int rowNum) throws SQLException { + UserInfo author = new UserInfo( + rs.getLong("author_id"), + rs.getString("author_nickname") + ); + + CommunityResult.Detail community = new CommunityResult.Detail( + rs.getLong("community_id"), + rs.getString("community_name"), + rs.getString("community_job_group") + ); + + return new FeedSummary( + rs.getLong("id"), + rs.getString("title"), + author, + community, + rs.getInt("comment_count") + ); + } +} diff --git a/src/main/java/com/example/bak/feed/presentation/FeedController.java b/src/main/java/com/example/bak/feed/presentation/FeedController.java index 3bfe5a0..b502f96 100644 --- a/src/main/java/com/example/bak/feed/presentation/FeedController.java +++ b/src/main/java/com/example/bak/feed/presentation/FeedController.java @@ -1,6 +1,7 @@ package com.example.bak.feed.presentation; -import com.example.bak.feed.application.FeedService; +import com.example.bak.feed.application.command.FeedCommandService; +import com.example.bak.feed.application.query.FeedQueryService; import com.example.bak.feed.application.query.dto.FeedDetail; import com.example.bak.feed.application.query.dto.FeedResult; import com.example.bak.feed.application.query.dto.FeedSummary; @@ -24,11 +25,12 @@ @RequiredArgsConstructor public class FeedController { - private final FeedService feedService; + private final FeedCommandService feedCommandService; + private final FeedQueryService feedQueryService; @PostMapping() public ResponseEntity createFeed(@RequestBody FeedRequest request) { - FeedResult feedResult = feedService.createFeed( + FeedResult feedResult = feedCommandService.createFeed( request.title(), request.content(), request.communityId(), @@ -44,21 +46,21 @@ public ResponseEntity getFeeds( @RequestParam(defaultValue = "0") int page, @RequestParam(defaultValue = "10") int size ) { - List feeds = feedService.getFeeds(page, size); + List feeds = feedQueryService.getFeeds(page, size); ApiResponse response = ApiResponseFactory.success("피드 목록을 성공적으로 조회하였습니다.", feeds); return ResponseEntity.ok(response); } @GetMapping("/{feedId}/summary") public ResponseEntity getFeedSummary(@PathVariable Long feedId) { - FeedSummary summary = feedService.getFeedSummary(feedId); + FeedSummary summary = feedQueryService.getFeedSummary(feedId); ApiResponse response = ApiResponseFactory.success("피드 요약을 성공적으로 조회하였습니다.", summary); return ResponseEntity.ok(response); } @GetMapping("/{feedId}") public ResponseEntity getFeedDetail(@PathVariable Long feedId) { - FeedDetail detail = feedService.getFeedDetail(feedId); + FeedDetail detail = feedQueryService.getFeedDetail(feedId); ApiResponse response = ApiResponseFactory.success("피드 상세를 성공적으로 조회하였습니다.", detail); return ResponseEntity.ok(response); } From 291ae9a5a8a3de1afd6ee03288fccbfe1daddad1 Mon Sep 17 00:00:00 2001 From: jbh010204 Date: Wed, 3 Dec 2025 20:27:14 +0900 Subject: [PATCH 03/29] =?UTF-8?q?refactor:=20feed=20=EB=8F=84=EB=A9=94?= =?UTF-8?q?=EC=9D=B8=20=EB=82=B4=20=EC=96=B4=EA=B7=B8=EB=A0=88=EC=9D=B4?= =?UTF-8?q?=ED=8A=B8=20=EC=95=BD=ED=95=9C=20=EC=B0=B8=EC=A1=B0=20=EA=B4=80?= =?UTF-8?q?=EA=B3=84=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../command/CommunityCommandAdaptor.java | 4 +- .../command/CommunityValidationAdapter.java | 17 +++++++ .../bak/feed/application/FeedService.java | 46 ------------------- .../command/FeedCommandService.java | 17 +++---- .../command/port/CommunityCommandPort.java | 9 ---- .../command/port/CommunityValidationPort.java | 6 +++ .../com/example/bak/feed/domain/Feed.java | 35 +++++++------- .../bak/feed/presentation/FeedController.java | 6 ++- .../feed/presentation/dto/FeedRequest.java | 3 +- 9 files changed, 51 insertions(+), 92 deletions(-) create mode 100644 src/main/java/com/example/bak/community/infra/command/CommunityValidationAdapter.java delete mode 100644 src/main/java/com/example/bak/feed/application/FeedService.java delete mode 100644 src/main/java/com/example/bak/feed/application/command/port/CommunityCommandPort.java create mode 100644 src/main/java/com/example/bak/feed/application/command/port/CommunityValidationPort.java diff --git a/src/main/java/com/example/bak/community/infra/command/CommunityCommandAdaptor.java b/src/main/java/com/example/bak/community/infra/command/CommunityCommandAdaptor.java index d419cd2..c07a906 100644 --- a/src/main/java/com/example/bak/community/infra/command/CommunityCommandAdaptor.java +++ b/src/main/java/com/example/bak/community/infra/command/CommunityCommandAdaptor.java @@ -8,9 +8,7 @@ @Repository @RequiredArgsConstructor -public class CommunityCommandAdaptor - implements CommunityCommandPort, - com.example.bak.feed.application.command.port.CommunityCommandPort { +public class CommunityCommandAdaptor implements CommunityCommandPort { private final CommunityJpaRepository communityJpaRepository; diff --git a/src/main/java/com/example/bak/community/infra/command/CommunityValidationAdapter.java b/src/main/java/com/example/bak/community/infra/command/CommunityValidationAdapter.java new file mode 100644 index 0000000..0f18961 --- /dev/null +++ b/src/main/java/com/example/bak/community/infra/command/CommunityValidationAdapter.java @@ -0,0 +1,17 @@ +package com.example.bak.community.infra.command; + +import com.example.bak.feed.application.command.port.CommunityValidationPort; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Repository; + +@Repository +@RequiredArgsConstructor +public class CommunityValidationAdapter implements CommunityValidationPort { + + private final CommunityJpaRepository communityJpaRepository; + + @Override + public boolean isCommunityExists(Long communityId) { + return communityJpaRepository.existsById(communityId); + } +} diff --git a/src/main/java/com/example/bak/feed/application/FeedService.java b/src/main/java/com/example/bak/feed/application/FeedService.java deleted file mode 100644 index 637fc1b..0000000 --- a/src/main/java/com/example/bak/feed/application/FeedService.java +++ /dev/null @@ -1,46 +0,0 @@ -package com.example.bak.feed.application; - -import com.example.bak.community.domain.Community; -import com.example.bak.feed.application.query.dto.FeedDetail; -import com.example.bak.feed.application.query.dto.FeedResult; -import com.example.bak.feed.application.query.dto.FeedSummary; -import com.example.bak.feed.application.command.port.CommunityCommandPort; -import com.example.bak.feed.domain.Feed; -import com.example.bak.feed.domain.FeedRepository; -import com.example.bak.global.exception.BusinessException; -import com.example.bak.global.exception.ErrorCode; -import com.example.bak.user.domain.User; -import com.example.bak.user.domain.UserRepository; -import java.util.List; -import lombok.RequiredArgsConstructor; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.PageRequest; -import org.springframework.data.domain.Pageable; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Propagation; -import org.springframework.transaction.annotation.Transactional; - -@Service -@RequiredArgsConstructor -@Transactional(readOnly = true, propagation = Propagation.SUPPORTS) -public class FeedService { - - private final FeedRepository feedRepository; - private final UserRepository userRepository; - private final CommunityCommandPort communityCommandPort; - - @Transactional - public FeedResult createFeed(String title, String content, Long communityId, Long userId) { - User user = userRepository.findById(userId) - .orElseThrow(() -> new BusinessException(ErrorCode.USER_NOT_FOUND)); - - Community community = communityCommandPort.findById(communityId) - .orElseThrow(() -> new BusinessException(ErrorCode.COMMUNITY_NOT_FOUND)); - - Feed newFeed = Feed.create(title, content, community, user); - Feed savedFeed = feedRepository.save(newFeed); - - return FeedResult.of(savedFeed.getId()); - } - -} diff --git a/src/main/java/com/example/bak/feed/application/command/FeedCommandService.java b/src/main/java/com/example/bak/feed/application/command/FeedCommandService.java index c79ee1e..1fcb661 100644 --- a/src/main/java/com/example/bak/feed/application/command/FeedCommandService.java +++ b/src/main/java/com/example/bak/feed/application/command/FeedCommandService.java @@ -1,14 +1,11 @@ package com.example.bak.feed.application.command; -import com.example.bak.community.domain.Community; -import com.example.bak.feed.application.command.port.CommunityCommandPort; +import com.example.bak.feed.application.command.port.CommunityValidationPort; import com.example.bak.feed.application.command.port.FeedCommandPort; import com.example.bak.feed.application.query.dto.FeedResult; import com.example.bak.feed.domain.Feed; import com.example.bak.global.exception.BusinessException; import com.example.bak.global.exception.ErrorCode; -import com.example.bak.user.domain.User; -import com.example.bak.user.domain.UserRepository; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -19,17 +16,15 @@ public class FeedCommandService { private final FeedCommandPort feedCommandPort; - private final UserRepository userRepository; - private final CommunityCommandPort communityCommandPort; + private final CommunityValidationPort communityValidationPort; public FeedResult createFeed(String title, String content, Long communityId, Long userId) { - User user = userRepository.findById(userId) - .orElseThrow(() -> new BusinessException(ErrorCode.USER_NOT_FOUND)); - Community community = communityCommandPort.findById(communityId) - .orElseThrow(() -> new BusinessException(ErrorCode.COMMUNITY_NOT_FOUND)); + if (!communityValidationPort.isCommunityExists(communityId)) { + throw new BusinessException(ErrorCode.COMMUNITY_NOT_FOUND); + } - Feed newFeed = Feed.create(title, content, community, user); + Feed newFeed = Feed.create(title, content, communityId, userId); Feed savedFeed = feedCommandPort.save(newFeed); return FeedResult.of(savedFeed.getId()); diff --git a/src/main/java/com/example/bak/feed/application/command/port/CommunityCommandPort.java b/src/main/java/com/example/bak/feed/application/command/port/CommunityCommandPort.java deleted file mode 100644 index da1c1bd..0000000 --- a/src/main/java/com/example/bak/feed/application/command/port/CommunityCommandPort.java +++ /dev/null @@ -1,9 +0,0 @@ -package com.example.bak.feed.application.command.port; - -import com.example.bak.community.domain.Community; -import java.util.Optional; - -public interface CommunityCommandPort { - - Optional findById(Long communityId); -} diff --git a/src/main/java/com/example/bak/feed/application/command/port/CommunityValidationPort.java b/src/main/java/com/example/bak/feed/application/command/port/CommunityValidationPort.java new file mode 100644 index 0000000..a6062ac --- /dev/null +++ b/src/main/java/com/example/bak/feed/application/command/port/CommunityValidationPort.java @@ -0,0 +1,6 @@ +package com.example.bak.feed.application.command.port; + +public interface CommunityValidationPort { + + boolean isCommunityExists(Long communityId); +} diff --git a/src/main/java/com/example/bak/feed/domain/Feed.java b/src/main/java/com/example/bak/feed/domain/Feed.java index a202cd5..669318d 100644 --- a/src/main/java/com/example/bak/feed/domain/Feed.java +++ b/src/main/java/com/example/bak/feed/domain/Feed.java @@ -1,8 +1,6 @@ package com.example.bak.feed.domain; -import com.example.bak.community.domain.Community; import com.example.bak.feedcomment.domain.FeedComment; -import com.example.bak.user.domain.User; import jakarta.persistence.CascadeType; import jakarta.persistence.Column; import jakarta.persistence.Entity; @@ -10,7 +8,6 @@ import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; -import jakarta.persistence.ManyToOne; import jakarta.persistence.OneToMany; import java.util.ArrayList; import java.util.List; @@ -38,44 +35,44 @@ public class Feed { @OneToMany(mappedBy = "feed", fetch = FetchType.LAZY, cascade = CascadeType.ALL, orphanRemoval = true) private List comments = new ArrayList<>(); - @ManyToOne(fetch = FetchType.LAZY) - private Community community; + @Column(nullable = false) + private Long communityId; - @ManyToOne(fetch = FetchType.LAZY) - private User author; + @Column(nullable = false) + private Long authorId; - private Feed(Long id, String title, String content, Community community, User author) { + private Feed(Long id, String title, String content, Long communityId, Long authorId) { this.id = id; this.title = title; this.content = content; - this.community = community; - this.author = author; + this.communityId = communityId; + this.authorId = authorId; } - private Feed(String title, String content, Community community, User author) { + private Feed(String title, String content, Long communityId, Long authorId) { this.title = title; this.content = content; - this.community = community; - this.author = author; + this.communityId = communityId; + this.authorId = authorId; } public static Feed create( String title, String content, - Community community, - User author + Long communityId, + Long authorId ) { - return new Feed(title, content, community, author); + return new Feed(title, content, communityId, authorId); } public static Feed testInstance( Long id, String title, String content, - Community community, - User author + Long communityId, + Long userId ) { - return new Feed(id, title, content, community, author); + return new Feed(id, title, content, communityId, userId); } public void addComment(FeedComment comment) { diff --git a/src/main/java/com/example/bak/feed/presentation/FeedController.java b/src/main/java/com/example/bak/feed/presentation/FeedController.java index b502f96..8c3d662 100644 --- a/src/main/java/com/example/bak/feed/presentation/FeedController.java +++ b/src/main/java/com/example/bak/feed/presentation/FeedController.java @@ -9,6 +9,7 @@ import com.example.bak.global.common.response.ApiResponse; import com.example.bak.global.common.response.ApiResponseFactory; import com.example.bak.global.common.utils.UriUtils; +import com.example.bak.global.security.annotation.AuthUser; import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; @@ -29,12 +30,13 @@ public class FeedController { private final FeedQueryService feedQueryService; @PostMapping() - public ResponseEntity createFeed(@RequestBody FeedRequest request) { + public ResponseEntity createFeed(@AuthUser Long userId, + @RequestBody FeedRequest request) { FeedResult feedResult = feedCommandService.createFeed( request.title(), request.content(), request.communityId(), - request.userId() + userId ); ApiResponse response = ApiResponseFactory.successVoid("피드를 성공적으로 생성하였습니다."); return ResponseEntity.created(UriUtils.current(feedResult.id())) diff --git a/src/main/java/com/example/bak/feed/presentation/dto/FeedRequest.java b/src/main/java/com/example/bak/feed/presentation/dto/FeedRequest.java index 1794b74..300feee 100644 --- a/src/main/java/com/example/bak/feed/presentation/dto/FeedRequest.java +++ b/src/main/java/com/example/bak/feed/presentation/dto/FeedRequest.java @@ -3,8 +3,7 @@ public record FeedRequest( String title, String content, - Long communityId, - Long userId + Long communityId ) { } \ No newline at end of file From b30480af78d5a3686c284496607b982c0158f8a5 Mon Sep 17 00:00:00 2001 From: jbh010204 Date: Wed, 3 Dec 2025 20:35:47 +0900 Subject: [PATCH 04/29] =?UTF-8?q?refactor:=20Feed=EA=B4=80=EB=A0=A8=20DTO?= =?UTF-8?q?=EC=97=90=EC=84=9C=20=EC=82=AC=EC=9A=A9=ED=95=98=EC=A7=80=20?= =?UTF-8?q?=EC=95=8A=EB=8A=94=20=EB=A9=94=EC=84=9C=EB=93=9C=20=EC=82=AD?= =?UTF-8?q?=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/query/dto/FeedDetail.java | 16 +----------- .../application/query/dto/FeedSummary.java | 25 +------------------ 2 files changed, 2 insertions(+), 39 deletions(-) diff --git a/src/main/java/com/example/bak/feed/application/query/dto/FeedDetail.java b/src/main/java/com/example/bak/feed/application/query/dto/FeedDetail.java index 2250b74..da7ad1e 100644 --- a/src/main/java/com/example/bak/feed/application/query/dto/FeedDetail.java +++ b/src/main/java/com/example/bak/feed/application/query/dto/FeedDetail.java @@ -1,7 +1,6 @@ package com.example.bak.feed.application.query.dto; import com.example.bak.community.application.query.dto.CommunityResult; -import com.example.bak.feed.domain.Feed; import com.example.bak.user.application.dto.UserInfo; /** @@ -14,17 +13,4 @@ public record FeedDetail( UserInfo author, CommunityResult.Detail community ) { - - public static FeedDetail from(Feed feed) { - final UserInfo author = UserInfo.from(feed.getAuthor()); - final CommunityResult.Detail community = CommunityResult.Detail.from(feed.getCommunity()); - - return new FeedDetail( - feed.getId(), - feed.getTitle(), - feed.getContent(), - author, - community - ); - } -} \ No newline at end of file +} diff --git a/src/main/java/com/example/bak/feed/application/query/dto/FeedSummary.java b/src/main/java/com/example/bak/feed/application/query/dto/FeedSummary.java index 77be6bd..ee3d3c0 100644 --- a/src/main/java/com/example/bak/feed/application/query/dto/FeedSummary.java +++ b/src/main/java/com/example/bak/feed/application/query/dto/FeedSummary.java @@ -1,10 +1,7 @@ package com.example.bak.feed.application.query.dto; import com.example.bak.community.application.query.dto.CommunityResult; -import com.example.bak.feed.domain.Feed; import com.example.bak.user.application.dto.UserInfo; -import java.util.List; -import org.springframework.data.domain.Page; /** * Feed 도메인의 간단한 정보를 담는 DTO 목록 조회 시 사용 @@ -16,24 +13,4 @@ public record FeedSummary( CommunityResult.Detail community, int commentCount ) { - - public static FeedSummary from(Feed feed) { - final UserInfo author = UserInfo.from(feed.getAuthor()); - final CommunityResult.Detail community = CommunityResult.Detail.from(feed.getCommunity()); - final int commentCount = feed.getComments().size(); - - return new FeedSummary( - feed.getId(), - feed.getTitle(), - author, - community, - commentCount - ); - } - - public static List listFrom(Page page) { - return page.getContent().stream() - .map(FeedSummary::from) - .toList(); - } -} \ No newline at end of file +} From 7553103d27700accd729641417c511c2271b74c4 Mon Sep 17 00:00:00 2001 From: jbh010204 Date: Wed, 3 Dec 2025 20:50:22 +0900 Subject: [PATCH 05/29] =?UTF-8?q?refactor:=20Feed=EC=88=98=EC=A0=95?= =?UTF-8?q?=EC=97=90=20=EB=A7=9E=EC=B6=B0=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/CommunityRepositoryStub.java | 4 +- .../feed/application/FeedServiceUnitTest.java | 294 +++++++++++------- .../FeedCommentServiceUnitTest.java | 8 +- 3 files changed, 195 insertions(+), 111 deletions(-) diff --git a/src/test/java/com/example/bak/community/domain/CommunityRepositoryStub.java b/src/test/java/com/example/bak/community/domain/CommunityRepositoryStub.java index b047e12..0450d79 100644 --- a/src/test/java/com/example/bak/community/domain/CommunityRepositoryStub.java +++ b/src/test/java/com/example/bak/community/domain/CommunityRepositoryStub.java @@ -5,9 +5,7 @@ import java.util.Objects; public class CommunityRepositoryStub - extends AbstractStubRepository - implements CommunityCommandPort, - com.example.bak.feed.application.command.port.CommunityCommandPort { + extends AbstractStubRepository implements CommunityCommandPort { @Override protected Long getId(Community community) { diff --git a/src/test/java/com/example/bak/feed/application/FeedServiceUnitTest.java b/src/test/java/com/example/bak/feed/application/FeedServiceUnitTest.java index 3748e19..680e143 100644 --- a/src/test/java/com/example/bak/feed/application/FeedServiceUnitTest.java +++ b/src/test/java/com/example/bak/feed/application/FeedServiceUnitTest.java @@ -3,190 +3,270 @@ import static com.example.bak.global.utils.AssertionsErrorCode.assertBusiness; import static org.assertj.core.api.Assertions.assertThat; -import com.example.bak.community.domain.Community; -import com.example.bak.community.domain.CommunityRepositoryStub; -import com.example.bak.company.domain.Company; +import com.example.bak.community.application.query.dto.CommunityResult; +import com.example.bak.feed.application.command.FeedCommandService; +import com.example.bak.feed.application.command.port.CommunityValidationPort; +import com.example.bak.feed.application.command.port.FeedCommandPort; +import com.example.bak.feed.application.query.FeedQueryService; import com.example.bak.feed.application.query.dto.FeedDetail; import com.example.bak.feed.application.query.dto.FeedResult; import com.example.bak.feed.application.query.dto.FeedSummary; +import com.example.bak.feed.application.query.port.FeedQueryPort; import com.example.bak.feed.domain.Feed; -import com.example.bak.feed.domain.FeedRepositoryStub; import com.example.bak.global.exception.ErrorCode; -import com.example.bak.user.domain.User; -import com.example.bak.user.domain.UserRepositoryStub; +import com.example.bak.user.application.dto.UserInfo; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashMap; import java.util.List; -import java.util.stream.IntStream; +import java.util.Map; +import java.util.Optional; +import java.util.Set; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.Pageable; @DisplayName("FeedService 단위 테스트") class FeedServiceUnitTest { private static final Long EXISTING_USER_ID = 1L; - private static final Long EXISTING_COMPANY_ID = 5L; private static final Long EXISTING_COMMUNITY_ID = 10L; - private static final Long NOT_FOUND_USER_ID = 999L; - private static final Long NOT_FOUND_COMMUNITY_ID = 888L; - private static final Long NOT_FOUND_FEED_ID = 777L; + private static final Long NOT_FOUND_COMMUNITY_ID = 999L; private static final String TITLE = "title"; private static final String CONTENT = "content"; - - private final User testUser = - User.testInstance(EXISTING_USER_ID, "user@test.com", "pw", "name", "nick"); - - private final Company company = - Company.testInstance(EXISTING_COMPANY_ID, "testDotCom", "test.com", "image.url.com", - "testing company1"); - - private final Community community = - Community.testInstance(EXISTING_COMMUNITY_ID, "community", "jobGroup", EXISTING_COMPANY_ID); - - private FeedService feedService; - private FeedRepositoryStub feedRepository; - private UserRepositoryStub userRepository; - private CommunityRepositoryStub communityRepository; - - @BeforeEach - void setUp() { - feedRepository = new FeedRepositoryStub(); - userRepository = new UserRepositoryStub(); - communityRepository = new CommunityRepositoryStub(); - - feedService = new FeedService(feedRepository, userRepository, communityRepository); - - userRepository.save(testUser); - communityRepository.save(community); - } - - private List createFeeds(int count) { - return IntStream.rangeClosed(1, count) - .mapToObj(i -> Feed.testInstance( - (long) i, - TITLE + i, - CONTENT + i, - community, - testUser - )) - .toList(); - } + private static final String NICKNAME = "nick"; + private static final String COMMUNITY_NAME = "community"; + private static final String JOB_GROUP = "jobGroup"; @Nested - @DisplayName("createFeed 테스트") - class CreateFeedTest { + @DisplayName("FeedCommandService") + class FeedCommandServiceTest { + + private FeedCommandService feedCommandService; + private InMemoryFeedCommandPort feedCommandPort; + private StubCommunityValidationPort communityValidationPort; + + @BeforeEach + void setUp() { + feedCommandPort = new InMemoryFeedCommandPort(); + communityValidationPort = new StubCommunityValidationPort(); + communityValidationPort.registerCommunity(EXISTING_COMMUNITY_ID); + feedCommandService = new FeedCommandService(feedCommandPort, communityValidationPort); + } @Test @DisplayName("피드 생성 성공") void createFeed_success() { - // when - FeedResult result = feedService.createFeed( - TITLE, CONTENT, + FeedResult result = feedCommandService.createFeed( + TITLE, + CONTENT, EXISTING_COMMUNITY_ID, EXISTING_USER_ID ); - // then assertThat(result).isNotNull(); - var saved = feedRepository.findAll().getFirst(); + Feed saved = feedCommandPort.findById(result.id()); + assertThat(saved).isNotNull(); assertThat(saved.getTitle()).isEqualTo(TITLE); assertThat(saved.getContent()).isEqualTo(CONTENT); - } - - @Test - @DisplayName("존재하지 않는 사용자면 예외 발생") - void createFeed_when_userNotFound() { - assertBusiness( - () -> feedService.createFeed(TITLE, CONTENT, EXISTING_COMMUNITY_ID, - NOT_FOUND_USER_ID), - ErrorCode.USER_NOT_FOUND - ); + assertThat(saved.getCommunityId()).isEqualTo(EXISTING_COMMUNITY_ID); + assertThat(saved.getAuthorId()).isEqualTo(EXISTING_USER_ID); } @Test @DisplayName("존재하지 않는 커뮤니티면 예외 발생") void createFeed_when_communityNotFound() { + communityValidationPort.removeCommunity(EXISTING_COMMUNITY_ID); + assertBusiness( - () -> feedService.createFeed(TITLE, CONTENT, NOT_FOUND_COMMUNITY_ID, - EXISTING_USER_ID), + () -> feedCommandService.createFeed( + TITLE, + CONTENT, + NOT_FOUND_COMMUNITY_ID, + EXISTING_USER_ID + ), ErrorCode.COMMUNITY_NOT_FOUND ); } } @Nested - @DisplayName("getFeedDetail 테스트") - class GetFeedDetailTest { + @DisplayName("FeedQueryService") + class FeedQueryServiceTest { + + private FeedQueryService feedQueryService; + private StubFeedQueryPort feedQueryPort; + + @BeforeEach + void setUp() { + feedQueryPort = new StubFeedQueryPort(); + feedQueryService = new FeedQueryService(feedQueryPort); + } @Test @DisplayName("피드 상세 조회 성공") void getFeedDetail_success() { - // given - Feed saved = feedRepository.save( - Feed.create(TITLE, CONTENT, community, testUser) - ); + feedQueryPort.save(summaryOf(1L, 0), detailOf(1L)); - // when - FeedDetail detail = feedService.getFeedDetail(saved.getId()); + FeedDetail detail = feedQueryService.getFeedDetail(1L); - // then - assertThat(detail.id()).isEqualTo(saved.getId()); - assertThat(detail.title()).isEqualTo(TITLE); + assertThat(detail.id()).isEqualTo(1L); + assertThat(detail.title()).isEqualTo(TITLE + 1); } @Test - @DisplayName("없는 피드 조회 시 예외 발생") + @DisplayName("피드 상세 조회 시 예외") void getFeedDetail_when_notFound() { assertBusiness( - () -> feedService.getFeedDetail(NOT_FOUND_FEED_ID), + () -> feedQueryService.getFeedDetail(1L), ErrorCode.FEED_NOT_FOUND ); } - } - - @Nested - @DisplayName("getFeedSummary 테스트") - class GetFeedSummaryTest { @Test @DisplayName("피드 요약 조회 성공") void getFeedSummary_success() { - Feed saved = feedRepository.save( - Feed.create(TITLE, CONTENT, community, testUser) - ); + feedQueryPort.save(summaryOf(2L, 3), detailOf(2L)); - FeedSummary summary = feedService.getFeedSummary(saved.getId()); + FeedSummary summary = feedQueryService.getFeedSummary(2L); - assertThat(summary.id()).isEqualTo(saved.getId()); - assertThat(summary.title()).isEqualTo(TITLE); + assertThat(summary.id()).isEqualTo(2L); + assertThat(summary.commentCount()).isEqualTo(3); } @Test - @DisplayName("없는 피드 요약 조회 시 예외 발생") + @DisplayName("피드 요약 조회 시 예외") void getFeedSummary_when_notFound() { assertBusiness( - () -> feedService.getFeedSummary(NOT_FOUND_FEED_ID), + () -> feedQueryService.getFeedSummary(2L), ErrorCode.FEED_NOT_FOUND ); } - } - - @Nested - @DisplayName("getFeeds 테스트") - class GetFeedsTest { @Test - @DisplayName("페이징 조회 성공") + @DisplayName("피드 목록 페이징 조회") void getFeeds_success() { - // given - createFeeds(5).forEach(feedRepository::save); + for (long i = 1; i <= 5; i++) { + feedQueryPort.save(summaryOf(i, (int) i), detailOf(i)); + } + + List feeds = feedQueryService.getFeeds(0, 3); + + assertThat(feeds).hasSize(3); + assertThat(feeds.get(0).id()).isEqualTo(1L); + } + } + + private FeedSummary summaryOf(Long id, int commentCount) { + return new FeedSummary( + id, + TITLE + id, + authorInfo(), + communityDetail(), + commentCount + ); + } + + private FeedDetail detailOf(Long id) { + return new FeedDetail( + id, + TITLE + id, + CONTENT + id, + authorInfo(), + communityDetail() + ); + } + + private UserInfo authorInfo() { + return new UserInfo(EXISTING_USER_ID, NICKNAME); + } + + private CommunityResult.Detail communityDetail() { + return new CommunityResult.Detail(EXISTING_COMMUNITY_ID, COMMUNITY_NAME, JOB_GROUP); + } + + private static class InMemoryFeedCommandPort implements FeedCommandPort { + + private final Map store = new HashMap<>(); + private long sequence = 1L; + + @Override + public Feed save(Feed feed) { + Long id = feed.getId(); + if (id == null) { + id = sequence++; + } + + Feed persisted = Feed.testInstance( + id, + feed.getTitle(), + feed.getContent(), + feed.getCommunityId(), + feed.getAuthorId() + ); + store.put(id, persisted); + return persisted; + } + + Feed findById(Long id) { + return store.get(id); + } + } + + private static class StubCommunityValidationPort implements CommunityValidationPort { - // when - List results = feedService.getFeeds(0, 3); + private final Set existingCommunityIds = new HashSet<>(); + + void registerCommunity(Long communityId) { + existingCommunityIds.add(communityId); + } + + void removeCommunity(Long communityId) { + existingCommunityIds.remove(communityId); + } + + @Override + public boolean isCommunityExists(Long communityId) { + return existingCommunityIds.contains(communityId); + } + } + + private static class StubFeedQueryPort implements FeedQueryPort { + + private final Map summaries = new LinkedHashMap<>(); + private final Map details = new LinkedHashMap<>(); + + void save(FeedSummary summary, FeedDetail detail) { + summaries.put(summary.id(), summary); + details.put(detail.id(), detail); + } + + @Override + public Page findAll(Pageable pageable) { + List list = new ArrayList<>(summaries.values()); + int start = (int) pageable.getOffset(); + if (start >= list.size()) { + return new PageImpl<>(List.of(), pageable, list.size()); + } + + int end = Math.min(start + pageable.getPageSize(), list.size()); + return new PageImpl<>(list.subList(start, end), pageable, list.size()); + } + + @Override + public Optional findSummaryById(Long feedId) { + return Optional.ofNullable(summaries.get(feedId)); + } - // then - assertThat(results).hasSize(3); + @Override + public Optional findDetailById(Long feedId) { + return Optional.ofNullable(details.get(feedId)); } } } diff --git a/src/test/java/com/example/bak/feedcomment/application/FeedCommentServiceUnitTest.java b/src/test/java/com/example/bak/feedcomment/application/FeedCommentServiceUnitTest.java index 0d812ae..0ae6d35 100644 --- a/src/test/java/com/example/bak/feedcomment/application/FeedCommentServiceUnitTest.java +++ b/src/test/java/com/example/bak/feedcomment/application/FeedCommentServiceUnitTest.java @@ -42,7 +42,13 @@ public class FeedCommentServiceUnitTest { Community.testInstance(1L, "name", "jobGroup", 1L); private final Feed testFeed = - Feed.testInstance(EXISTING_FEED_ID, "title", COMMENT_CONTENT, community, testUser); + Feed.testInstance( + EXISTING_FEED_ID, + "title", + COMMENT_CONTENT, + community.getId(), + testUser.getId() + ); private FeedCommentService feedCommentService; private FeedCommentRepositoryStub feedCommentRepository; From eb862fa660254006f13c3f7d9bce4a18133088b6 Mon Sep 17 00:00:00 2001 From: jbh010204 Date: Wed, 3 Dec 2025 21:23:03 +0900 Subject: [PATCH 06/29] =?UTF-8?q?feat:=20FeedComment=20=EC=95=BD=EA=B2=B0?= =?UTF-8?q?=ED=95=A9=20=EB=B6=84=EB=A6=AC=20=EB=B0=8F=20CQRS=20=ED=8C=A8?= =?UTF-8?q?=ED=84=B4=EC=9C=BC=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../FeedCommentCommandService.java} | 29 ++--- .../query/FeedCommentQueryService.java | 23 ++++ .../application/query/dto/CommentInfo.java | 6 +- .../bak/feedcomment/domain/FeedComment.java | 32 +++-- .../presentation/FeedCommentController.java | 18 +-- .../presentation/dto/CommentRequest.java | 5 +- .../FeedCommentServiceUnitTest.java | 111 +++++++++--------- 7 files changed, 125 insertions(+), 99 deletions(-) rename src/main/java/com/example/bak/feedcomment/application/{FeedCommentService.java => command/FeedCommentCommandService.java} (69%) create mode 100644 src/main/java/com/example/bak/feedcomment/application/query/FeedCommentQueryService.java diff --git a/src/main/java/com/example/bak/feedcomment/application/FeedCommentService.java b/src/main/java/com/example/bak/feedcomment/application/command/FeedCommentCommandService.java similarity index 69% rename from src/main/java/com/example/bak/feedcomment/application/FeedCommentService.java rename to src/main/java/com/example/bak/feedcomment/application/command/FeedCommentCommandService.java index acab0b2..f4f5864 100644 --- a/src/main/java/com/example/bak/feedcomment/application/FeedCommentService.java +++ b/src/main/java/com/example/bak/feedcomment/application/command/FeedCommentCommandService.java @@ -1,29 +1,26 @@ -package com.example.bak.feedcomment.application; +package com.example.bak.feedcomment.application.command; import com.example.bak.feed.domain.Feed; import com.example.bak.feed.domain.FeedRepository; -import com.example.bak.feedcomment.application.query.dto.CommentInfo; import com.example.bak.feedcomment.domain.FeedComment; import com.example.bak.feedcomment.domain.FeedCommentRepository; import com.example.bak.global.exception.BusinessException; import com.example.bak.global.exception.ErrorCode; import com.example.bak.user.domain.User; import com.example.bak.user.domain.UserRepository; -import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @Service @RequiredArgsConstructor -@Transactional(readOnly = true) -public class FeedCommentService { +@Transactional +public class FeedCommentCommandService { private final FeedCommentRepository commentRepository; private final FeedRepository feedRepository; private final UserRepository userRepository; - @Transactional public void createComment(Long feedId, String content, Long userId) { Feed feed = feedRepository.findById(feedId) .orElseThrow(() -> new BusinessException(ErrorCode.FEED_NOT_FOUND)); @@ -31,31 +28,25 @@ public void createComment(Long feedId, String content, Long userId) { User user = userRepository.findById(userId) .orElseThrow(() -> new BusinessException(ErrorCode.USER_NOT_FOUND)); - FeedComment newComment = FeedComment.create(content, user); + FeedComment newComment = FeedComment.create( + content, + user.getId(), + user.getProfile().getNickname() + ); feed.addComment(newComment); commentRepository.save(newComment); } - @Transactional public void updateComment(Long commentId, String content, Long userId) { FeedComment comment = commentRepository.findById(commentId) .orElseThrow(() -> new BusinessException(ErrorCode.COMMENT_NOT_FOUND)); - if (!isAuthor(comment, userId)) { + if (!comment.isWrittenBy(userId)) { throw new BusinessException(ErrorCode.UNAUTHORIZED_ACTION); } comment.updateComment(content); } - - public List getComments(Long feedId) { - return commentRepository.findByFeedId(feedId).stream() - .map(CommentInfo::from) - .toList(); - } - - private boolean isAuthor(FeedComment comment, Long userId) { - return comment.getAuthor().getId().equals(userId); - } } + diff --git a/src/main/java/com/example/bak/feedcomment/application/query/FeedCommentQueryService.java b/src/main/java/com/example/bak/feedcomment/application/query/FeedCommentQueryService.java new file mode 100644 index 0000000..8c527d4 --- /dev/null +++ b/src/main/java/com/example/bak/feedcomment/application/query/FeedCommentQueryService.java @@ -0,0 +1,23 @@ +package com.example.bak.feedcomment.application.query; + +import com.example.bak.feedcomment.application.query.dto.CommentInfo; +import com.example.bak.feedcomment.domain.FeedCommentRepository; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +@Transactional(readOnly = true) +public class FeedCommentQueryService { + + private final FeedCommentRepository commentRepository; + + public List getComments(Long feedId) { + return commentRepository.findByFeedId(feedId).stream() + .map(CommentInfo::from) + .toList(); + } +} + diff --git a/src/main/java/com/example/bak/feedcomment/application/query/dto/CommentInfo.java b/src/main/java/com/example/bak/feedcomment/application/query/dto/CommentInfo.java index 2cf8921..9ca0f6e 100644 --- a/src/main/java/com/example/bak/feedcomment/application/query/dto/CommentInfo.java +++ b/src/main/java/com/example/bak/feedcomment/application/query/dto/CommentInfo.java @@ -15,9 +15,9 @@ public record CommentInfo( public static CommentInfo from(FeedComment comment) { return new CommentInfo( comment.getId(), - comment.getAuthor().getId(), - comment.getAuthor().getProfile().getNickname(), + comment.getAuthorId(), + comment.getAuthorNickname(), comment.getComment() ); } -} \ No newline at end of file +} diff --git a/src/main/java/com/example/bak/feedcomment/domain/FeedComment.java b/src/main/java/com/example/bak/feedcomment/domain/FeedComment.java index 7ba377b..acde6f8 100644 --- a/src/main/java/com/example/bak/feedcomment/domain/FeedComment.java +++ b/src/main/java/com/example/bak/feedcomment/domain/FeedComment.java @@ -1,7 +1,6 @@ package com.example.bak.feedcomment.domain; import com.example.bak.feed.domain.Feed; -import com.example.bak.user.domain.User; import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.FetchType; @@ -9,6 +8,7 @@ import jakarta.persistence.GenerationType; import jakarta.persistence.Id; import jakarta.persistence.ManyToOne; +import java.util.Objects; import lombok.AccessLevel; import lombok.AllArgsConstructor; import lombok.Getter; @@ -24,8 +24,11 @@ public class FeedComment { @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; - @ManyToOne(fetch = FetchType.LAZY) - private User author; + @Column(nullable = false) + private Long authorId; + + @Column(nullable = false) + private String authorNickname; @Column(nullable = false) private String comment; @@ -33,24 +36,27 @@ public class FeedComment { @ManyToOne(fetch = FetchType.LAZY) private Feed feed; - private FeedComment(Long id, String comment, User author, Feed feed) { + private FeedComment(Long id, String comment, Long authorId, String authorNickname, Feed feed) { this.id = id; this.comment = comment; - this.author = author; + this.authorId = authorId; + this.authorNickname = authorNickname; this.feed = feed; } - private FeedComment(String comment, User author) { + private FeedComment(String comment, Long authorId, String authorNickname) { this.comment = comment; - this.author = author; + this.authorId = authorId; + this.authorNickname = authorNickname; } - public static FeedComment create(String comment, User author) { - return new FeedComment(comment, author); + public static FeedComment create(String comment, Long authorId, String authorNickname) { + return new FeedComment(comment, authorId, authorNickname); } - public static FeedComment testInstance(Long id, String comment, User author, Feed feed) { - return new FeedComment(id, comment, author, feed); + public static FeedComment testInstance(Long id, String comment, Long authorId, + String authorNickname, Feed feed) { + return new FeedComment(id, comment, authorId, authorNickname, feed); } public void joinFeed(Feed feed) { @@ -60,4 +66,8 @@ public void joinFeed(Feed feed) { public void updateComment(String comment) { this.comment = comment; } + + public boolean isWrittenBy(Long authorId) { + return Objects.equals(this.authorId, authorId); + } } diff --git a/src/main/java/com/example/bak/feedcomment/presentation/FeedCommentController.java b/src/main/java/com/example/bak/feedcomment/presentation/FeedCommentController.java index 63e501b..4a0c473 100644 --- a/src/main/java/com/example/bak/feedcomment/presentation/FeedCommentController.java +++ b/src/main/java/com/example/bak/feedcomment/presentation/FeedCommentController.java @@ -1,11 +1,13 @@ package com.example.bak.feedcomment.presentation; -import com.example.bak.feedcomment.application.FeedCommentService; +import com.example.bak.feedcomment.application.command.FeedCommentCommandService; +import com.example.bak.feedcomment.application.query.FeedCommentQueryService; import com.example.bak.feedcomment.application.query.dto.CommentInfo; import com.example.bak.feedcomment.presentation.dto.CommentRequest; import com.example.bak.global.common.response.ApiResponse; import com.example.bak.global.common.response.ApiResponseFactory; import com.example.bak.global.common.utils.UriUtils; +import com.example.bak.global.security.annotation.AuthUser; import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; @@ -22,17 +24,19 @@ @RequiredArgsConstructor public class FeedCommentController { - private final FeedCommentService feedCommentService; + private final FeedCommentCommandService feedCommentCommandService; + private final FeedCommentQueryService feedCommentQueryService; @PostMapping("/feeds/{feedId}/comments") public ResponseEntity createComment( @PathVariable Long feedId, - @RequestBody CommentRequest request + @RequestBody CommentRequest request, + @AuthUser Long userId ) { - feedCommentService.createComment( + feedCommentCommandService.createComment( feedId, request.content(), - request.userId() + userId ); ApiResponse response = ApiResponseFactory.successVoid("댓글을 성공적으로 생성하였습니다."); return ResponseEntity.created(UriUtils.current()) @@ -41,7 +45,7 @@ public ResponseEntity createComment( @GetMapping("/feeds/{feedId}/comments") public ResponseEntity getComment(@PathVariable Long feedId) { - List comments = feedCommentService.getComments(feedId); + List comments = feedCommentQueryService.getComments(feedId); ApiResponse response = ApiResponseFactory.success("댓글을 성공적으로 조회하였습니다.", comments); return ResponseEntity.ok(response); } @@ -51,7 +55,7 @@ public ResponseEntity updateComment( @PathVariable Long commentId, @RequestBody CommentRequest request ) { - feedCommentService.updateComment(commentId, request.content(), request.userId()); + feedCommentCommandService.updateComment(commentId, request.content(), request.userId()); ApiResponse response = ApiResponseFactory.successVoid("댓글을 성공적으로 수정하였습니다."); return ResponseEntity.ok(response); } diff --git a/src/main/java/com/example/bak/feedcomment/presentation/dto/CommentRequest.java b/src/main/java/com/example/bak/feedcomment/presentation/dto/CommentRequest.java index b570baf..a838ae1 100644 --- a/src/main/java/com/example/bak/feedcomment/presentation/dto/CommentRequest.java +++ b/src/main/java/com/example/bak/feedcomment/presentation/dto/CommentRequest.java @@ -1,8 +1,5 @@ package com.example.bak.feedcomment.presentation.dto; -public record CommentRequest( - String content, - Long userId -) { +public record CommentRequest(String content) { } diff --git a/src/test/java/com/example/bak/feedcomment/application/FeedCommentServiceUnitTest.java b/src/test/java/com/example/bak/feedcomment/application/FeedCommentServiceUnitTest.java index 0ae6d35..2976ad4 100644 --- a/src/test/java/com/example/bak/feedcomment/application/FeedCommentServiceUnitTest.java +++ b/src/test/java/com/example/bak/feedcomment/application/FeedCommentServiceUnitTest.java @@ -7,8 +7,10 @@ import com.example.bak.company.domain.Company; import com.example.bak.feed.domain.Feed; import com.example.bak.feed.domain.FeedRepositoryStub; -import com.example.bak.feedcomment.domain.FeedCommentRepositoryStub; +import com.example.bak.feedcomment.application.command.FeedCommentCommandService; +import com.example.bak.feedcomment.application.query.FeedCommentQueryService; import com.example.bak.feedcomment.domain.FeedComment; +import com.example.bak.feedcomment.domain.FeedCommentRepositoryStub; import com.example.bak.global.exception.ErrorCode; import com.example.bak.user.domain.User; import com.example.bak.user.domain.UserRepositoryStub; @@ -20,11 +22,10 @@ import org.junit.jupiter.api.Test; @DisplayName("FeedCommentService 단위 테스트") -public class FeedCommentServiceUnitTest { +class FeedCommentServiceUnitTest { private static final Long EXISTING_USER_ID = 1L; private static final Long EXISTING_FEED_ID = 1L; - private static final Long NOT_FOUND_USER_ID = 999L; private static final Long NOT_FOUND_FEED_ID = 999L; @@ -33,14 +34,11 @@ public class FeedCommentServiceUnitTest { private final User testUser = User.testInstance(EXISTING_USER_ID, "test@test.com", "password", "name", "nickname"); - private final Company company = Company.testInstance(1L, "testDotCom", "test.com", "image.url.com", "testing company1"); - private final Community community = Community.testInstance(1L, "name", "jobGroup", 1L); - private final Feed testFeed = Feed.testInstance( EXISTING_FEED_ID, @@ -50,50 +48,53 @@ public class FeedCommentServiceUnitTest { testUser.getId() ); - private FeedCommentService feedCommentService; - private FeedCommentRepositoryStub feedCommentRepository; - - @BeforeEach - void setUp() { - FeedRepositoryStub feedRepository = new FeedRepositoryStub(); - UserRepositoryStub userRepository = new UserRepositoryStub(); - - feedCommentRepository = new FeedCommentRepositoryStub(); - - feedCommentService = new FeedCommentService( - feedCommentRepository, - feedRepository, - userRepository - ); - - userRepository.save(testUser); - feedRepository.save(testFeed); + private String nickname() { + return testUser.getProfile().getNickname(); } private List createComments(int count) { return IntStream.rangeClosed(1, count) .mapToObj(i -> FeedComment.testInstance( (long) i, - COMMENT_CONTENT, - testUser, + COMMENT_CONTENT + i, + testUser.getId(), + nickname(), testFeed )) .toList(); } @Nested - @DisplayName("createComment 테스트") - class CreateCommentTest { + @DisplayName("FeedCommentCommandService") + class FeedCommentCommandServiceTest { + + private FeedCommentCommandService feedCommentCommandService; + private FeedCommentRepositoryStub feedCommentRepository; + + @BeforeEach + void setUp() { + FeedRepositoryStub feedRepository = new FeedRepositoryStub(); + UserRepositoryStub userRepository = new UserRepositoryStub(); + feedCommentRepository = new FeedCommentRepositoryStub(); + + feedCommentCommandService = new FeedCommentCommandService( + feedCommentRepository, + feedRepository, + userRepository + ); + + userRepository.save(testUser); + feedRepository.save(testFeed); + } @Test @DisplayName("피드 댓글 생성에 성공한다") void createComment_success() { - // when - feedCommentService.createComment(EXISTING_FEED_ID, COMMENT_CONTENT, EXISTING_USER_ID); + feedCommentCommandService.createComment(EXISTING_FEED_ID, COMMENT_CONTENT, EXISTING_USER_ID); - // then var saved = feedCommentRepository.findAll().getFirst(); - assertThat(saved.getAuthor().getId()).isEqualTo(EXISTING_USER_ID); + assertThat(saved.getAuthorId()).isEqualTo(EXISTING_USER_ID); + assertThat(saved.getAuthorNickname()).isEqualTo(nickname()); assertThat(saved.getFeed().getId()).isEqualTo(EXISTING_FEED_ID); assertThat(saved.getComment()).isEqualTo(COMMENT_CONTENT); } @@ -102,7 +103,7 @@ void createComment_success() { @DisplayName("존재하지 않는 피드에 예외를 던진다") void createComment_when_feedNotFound() { assertBusiness( - () -> feedCommentService.createComment( + () -> feedCommentCommandService.createComment( NOT_FOUND_FEED_ID, COMMENT_CONTENT, EXISTING_USER_ID @@ -115,7 +116,7 @@ void createComment_when_feedNotFound() { @DisplayName("존재하지 않는 사용자에 예외를 던진다") void createComment_when_userNotFound() { assertBusiness( - () -> feedCommentService.createComment( + () -> feedCommentCommandService.createComment( EXISTING_FEED_ID, COMMENT_CONTENT, NOT_FOUND_USER_ID @@ -123,24 +124,16 @@ void createComment_when_userNotFound() { ErrorCode.USER_NOT_FOUND ); } - } - - @Nested - @DisplayName("updateComment 테스트") - class UpdateCommentTest { @Test @DisplayName("피드 댓글 업데이트에 성공한다") void updateComment_success() { - // given feedCommentRepository.save( - FeedComment.testInstance(1L, COMMENT_CONTENT, testUser, testFeed) + FeedComment.testInstance(1L, COMMENT_CONTENT, EXISTING_USER_ID, nickname(), testFeed) ); - // when - feedCommentService.updateComment(1L, UPDATED_CONTENT, EXISTING_USER_ID); + feedCommentCommandService.updateComment(1L, UPDATED_CONTENT, EXISTING_USER_ID); - // then var updated = feedCommentRepository.findById(1L); assertThat(updated).isPresent(); assertThat(updated.get().getComment()).isEqualTo(UPDATED_CONTENT); @@ -150,39 +143,47 @@ void updateComment_success() { @DisplayName("피드 댓글 업데이트 권한이 없을 때 예외를 던진다") void updateComment_when_isNotAuthor() { feedCommentRepository.save( - FeedComment.testInstance(1L, COMMENT_CONTENT, testUser, testFeed) + FeedComment.testInstance(1L, COMMENT_CONTENT, EXISTING_USER_ID, nickname(), testFeed) ); assertBusiness( - () -> feedCommentService.updateComment(1L, UPDATED_CONTENT, NOT_FOUND_USER_ID), + () -> feedCommentCommandService.updateComment(1L, UPDATED_CONTENT, NOT_FOUND_USER_ID), ErrorCode.UNAUTHORIZED_ACTION ); } @Test - @DisplayName("존해하지 피드 댓글에 예외를 던진다.") - void updateComment_when_feedNotFound() { + @DisplayName("존재하지 않는 댓글 업데이트 시 예외") + void updateComment_when_commentNotFound() { assertBusiness( - () -> feedCommentService.updateComment(1L, UPDATED_CONTENT, EXISTING_USER_ID), + () -> feedCommentCommandService.updateComment(1L, UPDATED_CONTENT, EXISTING_USER_ID), ErrorCode.COMMENT_NOT_FOUND ); } } @Nested - @DisplayName("getComments 테스트") - class GetCommentsTest { + @DisplayName("FeedCommentQueryService") + class FeedCommentQueryServiceTest { + + private FeedCommentQueryService feedCommentQueryService; + private FeedCommentRepositoryStub feedCommentRepository; + + @BeforeEach + void setUp() { + feedCommentRepository = new FeedCommentRepositoryStub(); + feedCommentQueryService = new FeedCommentQueryService(feedCommentRepository); + } @Test + @DisplayName("댓글 목록 조회 성공") void getComments_success() { - // given createComments(4).forEach(feedCommentRepository::save); - // when - var comments = feedCommentService.getComments(EXISTING_FEED_ID); + var comments = feedCommentQueryService.getComments(EXISTING_FEED_ID); - // then assertThat(comments).hasSize(4); + assertThat(comments.getFirst().authorId()).isEqualTo(EXISTING_USER_ID); } } } From deec9d041f370a4d7dc68e16283f4dd7d3d37161 Mon Sep 17 00:00:00 2001 From: jbh010204 Date: Wed, 3 Dec 2025 22:01:50 +0900 Subject: [PATCH 07/29] =?UTF-8?q?feat:=20FeedCommand=20=ED=8F=AC=ED=8A=B8?= =?UTF-8?q?=20=ED=99=95=EC=9E=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../command/port/FeedCommandPort.java | 5 +- .../infra/command/FeedCommandAdapter.java | 6 + .../feed/infra/command/FeedJpaRepository.java | 4 + .../feed/application/FeedServiceUnitTest.java | 216 +++++++++--------- .../bak/feed/domain/FeedRepositoryStub.java | 3 +- 5 files changed, 124 insertions(+), 110 deletions(-) diff --git a/src/main/java/com/example/bak/feed/application/command/port/FeedCommandPort.java b/src/main/java/com/example/bak/feed/application/command/port/FeedCommandPort.java index 898fae0..a1b2cec 100644 --- a/src/main/java/com/example/bak/feed/application/command/port/FeedCommandPort.java +++ b/src/main/java/com/example/bak/feed/application/command/port/FeedCommandPort.java @@ -1,8 +1,11 @@ package com.example.bak.feed.application.command.port; import com.example.bak.feed.domain.Feed; +import java.util.Optional; public interface FeedCommandPort { - public Feed save(Feed feed); + Feed save(Feed feed); + + Optional findById(Long id); } diff --git a/src/main/java/com/example/bak/feed/infra/command/FeedCommandAdapter.java b/src/main/java/com/example/bak/feed/infra/command/FeedCommandAdapter.java index eac312c..95c5ff0 100644 --- a/src/main/java/com/example/bak/feed/infra/command/FeedCommandAdapter.java +++ b/src/main/java/com/example/bak/feed/infra/command/FeedCommandAdapter.java @@ -2,6 +2,7 @@ import com.example.bak.feed.application.command.port.FeedCommandPort; import com.example.bak.feed.domain.Feed; +import java.util.Optional; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Repository; @@ -15,4 +16,9 @@ public class FeedCommandAdapter implements FeedCommandPort { public Feed save(Feed feed) { return feedJpaRepository.save(feed); } + + @Override + public Optional findById(Long id) { + return feedJpaRepository.findById(id); + } } diff --git a/src/main/java/com/example/bak/feed/infra/command/FeedJpaRepository.java b/src/main/java/com/example/bak/feed/infra/command/FeedJpaRepository.java index 2eb5aa1..2825d70 100644 --- a/src/main/java/com/example/bak/feed/infra/command/FeedJpaRepository.java +++ b/src/main/java/com/example/bak/feed/infra/command/FeedJpaRepository.java @@ -2,6 +2,7 @@ import com.example.bak.feed.domain.Feed; import com.example.bak.feed.domain.FeedRepository; +import java.util.Optional; import org.springframework.data.jpa.repository.JpaRepository; public interface FeedJpaRepository extends JpaRepository, FeedRepository { @@ -9,4 +10,7 @@ public interface FeedJpaRepository extends JpaRepository, FeedReposi @Override Feed save(Feed feed); + @Override + Optional findById(Long id); + } diff --git a/src/test/java/com/example/bak/feed/application/FeedServiceUnitTest.java b/src/test/java/com/example/bak/feed/application/FeedServiceUnitTest.java index 680e143..514d6cd 100644 --- a/src/test/java/com/example/bak/feed/application/FeedServiceUnitTest.java +++ b/src/test/java/com/example/bak/feed/application/FeedServiceUnitTest.java @@ -44,6 +44,113 @@ class FeedServiceUnitTest { private static final String COMMUNITY_NAME = "community"; private static final String JOB_GROUP = "jobGroup"; + private FeedSummary summaryOf(Long id, int commentCount) { + return new FeedSummary( + id, + TITLE + id, + authorInfo(), + communityDetail(), + commentCount + ); + } + + private FeedDetail detailOf(Long id) { + return new FeedDetail( + id, + TITLE + id, + CONTENT + id, + authorInfo(), + communityDetail() + ); + } + + private UserInfo authorInfo() { + return new UserInfo(EXISTING_USER_ID, NICKNAME); + } + + private CommunityResult.Detail communityDetail() { + return new CommunityResult.Detail(EXISTING_COMMUNITY_ID, COMMUNITY_NAME, JOB_GROUP); + } + + private static class InMemoryFeedCommandPort implements FeedCommandPort { + + private final Map store = new HashMap<>(); + private long sequence = 1L; + + @Override + public Feed save(Feed feed) { + Long id = feed.getId(); + if (id == null) { + id = sequence++; + } + + Feed persisted = Feed.testInstance( + id, + feed.getTitle(), + feed.getContent(), + feed.getCommunityId(), + feed.getAuthorId() + ); + store.put(id, persisted); + return persisted; + } + + public Optional findById(Long id) { + return Optional.ofNullable(store.get(id)); + } + } + + private static class StubCommunityValidationPort implements CommunityValidationPort { + + private final Set existingCommunityIds = new HashSet<>(); + + void registerCommunity(Long communityId) { + existingCommunityIds.add(communityId); + } + + void removeCommunity(Long communityId) { + existingCommunityIds.remove(communityId); + } + + @Override + public boolean isCommunityExists(Long communityId) { + return existingCommunityIds.contains(communityId); + } + } + + private static class StubFeedQueryPort implements FeedQueryPort { + + private final Map summaries = new LinkedHashMap<>(); + private final Map details = new LinkedHashMap<>(); + + void save(FeedSummary summary, FeedDetail detail) { + summaries.put(summary.id(), summary); + details.put(detail.id(), detail); + } + + @Override + public Page findAll(Pageable pageable) { + List list = new ArrayList<>(summaries.values()); + int start = (int) pageable.getOffset(); + if (start >= list.size()) { + return new PageImpl<>(List.of(), pageable, list.size()); + } + + int end = Math.min(start + pageable.getPageSize(), list.size()); + return new PageImpl<>(list.subList(start, end), pageable, list.size()); + } + + @Override + public Optional findSummaryById(Long feedId) { + return Optional.ofNullable(summaries.get(feedId)); + } + + @Override + public Optional findDetailById(Long feedId) { + return Optional.ofNullable(details.get(feedId)); + } + } + @Nested @DisplayName("FeedCommandService") class FeedCommandServiceTest { @@ -71,7 +178,7 @@ void createFeed_success() { ); assertThat(result).isNotNull(); - Feed saved = feedCommandPort.findById(result.id()); + Feed saved = feedCommandPort.findById(result.id()).orElse(null); assertThat(saved).isNotNull(); assertThat(saved.getTitle()).isEqualTo(TITLE); assertThat(saved.getContent()).isEqualTo(CONTENT); @@ -162,112 +269,5 @@ void getFeeds_success() { assertThat(feeds.get(0).id()).isEqualTo(1L); } } - - private FeedSummary summaryOf(Long id, int commentCount) { - return new FeedSummary( - id, - TITLE + id, - authorInfo(), - communityDetail(), - commentCount - ); - } - - private FeedDetail detailOf(Long id) { - return new FeedDetail( - id, - TITLE + id, - CONTENT + id, - authorInfo(), - communityDetail() - ); - } - - private UserInfo authorInfo() { - return new UserInfo(EXISTING_USER_ID, NICKNAME); - } - - private CommunityResult.Detail communityDetail() { - return new CommunityResult.Detail(EXISTING_COMMUNITY_ID, COMMUNITY_NAME, JOB_GROUP); - } - - private static class InMemoryFeedCommandPort implements FeedCommandPort { - - private final Map store = new HashMap<>(); - private long sequence = 1L; - - @Override - public Feed save(Feed feed) { - Long id = feed.getId(); - if (id == null) { - id = sequence++; - } - - Feed persisted = Feed.testInstance( - id, - feed.getTitle(), - feed.getContent(), - feed.getCommunityId(), - feed.getAuthorId() - ); - store.put(id, persisted); - return persisted; - } - - Feed findById(Long id) { - return store.get(id); - } - } - - private static class StubCommunityValidationPort implements CommunityValidationPort { - - private final Set existingCommunityIds = new HashSet<>(); - - void registerCommunity(Long communityId) { - existingCommunityIds.add(communityId); - } - - void removeCommunity(Long communityId) { - existingCommunityIds.remove(communityId); - } - - @Override - public boolean isCommunityExists(Long communityId) { - return existingCommunityIds.contains(communityId); - } - } - - private static class StubFeedQueryPort implements FeedQueryPort { - - private final Map summaries = new LinkedHashMap<>(); - private final Map details = new LinkedHashMap<>(); - - void save(FeedSummary summary, FeedDetail detail) { - summaries.put(summary.id(), summary); - details.put(detail.id(), detail); - } - - @Override - public Page findAll(Pageable pageable) { - List list = new ArrayList<>(summaries.values()); - int start = (int) pageable.getOffset(); - if (start >= list.size()) { - return new PageImpl<>(List.of(), pageable, list.size()); - } - - int end = Math.min(start + pageable.getPageSize(), list.size()); - return new PageImpl<>(list.subList(start, end), pageable, list.size()); - } - - @Override - public Optional findSummaryById(Long feedId) { - return Optional.ofNullable(summaries.get(feedId)); - } - - @Override - public Optional findDetailById(Long feedId) { - return Optional.ofNullable(details.get(feedId)); - } - } } diff --git a/src/test/java/com/example/bak/feed/domain/FeedRepositoryStub.java b/src/test/java/com/example/bak/feed/domain/FeedRepositoryStub.java index 7151f49..05bb110 100644 --- a/src/test/java/com/example/bak/feed/domain/FeedRepositoryStub.java +++ b/src/test/java/com/example/bak/feed/domain/FeedRepositoryStub.java @@ -1,11 +1,12 @@ package com.example.bak.feed.domain; +import com.example.bak.feed.application.command.port.FeedCommandPort; import com.example.bak.global.support.AbstractStubRepository; import java.util.Objects; public class FeedRepositoryStub extends AbstractStubRepository - implements FeedRepository { + implements FeedRepository, FeedCommandPort { @Override protected Long getId(Feed feed) { From f80d05c92699c4a1c46fb7e465f79e7e86fbd4ef Mon Sep 17 00:00:00 2001 From: jbh010204 Date: Wed, 3 Dec 2025 22:02:29 +0900 Subject: [PATCH 08/29] =?UTF-8?q?feat:=20FeedCommand=20=EB=8F=84=EB=A9=94?= =?UTF-8?q?=EC=9D=B8=20+=20=EC=84=9C=EB=B9=84=EC=8A=A4=20CQRS=20=EB=B6=84?= =?UTF-8?q?=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../command/FeedCommentCommandService.java | 28 +++++------ .../command/port/FeedCommentCommandPort.java | 12 +++++ .../query/FeedCommentQueryService.java | 9 ++-- .../query/port/FeedCommentQueryPort.java | 10 ++++ .../domain/FeedCommentRepository.java | 13 ------ .../FeedCommentCommandAdapter.java | 25 ++++++++++ .../persistence/FeedCommentJpaRepository.java | 13 +----- .../persistence/FeedCommentQueryAdapter.java | 22 +++++++++ .../presentation/FeedCommentController.java | 5 +- .../FeedCommentServiceUnitTest.java | 46 ++++++++++++++----- .../domain/FeedCommentRepositoryStub.java | 14 +++++- 11 files changed, 137 insertions(+), 60 deletions(-) create mode 100644 src/main/java/com/example/bak/feedcomment/application/command/port/FeedCommentCommandPort.java create mode 100644 src/main/java/com/example/bak/feedcomment/application/query/port/FeedCommentQueryPort.java delete mode 100644 src/main/java/com/example/bak/feedcomment/domain/FeedCommentRepository.java create mode 100644 src/main/java/com/example/bak/feedcomment/infra/persistence/FeedCommentCommandAdapter.java create mode 100644 src/main/java/com/example/bak/feedcomment/infra/persistence/FeedCommentQueryAdapter.java diff --git a/src/main/java/com/example/bak/feedcomment/application/command/FeedCommentCommandService.java b/src/main/java/com/example/bak/feedcomment/application/command/FeedCommentCommandService.java index f4f5864..1b8bedc 100644 --- a/src/main/java/com/example/bak/feedcomment/application/command/FeedCommentCommandService.java +++ b/src/main/java/com/example/bak/feedcomment/application/command/FeedCommentCommandService.java @@ -1,13 +1,13 @@ package com.example.bak.feedcomment.application.command; +import com.example.bak.feed.application.command.port.FeedCommandPort; import com.example.bak.feed.domain.Feed; -import com.example.bak.feed.domain.FeedRepository; +import com.example.bak.feedcomment.application.command.port.FeedCommentCommandPort; +import com.example.bak.feedcomment.application.command.port.UserDataPort; +import com.example.bak.feedcomment.application.command.port.UserDataPort.UserSnapshot; import com.example.bak.feedcomment.domain.FeedComment; -import com.example.bak.feedcomment.domain.FeedCommentRepository; import com.example.bak.global.exception.BusinessException; import com.example.bak.global.exception.ErrorCode; -import com.example.bak.user.domain.User; -import com.example.bak.user.domain.UserRepository; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -17,29 +17,29 @@ @Transactional public class FeedCommentCommandService { - private final FeedCommentRepository commentRepository; - private final FeedRepository feedRepository; - private final UserRepository userRepository; + private final FeedCommentCommandPort feedCommentCommandPort; + private final FeedCommandPort feedCommandPort; + private final UserDataPort userDataPort; public void createComment(Long feedId, String content, Long userId) { - Feed feed = feedRepository.findById(feedId) + Feed feed = feedCommandPort.findById(feedId) .orElseThrow(() -> new BusinessException(ErrorCode.FEED_NOT_FOUND)); - User user = userRepository.findById(userId) + UserSnapshot user = userDataPort.findById(userId) .orElseThrow(() -> new BusinessException(ErrorCode.USER_NOT_FOUND)); FeedComment newComment = FeedComment.create( content, - user.getId(), - user.getProfile().getNickname() + user.id(), + user.nickname() ); feed.addComment(newComment); - commentRepository.save(newComment); + feedCommentCommandPort.save(newComment); } public void updateComment(Long commentId, String content, Long userId) { - FeedComment comment = commentRepository.findById(commentId) + FeedComment comment = feedCommentCommandPort.findById(commentId) .orElseThrow(() -> new BusinessException(ErrorCode.COMMENT_NOT_FOUND)); if (!comment.isWrittenBy(userId)) { @@ -47,6 +47,6 @@ public void updateComment(Long commentId, String content, Long userId) { } comment.updateComment(content); + feedCommentCommandPort.save(comment); } } - diff --git a/src/main/java/com/example/bak/feedcomment/application/command/port/FeedCommentCommandPort.java b/src/main/java/com/example/bak/feedcomment/application/command/port/FeedCommentCommandPort.java new file mode 100644 index 0000000..77765cc --- /dev/null +++ b/src/main/java/com/example/bak/feedcomment/application/command/port/FeedCommentCommandPort.java @@ -0,0 +1,12 @@ +package com.example.bak.feedcomment.application.command.port; + +import com.example.bak.feedcomment.domain.FeedComment; +import java.util.Optional; + +public interface FeedCommentCommandPort { + + FeedComment save(FeedComment comment); + + Optional findById(Long commentId); +} + diff --git a/src/main/java/com/example/bak/feedcomment/application/query/FeedCommentQueryService.java b/src/main/java/com/example/bak/feedcomment/application/query/FeedCommentQueryService.java index 8c527d4..f1451d6 100644 --- a/src/main/java/com/example/bak/feedcomment/application/query/FeedCommentQueryService.java +++ b/src/main/java/com/example/bak/feedcomment/application/query/FeedCommentQueryService.java @@ -1,7 +1,7 @@ package com.example.bak.feedcomment.application.query; import com.example.bak.feedcomment.application.query.dto.CommentInfo; -import com.example.bak.feedcomment.domain.FeedCommentRepository; +import com.example.bak.feedcomment.application.query.port.FeedCommentQueryPort; import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; @@ -12,12 +12,9 @@ @Transactional(readOnly = true) public class FeedCommentQueryService { - private final FeedCommentRepository commentRepository; + private final FeedCommentQueryPort feedCommentQueryPort; public List getComments(Long feedId) { - return commentRepository.findByFeedId(feedId).stream() - .map(CommentInfo::from) - .toList(); + return feedCommentQueryPort.findByFeedId(feedId); } } - diff --git a/src/main/java/com/example/bak/feedcomment/application/query/port/FeedCommentQueryPort.java b/src/main/java/com/example/bak/feedcomment/application/query/port/FeedCommentQueryPort.java new file mode 100644 index 0000000..fab6fe9 --- /dev/null +++ b/src/main/java/com/example/bak/feedcomment/application/query/port/FeedCommentQueryPort.java @@ -0,0 +1,10 @@ +package com.example.bak.feedcomment.application.query.port; + +import com.example.bak.feedcomment.application.query.dto.CommentInfo; +import java.util.List; + +public interface FeedCommentQueryPort { + + List findByFeedId(Long feedId); +} + diff --git a/src/main/java/com/example/bak/feedcomment/domain/FeedCommentRepository.java b/src/main/java/com/example/bak/feedcomment/domain/FeedCommentRepository.java deleted file mode 100644 index 1890f76..0000000 --- a/src/main/java/com/example/bak/feedcomment/domain/FeedCommentRepository.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.example.bak.feedcomment.domain; - -import java.util.List; -import java.util.Optional; - -public interface FeedCommentRepository { - - FeedComment save(FeedComment comment); - - List findByFeedId(Long feedId); - - Optional findById(Long id); -} diff --git a/src/main/java/com/example/bak/feedcomment/infra/persistence/FeedCommentCommandAdapter.java b/src/main/java/com/example/bak/feedcomment/infra/persistence/FeedCommentCommandAdapter.java new file mode 100644 index 0000000..7d0f09f --- /dev/null +++ b/src/main/java/com/example/bak/feedcomment/infra/persistence/FeedCommentCommandAdapter.java @@ -0,0 +1,25 @@ +package com.example.bak.feedcomment.infra.persistence; + +import com.example.bak.feedcomment.application.command.port.FeedCommentCommandPort; +import com.example.bak.feedcomment.domain.FeedComment; +import java.util.Optional; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Repository; + +@Repository +@RequiredArgsConstructor +public class FeedCommentCommandAdapter implements FeedCommentCommandPort { + + private final FeedCommentJpaRepository feedCommentJpaRepository; + + @Override + public FeedComment save(FeedComment comment) { + return feedCommentJpaRepository.save(comment); + } + + @Override + public Optional findById(Long commentId) { + return feedCommentJpaRepository.findById(commentId); + } +} + diff --git a/src/main/java/com/example/bak/feedcomment/infra/persistence/FeedCommentJpaRepository.java b/src/main/java/com/example/bak/feedcomment/infra/persistence/FeedCommentJpaRepository.java index 82391a9..37ecab1 100644 --- a/src/main/java/com/example/bak/feedcomment/infra/persistence/FeedCommentJpaRepository.java +++ b/src/main/java/com/example/bak/feedcomment/infra/persistence/FeedCommentJpaRepository.java @@ -1,21 +1,10 @@ package com.example.bak.feedcomment.infra.persistence; import com.example.bak.feedcomment.domain.FeedComment; -import com.example.bak.feedcomment.domain.FeedCommentRepository; import java.util.List; -import java.util.Optional; import org.springframework.data.jpa.repository.JpaRepository; +public interface FeedCommentJpaRepository extends JpaRepository { -public interface FeedCommentJpaRepository extends JpaRepository, - FeedCommentRepository { - - @Override - FeedComment save(FeedComment comment); - - @Override List findByFeedId(Long feedId); - - @Override - Optional findById(Long id); } diff --git a/src/main/java/com/example/bak/feedcomment/infra/persistence/FeedCommentQueryAdapter.java b/src/main/java/com/example/bak/feedcomment/infra/persistence/FeedCommentQueryAdapter.java new file mode 100644 index 0000000..d051e74 --- /dev/null +++ b/src/main/java/com/example/bak/feedcomment/infra/persistence/FeedCommentQueryAdapter.java @@ -0,0 +1,22 @@ +package com.example.bak.feedcomment.infra.persistence; + +import com.example.bak.feedcomment.application.query.dto.CommentInfo; +import com.example.bak.feedcomment.application.query.port.FeedCommentQueryPort; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Repository; + +@Repository +@RequiredArgsConstructor +public class FeedCommentQueryAdapter implements FeedCommentQueryPort { + + private final FeedCommentJpaRepository feedCommentJpaRepository; + + @Override + public List findByFeedId(Long feedId) { + return feedCommentJpaRepository.findByFeedId(feedId).stream() + .map(CommentInfo::from) + .toList(); + } +} + diff --git a/src/main/java/com/example/bak/feedcomment/presentation/FeedCommentController.java b/src/main/java/com/example/bak/feedcomment/presentation/FeedCommentController.java index 4a0c473..44b9d33 100644 --- a/src/main/java/com/example/bak/feedcomment/presentation/FeedCommentController.java +++ b/src/main/java/com/example/bak/feedcomment/presentation/FeedCommentController.java @@ -53,9 +53,10 @@ public ResponseEntity getComment(@PathVariable Long feedId) { @PutMapping("/comments/{commentId}") public ResponseEntity updateComment( @PathVariable Long commentId, - @RequestBody CommentRequest request + @RequestBody CommentRequest request, + @AuthUser Long userId ) { - feedCommentCommandService.updateComment(commentId, request.content(), request.userId()); + feedCommentCommandService.updateComment(commentId, request.content(), userId); ApiResponse response = ApiResponseFactory.successVoid("댓글을 성공적으로 수정하였습니다."); return ResponseEntity.ok(response); } diff --git a/src/test/java/com/example/bak/feedcomment/application/FeedCommentServiceUnitTest.java b/src/test/java/com/example/bak/feedcomment/application/FeedCommentServiceUnitTest.java index 2976ad4..11f279e 100644 --- a/src/test/java/com/example/bak/feedcomment/application/FeedCommentServiceUnitTest.java +++ b/src/test/java/com/example/bak/feedcomment/application/FeedCommentServiceUnitTest.java @@ -8,13 +8,15 @@ import com.example.bak.feed.domain.Feed; import com.example.bak.feed.domain.FeedRepositoryStub; import com.example.bak.feedcomment.application.command.FeedCommentCommandService; +import com.example.bak.feedcomment.application.command.port.UserDataPort; import com.example.bak.feedcomment.application.query.FeedCommentQueryService; import com.example.bak.feedcomment.domain.FeedComment; import com.example.bak.feedcomment.domain.FeedCommentRepositoryStub; import com.example.bak.global.exception.ErrorCode; import com.example.bak.user.domain.User; -import com.example.bak.user.domain.UserRepositoryStub; import java.util.List; +import java.util.Optional; +import java.util.concurrent.ConcurrentHashMap; import java.util.stream.IntStream; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; @@ -64,33 +66,51 @@ private List createComments(int count) { .toList(); } + private static class StubUserDataPort implements UserDataPort { + + private final ConcurrentHashMap store = new ConcurrentHashMap<>(); + + void save(User user) { + store.put(user.getId(), + new UserSnapshot(user.getId(), user.getProfile().getNickname())); + } + + @Override + public Optional findById(Long userId) { + return Optional.ofNullable(store.get(userId)); + } + } + @Nested @DisplayName("FeedCommentCommandService") class FeedCommentCommandServiceTest { private FeedCommentCommandService feedCommentCommandService; private FeedCommentRepositoryStub feedCommentRepository; + private StubUserDataPort userDataPort; @BeforeEach void setUp() { FeedRepositoryStub feedRepository = new FeedRepositoryStub(); - UserRepositoryStub userRepository = new UserRepositoryStub(); + feedRepository.save(testFeed); + + userDataPort = new StubUserDataPort(); + userDataPort.save(testUser); + feedCommentRepository = new FeedCommentRepositoryStub(); feedCommentCommandService = new FeedCommentCommandService( feedCommentRepository, feedRepository, - userRepository + userDataPort ); - - userRepository.save(testUser); - feedRepository.save(testFeed); } @Test @DisplayName("피드 댓글 생성에 성공한다") void createComment_success() { - feedCommentCommandService.createComment(EXISTING_FEED_ID, COMMENT_CONTENT, EXISTING_USER_ID); + feedCommentCommandService.createComment(EXISTING_FEED_ID, COMMENT_CONTENT, + EXISTING_USER_ID); var saved = feedCommentRepository.findAll().getFirst(); assertThat(saved.getAuthorId()).isEqualTo(EXISTING_USER_ID); @@ -129,7 +149,8 @@ void createComment_when_userNotFound() { @DisplayName("피드 댓글 업데이트에 성공한다") void updateComment_success() { feedCommentRepository.save( - FeedComment.testInstance(1L, COMMENT_CONTENT, EXISTING_USER_ID, nickname(), testFeed) + FeedComment.testInstance(1L, COMMENT_CONTENT, EXISTING_USER_ID, nickname(), + testFeed) ); feedCommentCommandService.updateComment(1L, UPDATED_CONTENT, EXISTING_USER_ID); @@ -143,11 +164,13 @@ void updateComment_success() { @DisplayName("피드 댓글 업데이트 권한이 없을 때 예외를 던진다") void updateComment_when_isNotAuthor() { feedCommentRepository.save( - FeedComment.testInstance(1L, COMMENT_CONTENT, EXISTING_USER_ID, nickname(), testFeed) + FeedComment.testInstance(1L, COMMENT_CONTENT, EXISTING_USER_ID, nickname(), + testFeed) ); assertBusiness( - () -> feedCommentCommandService.updateComment(1L, UPDATED_CONTENT, NOT_FOUND_USER_ID), + () -> feedCommentCommandService.updateComment(1L, UPDATED_CONTENT, + NOT_FOUND_USER_ID), ErrorCode.UNAUTHORIZED_ACTION ); } @@ -156,7 +179,8 @@ void updateComment_when_isNotAuthor() { @DisplayName("존재하지 않는 댓글 업데이트 시 예외") void updateComment_when_commentNotFound() { assertBusiness( - () -> feedCommentCommandService.updateComment(1L, UPDATED_CONTENT, EXISTING_USER_ID), + () -> feedCommentCommandService.updateComment(1L, UPDATED_CONTENT, + EXISTING_USER_ID), ErrorCode.COMMENT_NOT_FOUND ); } diff --git a/src/test/java/com/example/bak/feedcomment/domain/FeedCommentRepositoryStub.java b/src/test/java/com/example/bak/feedcomment/domain/FeedCommentRepositoryStub.java index 17580e5..ddaa728 100644 --- a/src/test/java/com/example/bak/feedcomment/domain/FeedCommentRepositoryStub.java +++ b/src/test/java/com/example/bak/feedcomment/domain/FeedCommentRepositoryStub.java @@ -1,12 +1,16 @@ package com.example.bak.feedcomment.domain; +import com.example.bak.feedcomment.application.command.port.FeedCommentCommandPort; +import com.example.bak.feedcomment.application.query.dto.CommentInfo; +import com.example.bak.feedcomment.application.query.port.FeedCommentQueryPort; import com.example.bak.global.support.AbstractStubRepository; import java.util.List; import java.util.Objects; +import java.util.Optional; public class FeedCommentRepositoryStub extends AbstractStubRepository - implements FeedCommentRepository { + implements FeedCommentCommandPort, FeedCommentQueryPort { @Override protected Long getId(FeedComment feedComment) { @@ -19,9 +23,15 @@ protected boolean isSame(Long left, Long right) { } @Override - public List findByFeedId(Long feedId) { + public List findByFeedId(Long feedId) { return findAll().stream() .filter(comment -> comment.getFeed().getId().equals(feedId)) + .map(CommentInfo::from) .toList(); } + + @Override + public Optional findById(Long commentId) { + return super.findById(commentId); + } } From 4a3725850c882289a08ebc273c6c5f94f6b05374 Mon Sep 17 00:00:00 2001 From: jbh010204 Date: Wed, 3 Dec 2025 22:02:51 +0900 Subject: [PATCH 09/29] =?UTF-8?q?feat:=20=EB=8C=93=EA=B8=80=20=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C=EC=8B=9C=20=ED=95=84=EC=9A=94=ED=95=9C=20=EC=9C=A0?= =?UTF-8?q?=EC=A0=80=20=EC=A0=95=EB=B3=B4=EB=A5=BC=20=EC=9C=84=ED=95=B4=20?= =?UTF-8?q?UserData=20=ED=8F=AC=ED=8A=B8/=EC=96=B4=EB=8C=91=ED=84=B0=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../command/port/UserDataPort.java | 12 ++++++++++ .../bak/user/infra/query/UserDataAdapter.java | 24 +++++++++++++++++++ 2 files changed, 36 insertions(+) create mode 100644 src/main/java/com/example/bak/feedcomment/application/command/port/UserDataPort.java create mode 100644 src/main/java/com/example/bak/user/infra/query/UserDataAdapter.java diff --git a/src/main/java/com/example/bak/feedcomment/application/command/port/UserDataPort.java b/src/main/java/com/example/bak/feedcomment/application/command/port/UserDataPort.java new file mode 100644 index 0000000..2e9148a --- /dev/null +++ b/src/main/java/com/example/bak/feedcomment/application/command/port/UserDataPort.java @@ -0,0 +1,12 @@ +package com.example.bak.feedcomment.application.command.port; + +import java.util.Optional; + +public interface UserDataPort { + + Optional findById(Long userId); + + record UserSnapshot(Long id, String nickname) { + + } +} diff --git a/src/main/java/com/example/bak/user/infra/query/UserDataAdapter.java b/src/main/java/com/example/bak/user/infra/query/UserDataAdapter.java new file mode 100644 index 0000000..8960d67 --- /dev/null +++ b/src/main/java/com/example/bak/user/infra/query/UserDataAdapter.java @@ -0,0 +1,24 @@ +package com.example.bak.user.infra.query; + +import com.example.bak.feedcomment.application.command.port.UserDataPort; +import com.example.bak.feedcomment.application.command.port.UserDataPort.UserSnapshot; +import com.example.bak.user.domain.UserRepository; +import java.util.Optional; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Repository; + +@Repository +@RequiredArgsConstructor +public class UserDataAdapter implements UserDataPort { + + private final UserRepository userRepository; + + @Override + public Optional findById(Long userId) { + return userRepository.findById(userId) + .map(user -> new UserSnapshot( + user.getId(), + user.getProfile().getNickname() + )); + } +} From 4179e1e29e73e60615e08dc49c7c4e10ffd7fb03 Mon Sep 17 00:00:00 2001 From: jbh010204 Date: Thu, 4 Dec 2025 19:59:43 +0900 Subject: [PATCH 10/29] =?UTF-8?q?refactor:=20Feed=EC=99=80=20FeedComment?= =?UTF-8?q?=20=EB=8A=90=EC=8A=A8=ED=95=9C=20=ED=98=95=ED=83=9C=EB=A1=9C=20?= =?UTF-8?q?=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/example/bak/feed/domain/Feed.java | 16 ---------- .../command/FeedCommentCommandService.java | 5 ++-- .../bak/feedcomment/domain/FeedComment.java | 30 ++++++++----------- .../bak/user/infra/query/UserDataAdapter.java | 1 - .../FeedCommentServiceUnitTest.java | 14 ++++----- .../domain/FeedCommentRepositoryStub.java | 2 +- 6 files changed, 22 insertions(+), 46 deletions(-) diff --git a/src/main/java/com/example/bak/feed/domain/Feed.java b/src/main/java/com/example/bak/feed/domain/Feed.java index 669318d..c89cc60 100644 --- a/src/main/java/com/example/bak/feed/domain/Feed.java +++ b/src/main/java/com/example/bak/feed/domain/Feed.java @@ -1,25 +1,17 @@ package com.example.bak.feed.domain; -import com.example.bak.feedcomment.domain.FeedComment; -import jakarta.persistence.CascadeType; import jakarta.persistence.Column; import jakarta.persistence.Entity; -import jakarta.persistence.FetchType; import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; -import jakarta.persistence.OneToMany; -import java.util.ArrayList; -import java.util.List; import lombok.AccessLevel; -import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; @Entity(name = "feeds") @Getter @NoArgsConstructor(access = AccessLevel.PROTECTED) -@AllArgsConstructor(access = AccessLevel.PROTECTED) public class Feed { @Id @@ -32,9 +24,6 @@ public class Feed { @Column(nullable = false) private String content; - @OneToMany(mappedBy = "feed", fetch = FetchType.LAZY, cascade = CascadeType.ALL, orphanRemoval = true) - private List comments = new ArrayList<>(); - @Column(nullable = false) private Long communityId; @@ -74,9 +63,4 @@ public static Feed testInstance( ) { return new Feed(id, title, content, communityId, userId); } - - public void addComment(FeedComment comment) { - comment.joinFeed(this); - this.comments.add(comment); - } } diff --git a/src/main/java/com/example/bak/feedcomment/application/command/FeedCommentCommandService.java b/src/main/java/com/example/bak/feedcomment/application/command/FeedCommentCommandService.java index 1b8bedc..2cc336f 100644 --- a/src/main/java/com/example/bak/feedcomment/application/command/FeedCommentCommandService.java +++ b/src/main/java/com/example/bak/feedcomment/application/command/FeedCommentCommandService.java @@ -1,7 +1,6 @@ package com.example.bak.feedcomment.application.command; import com.example.bak.feed.application.command.port.FeedCommandPort; -import com.example.bak.feed.domain.Feed; import com.example.bak.feedcomment.application.command.port.FeedCommentCommandPort; import com.example.bak.feedcomment.application.command.port.UserDataPort; import com.example.bak.feedcomment.application.command.port.UserDataPort.UserSnapshot; @@ -22,19 +21,19 @@ public class FeedCommentCommandService { private final UserDataPort userDataPort; public void createComment(Long feedId, String content, Long userId) { - Feed feed = feedCommandPort.findById(feedId) + feedCommandPort.findById(feedId) .orElseThrow(() -> new BusinessException(ErrorCode.FEED_NOT_FOUND)); UserSnapshot user = userDataPort.findById(userId) .orElseThrow(() -> new BusinessException(ErrorCode.USER_NOT_FOUND)); FeedComment newComment = FeedComment.create( + feedId, content, user.id(), user.nickname() ); - feed.addComment(newComment); feedCommentCommandPort.save(newComment); } diff --git a/src/main/java/com/example/bak/feedcomment/domain/FeedComment.java b/src/main/java/com/example/bak/feedcomment/domain/FeedComment.java index acde6f8..c9f48a9 100644 --- a/src/main/java/com/example/bak/feedcomment/domain/FeedComment.java +++ b/src/main/java/com/example/bak/feedcomment/domain/FeedComment.java @@ -1,13 +1,10 @@ package com.example.bak.feedcomment.domain; -import com.example.bak.feed.domain.Feed; import jakarta.persistence.Column; import jakarta.persistence.Entity; -import jakarta.persistence.FetchType; import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; -import jakarta.persistence.ManyToOne; import java.util.Objects; import lombok.AccessLevel; import lombok.AllArgsConstructor; @@ -24,6 +21,9 @@ public class FeedComment { @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; + @Column(name = "feed_id", nullable = false) + private Long feedId; + @Column(nullable = false) private Long authorId; @@ -33,34 +33,28 @@ public class FeedComment { @Column(nullable = false) private String comment; - @ManyToOne(fetch = FetchType.LAZY) - private Feed feed; - - private FeedComment(Long id, String comment, Long authorId, String authorNickname, Feed feed) { + private FeedComment(Long id, Long feedId, String comment, Long authorId, String authorNickname) { this.id = id; + this.feedId = feedId; this.comment = comment; this.authorId = authorId; this.authorNickname = authorNickname; - this.feed = feed; } - private FeedComment(String comment, Long authorId, String authorNickname) { + private FeedComment(Long feedId, String comment, Long authorId, String authorNickname) { + this.feedId = feedId; this.comment = comment; this.authorId = authorId; this.authorNickname = authorNickname; } - public static FeedComment create(String comment, Long authorId, String authorNickname) { - return new FeedComment(comment, authorId, authorNickname); - } - - public static FeedComment testInstance(Long id, String comment, Long authorId, - String authorNickname, Feed feed) { - return new FeedComment(id, comment, authorId, authorNickname, feed); + public static FeedComment create(Long feedId, String comment, Long authorId, String authorNickname) { + return new FeedComment(feedId, comment, authorId, authorNickname); } - public void joinFeed(Feed feed) { - this.feed = feed; + public static FeedComment testInstance(Long id, Long feedId, String comment, Long authorId, + String authorNickname) { + return new FeedComment(id, feedId, comment, authorId, authorNickname); } public void updateComment(String comment) { diff --git a/src/main/java/com/example/bak/user/infra/query/UserDataAdapter.java b/src/main/java/com/example/bak/user/infra/query/UserDataAdapter.java index 8960d67..5672e07 100644 --- a/src/main/java/com/example/bak/user/infra/query/UserDataAdapter.java +++ b/src/main/java/com/example/bak/user/infra/query/UserDataAdapter.java @@ -1,7 +1,6 @@ package com.example.bak.user.infra.query; import com.example.bak.feedcomment.application.command.port.UserDataPort; -import com.example.bak.feedcomment.application.command.port.UserDataPort.UserSnapshot; import com.example.bak.user.domain.UserRepository; import java.util.Optional; import lombok.RequiredArgsConstructor; diff --git a/src/test/java/com/example/bak/feedcomment/application/FeedCommentServiceUnitTest.java b/src/test/java/com/example/bak/feedcomment/application/FeedCommentServiceUnitTest.java index 11f279e..a863351 100644 --- a/src/test/java/com/example/bak/feedcomment/application/FeedCommentServiceUnitTest.java +++ b/src/test/java/com/example/bak/feedcomment/application/FeedCommentServiceUnitTest.java @@ -58,10 +58,10 @@ private List createComments(int count) { return IntStream.rangeClosed(1, count) .mapToObj(i -> FeedComment.testInstance( (long) i, + testFeed.getId(), COMMENT_CONTENT + i, testUser.getId(), - nickname(), - testFeed + nickname() )) .toList(); } @@ -115,7 +115,7 @@ void createComment_success() { var saved = feedCommentRepository.findAll().getFirst(); assertThat(saved.getAuthorId()).isEqualTo(EXISTING_USER_ID); assertThat(saved.getAuthorNickname()).isEqualTo(nickname()); - assertThat(saved.getFeed().getId()).isEqualTo(EXISTING_FEED_ID); + assertThat(saved.getFeedId()).isEqualTo(EXISTING_FEED_ID); assertThat(saved.getComment()).isEqualTo(COMMENT_CONTENT); } @@ -149,8 +149,8 @@ void createComment_when_userNotFound() { @DisplayName("피드 댓글 업데이트에 성공한다") void updateComment_success() { feedCommentRepository.save( - FeedComment.testInstance(1L, COMMENT_CONTENT, EXISTING_USER_ID, nickname(), - testFeed) + FeedComment.testInstance(1L, testFeed.getId(), COMMENT_CONTENT, + EXISTING_USER_ID, nickname()) ); feedCommentCommandService.updateComment(1L, UPDATED_CONTENT, EXISTING_USER_ID); @@ -164,8 +164,8 @@ void updateComment_success() { @DisplayName("피드 댓글 업데이트 권한이 없을 때 예외를 던진다") void updateComment_when_isNotAuthor() { feedCommentRepository.save( - FeedComment.testInstance(1L, COMMENT_CONTENT, EXISTING_USER_ID, nickname(), - testFeed) + FeedComment.testInstance(1L, testFeed.getId(), COMMENT_CONTENT, + EXISTING_USER_ID, nickname()) ); assertBusiness( diff --git a/src/test/java/com/example/bak/feedcomment/domain/FeedCommentRepositoryStub.java b/src/test/java/com/example/bak/feedcomment/domain/FeedCommentRepositoryStub.java index ddaa728..8dc8813 100644 --- a/src/test/java/com/example/bak/feedcomment/domain/FeedCommentRepositoryStub.java +++ b/src/test/java/com/example/bak/feedcomment/domain/FeedCommentRepositoryStub.java @@ -25,7 +25,7 @@ protected boolean isSame(Long left, Long right) { @Override public List findByFeedId(Long feedId) { return findAll().stream() - .filter(comment -> comment.getFeed().getId().equals(feedId)) + .filter(comment -> comment.getFeedId().equals(feedId)) .map(CommentInfo::from) .toList(); } From 76b876312d7130042e90433153262d3b6566cdc7 Mon Sep 17 00:00:00 2001 From: jbh010204 Date: Tue, 9 Dec 2025 16:55:49 +0900 Subject: [PATCH 11/29] =?UTF-8?q?refactor:=20FeedResult=20=ED=8F=B4?= =?UTF-8?q?=EB=8D=94=20=EA=B5=AC=EC=A1=B0=20Command=EC=9D=98=20DTO=20?= =?UTF-8?q?=EC=9C=84=EC=B9=98=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../bak/feed/application/command/FeedCommandService.java | 3 +-- .../feed/application/{query => command}/dto/FeedResult.java | 2 +- .../java/com/example/bak/feed/presentation/FeedController.java | 2 +- .../feedcomment/application/query/FeedCommentQueryService.java | 2 +- 4 files changed, 4 insertions(+), 5 deletions(-) rename src/main/java/com/example/bak/feed/application/{query => command}/dto/FeedResult.java (71%) diff --git a/src/main/java/com/example/bak/feed/application/command/FeedCommandService.java b/src/main/java/com/example/bak/feed/application/command/FeedCommandService.java index 1fcb661..786680a 100644 --- a/src/main/java/com/example/bak/feed/application/command/FeedCommandService.java +++ b/src/main/java/com/example/bak/feed/application/command/FeedCommandService.java @@ -1,8 +1,8 @@ package com.example.bak.feed.application.command; +import com.example.bak.feed.application.command.dto.FeedResult; import com.example.bak.feed.application.command.port.CommunityValidationPort; import com.example.bak.feed.application.command.port.FeedCommandPort; -import com.example.bak.feed.application.query.dto.FeedResult; import com.example.bak.feed.domain.Feed; import com.example.bak.global.exception.BusinessException; import com.example.bak.global.exception.ErrorCode; @@ -19,7 +19,6 @@ public class FeedCommandService { private final CommunityValidationPort communityValidationPort; public FeedResult createFeed(String title, String content, Long communityId, Long userId) { - if (!communityValidationPort.isCommunityExists(communityId)) { throw new BusinessException(ErrorCode.COMMUNITY_NOT_FOUND); } diff --git a/src/main/java/com/example/bak/feed/application/query/dto/FeedResult.java b/src/main/java/com/example/bak/feed/application/command/dto/FeedResult.java similarity index 71% rename from src/main/java/com/example/bak/feed/application/query/dto/FeedResult.java rename to src/main/java/com/example/bak/feed/application/command/dto/FeedResult.java index 5c39b37..726e451 100644 --- a/src/main/java/com/example/bak/feed/application/query/dto/FeedResult.java +++ b/src/main/java/com/example/bak/feed/application/command/dto/FeedResult.java @@ -1,4 +1,4 @@ -package com.example.bak.feed.application.query.dto; +package com.example.bak.feed.application.command.dto; public record FeedResult( Long id diff --git a/src/main/java/com/example/bak/feed/presentation/FeedController.java b/src/main/java/com/example/bak/feed/presentation/FeedController.java index 8c3d662..d547dd6 100644 --- a/src/main/java/com/example/bak/feed/presentation/FeedController.java +++ b/src/main/java/com/example/bak/feed/presentation/FeedController.java @@ -1,9 +1,9 @@ package com.example.bak.feed.presentation; import com.example.bak.feed.application.command.FeedCommandService; +import com.example.bak.feed.application.command.dto.FeedResult; import com.example.bak.feed.application.query.FeedQueryService; import com.example.bak.feed.application.query.dto.FeedDetail; -import com.example.bak.feed.application.query.dto.FeedResult; import com.example.bak.feed.application.query.dto.FeedSummary; import com.example.bak.feed.presentation.dto.FeedRequest; import com.example.bak.global.common.response.ApiResponse; diff --git a/src/main/java/com/example/bak/feedcomment/application/query/FeedCommentQueryService.java b/src/main/java/com/example/bak/feedcomment/application/query/FeedCommentQueryService.java index f1451d6..6a1ac41 100644 --- a/src/main/java/com/example/bak/feedcomment/application/query/FeedCommentQueryService.java +++ b/src/main/java/com/example/bak/feedcomment/application/query/FeedCommentQueryService.java @@ -17,4 +17,4 @@ public class FeedCommentQueryService { public List getComments(Long feedId) { return feedCommentQueryPort.findByFeedId(feedId); } -} +} \ No newline at end of file From 4b37f15de18c314eec60d2dd57704642093c8602 Mon Sep 17 00:00:00 2001 From: jbh010204 Date: Tue, 9 Dec 2025 16:57:37 +0900 Subject: [PATCH 12/29] =?UTF-8?q?refactor:=20FeedQueryService=20=EB=B6=88?= =?UTF-8?q?=ED=95=84=EC=9A=94=ED=95=9C=20=ED=8A=B8=EB=9E=9C=EC=9E=AD?= =?UTF-8?q?=EC=85=98=20=EC=96=B4=EB=85=B8=ED=85=8C=EC=9D=B4=EC=85=98=20?= =?UTF-8?q?=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../example/bak/feed/application/query/FeedQueryService.java | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/main/java/com/example/bak/feed/application/query/FeedQueryService.java b/src/main/java/com/example/bak/feed/application/query/FeedQueryService.java index 9885037..a61c143 100644 --- a/src/main/java/com/example/bak/feed/application/query/FeedQueryService.java +++ b/src/main/java/com/example/bak/feed/application/query/FeedQueryService.java @@ -11,7 +11,6 @@ import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; @Service @RequiredArgsConstructor @@ -19,19 +18,16 @@ public class FeedQueryService { private final FeedQueryPort feedQueryPort; - @Transactional(readOnly = true) public FeedDetail getFeedDetail(Long feedId) { return feedQueryPort.findDetailById(feedId) .orElseThrow(() -> new BusinessException(ErrorCode.FEED_NOT_FOUND)); } - @Transactional(readOnly = true) public FeedSummary getFeedSummary(Long feedId) { return feedQueryPort.findSummaryById(feedId) .orElseThrow(() -> new BusinessException(ErrorCode.FEED_NOT_FOUND)); } - @Transactional(readOnly = true) public List getFeeds(int page, int size) { Pageable pageable = PageRequest.of(page, size); Page feedPage = feedQueryPort.findAll(pageable); From f5971efa6e16da150fdba3f1498cc61ad9c3170f Mon Sep 17 00:00:00 2001 From: jbh010204 Date: Tue, 9 Dec 2025 17:04:06 +0900 Subject: [PATCH 13/29] feat: UserSnapshot DTO renamed to UserSnapShot and updated references --- .../application/command/FeedCommentCommandService.java | 4 ++-- .../feedcomment/application/command/port/UserDataPort.java | 7 ++----- .../application/command/port/dto/UserSnapShot.java | 5 +++++ .../com/example/bak/user/infra/query/UserDataAdapter.java | 5 +++-- 4 files changed, 12 insertions(+), 9 deletions(-) create mode 100644 src/main/java/com/example/bak/feedcomment/application/command/port/dto/UserSnapShot.java diff --git a/src/main/java/com/example/bak/feedcomment/application/command/FeedCommentCommandService.java b/src/main/java/com/example/bak/feedcomment/application/command/FeedCommentCommandService.java index 2cc336f..52491b2 100644 --- a/src/main/java/com/example/bak/feedcomment/application/command/FeedCommentCommandService.java +++ b/src/main/java/com/example/bak/feedcomment/application/command/FeedCommentCommandService.java @@ -3,7 +3,7 @@ import com.example.bak.feed.application.command.port.FeedCommandPort; import com.example.bak.feedcomment.application.command.port.FeedCommentCommandPort; import com.example.bak.feedcomment.application.command.port.UserDataPort; -import com.example.bak.feedcomment.application.command.port.UserDataPort.UserSnapshot; +import com.example.bak.feedcomment.application.command.port.dto.UserSnapShot; import com.example.bak.feedcomment.domain.FeedComment; import com.example.bak.global.exception.BusinessException; import com.example.bak.global.exception.ErrorCode; @@ -24,7 +24,7 @@ public void createComment(Long feedId, String content, Long userId) { feedCommandPort.findById(feedId) .orElseThrow(() -> new BusinessException(ErrorCode.FEED_NOT_FOUND)); - UserSnapshot user = userDataPort.findById(userId) + UserSnapShot user = userDataPort.findById(userId) .orElseThrow(() -> new BusinessException(ErrorCode.USER_NOT_FOUND)); FeedComment newComment = FeedComment.create( diff --git a/src/main/java/com/example/bak/feedcomment/application/command/port/UserDataPort.java b/src/main/java/com/example/bak/feedcomment/application/command/port/UserDataPort.java index 2e9148a..d14f547 100644 --- a/src/main/java/com/example/bak/feedcomment/application/command/port/UserDataPort.java +++ b/src/main/java/com/example/bak/feedcomment/application/command/port/UserDataPort.java @@ -1,12 +1,9 @@ package com.example.bak.feedcomment.application.command.port; +import com.example.bak.feedcomment.application.command.port.dto.UserSnapShot; import java.util.Optional; public interface UserDataPort { - Optional findById(Long userId); - - record UserSnapshot(Long id, String nickname) { - - } + Optional findById(Long userId); } diff --git a/src/main/java/com/example/bak/feedcomment/application/command/port/dto/UserSnapShot.java b/src/main/java/com/example/bak/feedcomment/application/command/port/dto/UserSnapShot.java new file mode 100644 index 0000000..900bdfd --- /dev/null +++ b/src/main/java/com/example/bak/feedcomment/application/command/port/dto/UserSnapShot.java @@ -0,0 +1,5 @@ +package com.example.bak.feedcomment.application.command.port.dto; + +public record UserSnapShot(Long id, String nickname) { + +} diff --git a/src/main/java/com/example/bak/user/infra/query/UserDataAdapter.java b/src/main/java/com/example/bak/user/infra/query/UserDataAdapter.java index 5672e07..cb538b0 100644 --- a/src/main/java/com/example/bak/user/infra/query/UserDataAdapter.java +++ b/src/main/java/com/example/bak/user/infra/query/UserDataAdapter.java @@ -1,6 +1,7 @@ package com.example.bak.user.infra.query; import com.example.bak.feedcomment.application.command.port.UserDataPort; +import com.example.bak.feedcomment.application.command.port.dto.UserSnapShot; import com.example.bak.user.domain.UserRepository; import java.util.Optional; import lombok.RequiredArgsConstructor; @@ -13,9 +14,9 @@ public class UserDataAdapter implements UserDataPort { private final UserRepository userRepository; @Override - public Optional findById(Long userId) { + public Optional findById(Long userId) { return userRepository.findById(userId) - .map(user -> new UserSnapshot( + .map(user -> new UserSnapShot( user.getId(), user.getProfile().getNickname() )); From fb40dd709fe963761daa1cd1fa9ea116105ff82c Mon Sep 17 00:00:00 2001 From: jbh010204 Date: Tue, 9 Dec 2025 17:06:04 +0900 Subject: [PATCH 14/29] =?UTF-8?q?refactor:=20FeedComment=EC=9D=98=20commen?= =?UTF-8?q?t=ED=95=84=EB=93=9C=EB=A5=BC=20content=EB=A1=9C=20=EB=AA=85?= =?UTF-8?q?=EC=B9=AD=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/query/dto/CommentInfo.java | 2 +- .../bak/feedcomment/domain/FeedComment.java | 16 +++++++++------- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/src/main/java/com/example/bak/feedcomment/application/query/dto/CommentInfo.java b/src/main/java/com/example/bak/feedcomment/application/query/dto/CommentInfo.java index 9ca0f6e..8573ed4 100644 --- a/src/main/java/com/example/bak/feedcomment/application/query/dto/CommentInfo.java +++ b/src/main/java/com/example/bak/feedcomment/application/query/dto/CommentInfo.java @@ -17,7 +17,7 @@ public static CommentInfo from(FeedComment comment) { comment.getId(), comment.getAuthorId(), comment.getAuthorNickname(), - comment.getComment() + comment.getContent() ); } } diff --git a/src/main/java/com/example/bak/feedcomment/domain/FeedComment.java b/src/main/java/com/example/bak/feedcomment/domain/FeedComment.java index c9f48a9..11f4bcb 100644 --- a/src/main/java/com/example/bak/feedcomment/domain/FeedComment.java +++ b/src/main/java/com/example/bak/feedcomment/domain/FeedComment.java @@ -31,24 +31,26 @@ public class FeedComment { private String authorNickname; @Column(nullable = false) - private String comment; + private String content; - private FeedComment(Long id, Long feedId, String comment, Long authorId, String authorNickname) { + private FeedComment(Long id, Long feedId, String content, Long authorId, + String authorNickname) { this.id = id; this.feedId = feedId; - this.comment = comment; + this.content = content; this.authorId = authorId; this.authorNickname = authorNickname; } - private FeedComment(Long feedId, String comment, Long authorId, String authorNickname) { + private FeedComment(Long feedId, String content, Long authorId, String authorNickname) { this.feedId = feedId; - this.comment = comment; + this.content = content; this.authorId = authorId; this.authorNickname = authorNickname; } - public static FeedComment create(Long feedId, String comment, Long authorId, String authorNickname) { + public static FeedComment create(Long feedId, String comment, Long authorId, + String authorNickname) { return new FeedComment(feedId, comment, authorId, authorNickname); } @@ -58,7 +60,7 @@ public static FeedComment testInstance(Long id, Long feedId, String comment, Lon } public void updateComment(String comment) { - this.comment = comment; + this.content = comment; } public boolean isWrittenBy(Long authorId) { From ff62a4e9c35809512b0f9901c90c243786d0e0ac Mon Sep 17 00:00:00 2001 From: jbh010204 Date: Tue, 9 Dec 2025 17:20:51 +0900 Subject: [PATCH 15/29] feat: rename FeedComment Aggregate to Comment --- .../command/CommentCommandService.java} | 22 ++--- .../command/port/CommentCommandPort.java | 11 +++ .../command/port/UserDataPort.java | 9 ++ .../command/port/dto/UserSnapShot.java | 5 ++ .../query/CommentQueryService.java | 20 +++++ .../application/query/dto/CommentInfo.java | 8 +- .../query/port/CommentQueryPort.java | 9 ++ .../domain/Comment.java} | 16 ++-- .../persistence/CommentCommandAdapter.java | 24 ++++++ .../persistence/CommentJpaRepository.java | 10 +++ .../persistence/CommentQueryAdapter.java | 21 +++++ .../presentation/CommentController.java} | 22 ++--- .../presentation/dto/CommentRequest.java | 5 ++ .../command/port/FeedCommentCommandPort.java | 12 --- .../command/port/UserDataPort.java | 9 -- .../command/port/dto/UserSnapShot.java | 5 -- .../query/FeedCommentQueryService.java | 20 ----- .../query/port/FeedCommentQueryPort.java | 10 --- .../FeedCommentCommandAdapter.java | 25 ------ .../persistence/FeedCommentJpaRepository.java | 10 --- .../persistence/FeedCommentQueryAdapter.java | 22 ----- .../presentation/dto/CommentRequest.java | 5 -- .../bak/user/infra/query/UserDataAdapter.java | 4 +- .../application/CommentServiceUnitTest.java} | 85 ++++++++++--------- .../domain/CommentRepositoryStub.java} | 20 ++--- .../feed/application/FeedServiceUnitTest.java | 2 +- 26 files changed, 204 insertions(+), 207 deletions(-) rename src/main/java/com/example/bak/{feedcomment/application/command/FeedCommentCommandService.java => comment/application/command/CommentCommandService.java} (65%) create mode 100644 src/main/java/com/example/bak/comment/application/command/port/CommentCommandPort.java create mode 100644 src/main/java/com/example/bak/comment/application/command/port/UserDataPort.java create mode 100644 src/main/java/com/example/bak/comment/application/command/port/dto/UserSnapShot.java create mode 100644 src/main/java/com/example/bak/comment/application/query/CommentQueryService.java rename src/main/java/com/example/bak/{feedcomment => comment}/application/query/dto/CommentInfo.java (57%) create mode 100644 src/main/java/com/example/bak/comment/application/query/port/CommentQueryPort.java rename src/main/java/com/example/bak/{feedcomment/domain/FeedComment.java => comment/domain/Comment.java} (73%) create mode 100644 src/main/java/com/example/bak/comment/infra/persistence/CommentCommandAdapter.java create mode 100644 src/main/java/com/example/bak/comment/infra/persistence/CommentJpaRepository.java create mode 100644 src/main/java/com/example/bak/comment/infra/persistence/CommentQueryAdapter.java rename src/main/java/com/example/bak/{feedcomment/presentation/FeedCommentController.java => comment/presentation/CommentController.java} (73%) create mode 100644 src/main/java/com/example/bak/comment/presentation/dto/CommentRequest.java delete mode 100644 src/main/java/com/example/bak/feedcomment/application/command/port/FeedCommentCommandPort.java delete mode 100644 src/main/java/com/example/bak/feedcomment/application/command/port/UserDataPort.java delete mode 100644 src/main/java/com/example/bak/feedcomment/application/command/port/dto/UserSnapShot.java delete mode 100644 src/main/java/com/example/bak/feedcomment/application/query/FeedCommentQueryService.java delete mode 100644 src/main/java/com/example/bak/feedcomment/application/query/port/FeedCommentQueryPort.java delete mode 100644 src/main/java/com/example/bak/feedcomment/infra/persistence/FeedCommentCommandAdapter.java delete mode 100644 src/main/java/com/example/bak/feedcomment/infra/persistence/FeedCommentJpaRepository.java delete mode 100644 src/main/java/com/example/bak/feedcomment/infra/persistence/FeedCommentQueryAdapter.java delete mode 100644 src/main/java/com/example/bak/feedcomment/presentation/dto/CommentRequest.java rename src/test/java/com/example/bak/{feedcomment/application/FeedCommentServiceUnitTest.java => comment/application/CommentServiceUnitTest.java} (65%) rename src/test/java/com/example/bak/{feedcomment/domain/FeedCommentRepositoryStub.java => comment/domain/CommentRepositoryStub.java} (50%) diff --git a/src/main/java/com/example/bak/feedcomment/application/command/FeedCommentCommandService.java b/src/main/java/com/example/bak/comment/application/command/CommentCommandService.java similarity index 65% rename from src/main/java/com/example/bak/feedcomment/application/command/FeedCommentCommandService.java rename to src/main/java/com/example/bak/comment/application/command/CommentCommandService.java index 52491b2..bc7ba31 100644 --- a/src/main/java/com/example/bak/feedcomment/application/command/FeedCommentCommandService.java +++ b/src/main/java/com/example/bak/comment/application/command/CommentCommandService.java @@ -1,10 +1,10 @@ -package com.example.bak.feedcomment.application.command; +package com.example.bak.comment.application.command; +import com.example.bak.comment.application.command.port.CommentCommandPort; +import com.example.bak.comment.application.command.port.UserDataPort; +import com.example.bak.comment.application.command.port.dto.UserSnapShot; +import com.example.bak.comment.domain.Comment; import com.example.bak.feed.application.command.port.FeedCommandPort; -import com.example.bak.feedcomment.application.command.port.FeedCommentCommandPort; -import com.example.bak.feedcomment.application.command.port.UserDataPort; -import com.example.bak.feedcomment.application.command.port.dto.UserSnapShot; -import com.example.bak.feedcomment.domain.FeedComment; import com.example.bak.global.exception.BusinessException; import com.example.bak.global.exception.ErrorCode; import lombok.RequiredArgsConstructor; @@ -14,9 +14,9 @@ @Service @RequiredArgsConstructor @Transactional -public class FeedCommentCommandService { +public class CommentCommandService { - private final FeedCommentCommandPort feedCommentCommandPort; + private final CommentCommandPort commentCommandPort; private final FeedCommandPort feedCommandPort; private final UserDataPort userDataPort; @@ -27,18 +27,18 @@ public void createComment(Long feedId, String content, Long userId) { UserSnapShot user = userDataPort.findById(userId) .orElseThrow(() -> new BusinessException(ErrorCode.USER_NOT_FOUND)); - FeedComment newComment = FeedComment.create( + Comment newComment = Comment.create( feedId, content, user.id(), user.nickname() ); - feedCommentCommandPort.save(newComment); + commentCommandPort.save(newComment); } public void updateComment(Long commentId, String content, Long userId) { - FeedComment comment = feedCommentCommandPort.findById(commentId) + Comment comment = commentCommandPort.findById(commentId) .orElseThrow(() -> new BusinessException(ErrorCode.COMMENT_NOT_FOUND)); if (!comment.isWrittenBy(userId)) { @@ -46,6 +46,6 @@ public void updateComment(Long commentId, String content, Long userId) { } comment.updateComment(content); - feedCommentCommandPort.save(comment); + commentCommandPort.save(comment); } } diff --git a/src/main/java/com/example/bak/comment/application/command/port/CommentCommandPort.java b/src/main/java/com/example/bak/comment/application/command/port/CommentCommandPort.java new file mode 100644 index 0000000..3d38b72 --- /dev/null +++ b/src/main/java/com/example/bak/comment/application/command/port/CommentCommandPort.java @@ -0,0 +1,11 @@ +package com.example.bak.comment.application.command.port; + +import com.example.bak.comment.domain.Comment; +import java.util.Optional; + +public interface CommentCommandPort { + + Comment save(Comment comment); + + Optional findById(Long commentId); +} diff --git a/src/main/java/com/example/bak/comment/application/command/port/UserDataPort.java b/src/main/java/com/example/bak/comment/application/command/port/UserDataPort.java new file mode 100644 index 0000000..82e5624 --- /dev/null +++ b/src/main/java/com/example/bak/comment/application/command/port/UserDataPort.java @@ -0,0 +1,9 @@ +package com.example.bak.comment.application.command.port; + +import com.example.bak.comment.application.command.port.dto.UserSnapShot; +import java.util.Optional; + +public interface UserDataPort { + + Optional findById(Long userId); +} diff --git a/src/main/java/com/example/bak/comment/application/command/port/dto/UserSnapShot.java b/src/main/java/com/example/bak/comment/application/command/port/dto/UserSnapShot.java new file mode 100644 index 0000000..1de1bbf --- /dev/null +++ b/src/main/java/com/example/bak/comment/application/command/port/dto/UserSnapShot.java @@ -0,0 +1,5 @@ +package com.example.bak.comment.application.command.port.dto; + +public record UserSnapShot(Long id, String nickname) { + +} diff --git a/src/main/java/com/example/bak/comment/application/query/CommentQueryService.java b/src/main/java/com/example/bak/comment/application/query/CommentQueryService.java new file mode 100644 index 0000000..ebd45a6 --- /dev/null +++ b/src/main/java/com/example/bak/comment/application/query/CommentQueryService.java @@ -0,0 +1,20 @@ +package com.example.bak.comment.application.query; + +import com.example.bak.comment.application.query.dto.CommentInfo; +import com.example.bak.comment.application.query.port.CommentQueryPort; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +@Transactional(readOnly = true) +public class CommentQueryService { + + private final CommentQueryPort commentQueryPort; + + public List getComments(Long feedId) { + return commentQueryPort.findByFeedId(feedId); + } +} diff --git a/src/main/java/com/example/bak/feedcomment/application/query/dto/CommentInfo.java b/src/main/java/com/example/bak/comment/application/query/dto/CommentInfo.java similarity index 57% rename from src/main/java/com/example/bak/feedcomment/application/query/dto/CommentInfo.java rename to src/main/java/com/example/bak/comment/application/query/dto/CommentInfo.java index 8573ed4..9f5aa1a 100644 --- a/src/main/java/com/example/bak/feedcomment/application/query/dto/CommentInfo.java +++ b/src/main/java/com/example/bak/comment/application/query/dto/CommentInfo.java @@ -1,9 +1,9 @@ -package com.example.bak.feedcomment.application.query.dto; +package com.example.bak.comment.application.query.dto; -import com.example.bak.feedcomment.domain.FeedComment; +import com.example.bak.comment.domain.Comment; /** - * FeedComment의 기본 정보를 담는 DTO Feed 상세 조회 시 포함됨 + * Comment의 기본 정보를 담는 DTO Feed 상세 조회 시 포함됨 */ public record CommentInfo( Long id, @@ -12,7 +12,7 @@ public record CommentInfo( String content ) { - public static CommentInfo from(FeedComment comment) { + public static CommentInfo from(Comment comment) { return new CommentInfo( comment.getId(), comment.getAuthorId(), diff --git a/src/main/java/com/example/bak/comment/application/query/port/CommentQueryPort.java b/src/main/java/com/example/bak/comment/application/query/port/CommentQueryPort.java new file mode 100644 index 0000000..1ff558a --- /dev/null +++ b/src/main/java/com/example/bak/comment/application/query/port/CommentQueryPort.java @@ -0,0 +1,9 @@ +package com.example.bak.comment.application.query.port; + +import com.example.bak.comment.application.query.dto.CommentInfo; +import java.util.List; + +public interface CommentQueryPort { + + List findByFeedId(Long feedId); +} diff --git a/src/main/java/com/example/bak/feedcomment/domain/FeedComment.java b/src/main/java/com/example/bak/comment/domain/Comment.java similarity index 73% rename from src/main/java/com/example/bak/feedcomment/domain/FeedComment.java rename to src/main/java/com/example/bak/comment/domain/Comment.java index 11f4bcb..d40f347 100644 --- a/src/main/java/com/example/bak/feedcomment/domain/FeedComment.java +++ b/src/main/java/com/example/bak/comment/domain/Comment.java @@ -1,4 +1,4 @@ -package com.example.bak.feedcomment.domain; +package com.example.bak.comment.domain; import jakarta.persistence.Column; import jakarta.persistence.Entity; @@ -15,7 +15,7 @@ @Getter @NoArgsConstructor(access = AccessLevel.PROTECTED) @AllArgsConstructor(access = AccessLevel.PROTECTED) -public class FeedComment { +public class Comment { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @@ -33,7 +33,7 @@ public class FeedComment { @Column(nullable = false) private String content; - private FeedComment(Long id, Long feedId, String content, Long authorId, + private Comment(Long id, Long feedId, String content, Long authorId, String authorNickname) { this.id = id; this.feedId = feedId; @@ -42,21 +42,21 @@ private FeedComment(Long id, Long feedId, String content, Long authorId, this.authorNickname = authorNickname; } - private FeedComment(Long feedId, String content, Long authorId, String authorNickname) { + private Comment(Long feedId, String content, Long authorId, String authorNickname) { this.feedId = feedId; this.content = content; this.authorId = authorId; this.authorNickname = authorNickname; } - public static FeedComment create(Long feedId, String comment, Long authorId, + public static Comment create(Long feedId, String comment, Long authorId, String authorNickname) { - return new FeedComment(feedId, comment, authorId, authorNickname); + return new Comment(feedId, comment, authorId, authorNickname); } - public static FeedComment testInstance(Long id, Long feedId, String comment, Long authorId, + public static Comment testInstance(Long id, Long feedId, String comment, Long authorId, String authorNickname) { - return new FeedComment(id, feedId, comment, authorId, authorNickname); + return new Comment(id, feedId, comment, authorId, authorNickname); } public void updateComment(String comment) { diff --git a/src/main/java/com/example/bak/comment/infra/persistence/CommentCommandAdapter.java b/src/main/java/com/example/bak/comment/infra/persistence/CommentCommandAdapter.java new file mode 100644 index 0000000..e048f7a --- /dev/null +++ b/src/main/java/com/example/bak/comment/infra/persistence/CommentCommandAdapter.java @@ -0,0 +1,24 @@ +package com.example.bak.comment.infra.persistence; + +import com.example.bak.comment.application.command.port.CommentCommandPort; +import com.example.bak.comment.domain.Comment; +import java.util.Optional; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Repository; + +@Repository +@RequiredArgsConstructor +public class CommentCommandAdapter implements CommentCommandPort { + + private final CommentJpaRepository commentJpaRepository; + + @Override + public Comment save(Comment comment) { + return commentJpaRepository.save(comment); + } + + @Override + public Optional findById(Long commentId) { + return commentJpaRepository.findById(commentId); + } +} diff --git a/src/main/java/com/example/bak/comment/infra/persistence/CommentJpaRepository.java b/src/main/java/com/example/bak/comment/infra/persistence/CommentJpaRepository.java new file mode 100644 index 0000000..6579811 --- /dev/null +++ b/src/main/java/com/example/bak/comment/infra/persistence/CommentJpaRepository.java @@ -0,0 +1,10 @@ +package com.example.bak.comment.infra.persistence; + +import com.example.bak.comment.domain.Comment; +import java.util.List; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface CommentJpaRepository extends JpaRepository { + + List findByFeedId(Long feedId); +} diff --git a/src/main/java/com/example/bak/comment/infra/persistence/CommentQueryAdapter.java b/src/main/java/com/example/bak/comment/infra/persistence/CommentQueryAdapter.java new file mode 100644 index 0000000..8e02131 --- /dev/null +++ b/src/main/java/com/example/bak/comment/infra/persistence/CommentQueryAdapter.java @@ -0,0 +1,21 @@ +package com.example.bak.comment.infra.persistence; + +import com.example.bak.comment.application.query.dto.CommentInfo; +import com.example.bak.comment.application.query.port.CommentQueryPort; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Repository; + +@Repository +@RequiredArgsConstructor +public class CommentQueryAdapter implements CommentQueryPort { + + private final CommentJpaRepository commentJpaRepository; + + @Override + public List findByFeedId(Long feedId) { + return commentJpaRepository.findByFeedId(feedId).stream() + .map(CommentInfo::from) + .toList(); + } +} diff --git a/src/main/java/com/example/bak/feedcomment/presentation/FeedCommentController.java b/src/main/java/com/example/bak/comment/presentation/CommentController.java similarity index 73% rename from src/main/java/com/example/bak/feedcomment/presentation/FeedCommentController.java rename to src/main/java/com/example/bak/comment/presentation/CommentController.java index 44b9d33..635af2e 100644 --- a/src/main/java/com/example/bak/feedcomment/presentation/FeedCommentController.java +++ b/src/main/java/com/example/bak/comment/presentation/CommentController.java @@ -1,9 +1,9 @@ -package com.example.bak.feedcomment.presentation; +package com.example.bak.comment.presentation; -import com.example.bak.feedcomment.application.command.FeedCommentCommandService; -import com.example.bak.feedcomment.application.query.FeedCommentQueryService; -import com.example.bak.feedcomment.application.query.dto.CommentInfo; -import com.example.bak.feedcomment.presentation.dto.CommentRequest; +import com.example.bak.comment.application.command.CommentCommandService; +import com.example.bak.comment.application.query.CommentQueryService; +import com.example.bak.comment.application.query.dto.CommentInfo; +import com.example.bak.comment.presentation.dto.CommentRequest; import com.example.bak.global.common.response.ApiResponse; import com.example.bak.global.common.response.ApiResponseFactory; import com.example.bak.global.common.utils.UriUtils; @@ -22,10 +22,10 @@ @RestController @RequestMapping("/api/v1") @RequiredArgsConstructor -public class FeedCommentController { +public class CommentController { - private final FeedCommentCommandService feedCommentCommandService; - private final FeedCommentQueryService feedCommentQueryService; + private final CommentCommandService commentCommandService; + private final CommentQueryService commentQueryService; @PostMapping("/feeds/{feedId}/comments") public ResponseEntity createComment( @@ -33,7 +33,7 @@ public ResponseEntity createComment( @RequestBody CommentRequest request, @AuthUser Long userId ) { - feedCommentCommandService.createComment( + commentCommandService.createComment( feedId, request.content(), userId @@ -45,7 +45,7 @@ public ResponseEntity createComment( @GetMapping("/feeds/{feedId}/comments") public ResponseEntity getComment(@PathVariable Long feedId) { - List comments = feedCommentQueryService.getComments(feedId); + List comments = commentQueryService.getComments(feedId); ApiResponse response = ApiResponseFactory.success("댓글을 성공적으로 조회하였습니다.", comments); return ResponseEntity.ok(response); } @@ -56,7 +56,7 @@ public ResponseEntity updateComment( @RequestBody CommentRequest request, @AuthUser Long userId ) { - feedCommentCommandService.updateComment(commentId, request.content(), userId); + commentCommandService.updateComment(commentId, request.content(), userId); ApiResponse response = ApiResponseFactory.successVoid("댓글을 성공적으로 수정하였습니다."); return ResponseEntity.ok(response); } diff --git a/src/main/java/com/example/bak/comment/presentation/dto/CommentRequest.java b/src/main/java/com/example/bak/comment/presentation/dto/CommentRequest.java new file mode 100644 index 0000000..d167c55 --- /dev/null +++ b/src/main/java/com/example/bak/comment/presentation/dto/CommentRequest.java @@ -0,0 +1,5 @@ +package com.example.bak.comment.presentation.dto; + +public record CommentRequest(String content) { + +} diff --git a/src/main/java/com/example/bak/feedcomment/application/command/port/FeedCommentCommandPort.java b/src/main/java/com/example/bak/feedcomment/application/command/port/FeedCommentCommandPort.java deleted file mode 100644 index 77765cc..0000000 --- a/src/main/java/com/example/bak/feedcomment/application/command/port/FeedCommentCommandPort.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.example.bak.feedcomment.application.command.port; - -import com.example.bak.feedcomment.domain.FeedComment; -import java.util.Optional; - -public interface FeedCommentCommandPort { - - FeedComment save(FeedComment comment); - - Optional findById(Long commentId); -} - diff --git a/src/main/java/com/example/bak/feedcomment/application/command/port/UserDataPort.java b/src/main/java/com/example/bak/feedcomment/application/command/port/UserDataPort.java deleted file mode 100644 index d14f547..0000000 --- a/src/main/java/com/example/bak/feedcomment/application/command/port/UserDataPort.java +++ /dev/null @@ -1,9 +0,0 @@ -package com.example.bak.feedcomment.application.command.port; - -import com.example.bak.feedcomment.application.command.port.dto.UserSnapShot; -import java.util.Optional; - -public interface UserDataPort { - - Optional findById(Long userId); -} diff --git a/src/main/java/com/example/bak/feedcomment/application/command/port/dto/UserSnapShot.java b/src/main/java/com/example/bak/feedcomment/application/command/port/dto/UserSnapShot.java deleted file mode 100644 index 900bdfd..0000000 --- a/src/main/java/com/example/bak/feedcomment/application/command/port/dto/UserSnapShot.java +++ /dev/null @@ -1,5 +0,0 @@ -package com.example.bak.feedcomment.application.command.port.dto; - -public record UserSnapShot(Long id, String nickname) { - -} diff --git a/src/main/java/com/example/bak/feedcomment/application/query/FeedCommentQueryService.java b/src/main/java/com/example/bak/feedcomment/application/query/FeedCommentQueryService.java deleted file mode 100644 index 6a1ac41..0000000 --- a/src/main/java/com/example/bak/feedcomment/application/query/FeedCommentQueryService.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.example.bak.feedcomment.application.query; - -import com.example.bak.feedcomment.application.query.dto.CommentInfo; -import com.example.bak.feedcomment.application.query.port.FeedCommentQueryPort; -import java.util.List; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -@Service -@RequiredArgsConstructor -@Transactional(readOnly = true) -public class FeedCommentQueryService { - - private final FeedCommentQueryPort feedCommentQueryPort; - - public List getComments(Long feedId) { - return feedCommentQueryPort.findByFeedId(feedId); - } -} \ No newline at end of file diff --git a/src/main/java/com/example/bak/feedcomment/application/query/port/FeedCommentQueryPort.java b/src/main/java/com/example/bak/feedcomment/application/query/port/FeedCommentQueryPort.java deleted file mode 100644 index fab6fe9..0000000 --- a/src/main/java/com/example/bak/feedcomment/application/query/port/FeedCommentQueryPort.java +++ /dev/null @@ -1,10 +0,0 @@ -package com.example.bak.feedcomment.application.query.port; - -import com.example.bak.feedcomment.application.query.dto.CommentInfo; -import java.util.List; - -public interface FeedCommentQueryPort { - - List findByFeedId(Long feedId); -} - diff --git a/src/main/java/com/example/bak/feedcomment/infra/persistence/FeedCommentCommandAdapter.java b/src/main/java/com/example/bak/feedcomment/infra/persistence/FeedCommentCommandAdapter.java deleted file mode 100644 index 7d0f09f..0000000 --- a/src/main/java/com/example/bak/feedcomment/infra/persistence/FeedCommentCommandAdapter.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.example.bak.feedcomment.infra.persistence; - -import com.example.bak.feedcomment.application.command.port.FeedCommentCommandPort; -import com.example.bak.feedcomment.domain.FeedComment; -import java.util.Optional; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Repository; - -@Repository -@RequiredArgsConstructor -public class FeedCommentCommandAdapter implements FeedCommentCommandPort { - - private final FeedCommentJpaRepository feedCommentJpaRepository; - - @Override - public FeedComment save(FeedComment comment) { - return feedCommentJpaRepository.save(comment); - } - - @Override - public Optional findById(Long commentId) { - return feedCommentJpaRepository.findById(commentId); - } -} - diff --git a/src/main/java/com/example/bak/feedcomment/infra/persistence/FeedCommentJpaRepository.java b/src/main/java/com/example/bak/feedcomment/infra/persistence/FeedCommentJpaRepository.java deleted file mode 100644 index 37ecab1..0000000 --- a/src/main/java/com/example/bak/feedcomment/infra/persistence/FeedCommentJpaRepository.java +++ /dev/null @@ -1,10 +0,0 @@ -package com.example.bak.feedcomment.infra.persistence; - -import com.example.bak.feedcomment.domain.FeedComment; -import java.util.List; -import org.springframework.data.jpa.repository.JpaRepository; - -public interface FeedCommentJpaRepository extends JpaRepository { - - List findByFeedId(Long feedId); -} diff --git a/src/main/java/com/example/bak/feedcomment/infra/persistence/FeedCommentQueryAdapter.java b/src/main/java/com/example/bak/feedcomment/infra/persistence/FeedCommentQueryAdapter.java deleted file mode 100644 index d051e74..0000000 --- a/src/main/java/com/example/bak/feedcomment/infra/persistence/FeedCommentQueryAdapter.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.example.bak.feedcomment.infra.persistence; - -import com.example.bak.feedcomment.application.query.dto.CommentInfo; -import com.example.bak.feedcomment.application.query.port.FeedCommentQueryPort; -import java.util.List; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Repository; - -@Repository -@RequiredArgsConstructor -public class FeedCommentQueryAdapter implements FeedCommentQueryPort { - - private final FeedCommentJpaRepository feedCommentJpaRepository; - - @Override - public List findByFeedId(Long feedId) { - return feedCommentJpaRepository.findByFeedId(feedId).stream() - .map(CommentInfo::from) - .toList(); - } -} - diff --git a/src/main/java/com/example/bak/feedcomment/presentation/dto/CommentRequest.java b/src/main/java/com/example/bak/feedcomment/presentation/dto/CommentRequest.java deleted file mode 100644 index a838ae1..0000000 --- a/src/main/java/com/example/bak/feedcomment/presentation/dto/CommentRequest.java +++ /dev/null @@ -1,5 +0,0 @@ -package com.example.bak.feedcomment.presentation.dto; - -public record CommentRequest(String content) { - -} diff --git a/src/main/java/com/example/bak/user/infra/query/UserDataAdapter.java b/src/main/java/com/example/bak/user/infra/query/UserDataAdapter.java index cb538b0..ba01dba 100644 --- a/src/main/java/com/example/bak/user/infra/query/UserDataAdapter.java +++ b/src/main/java/com/example/bak/user/infra/query/UserDataAdapter.java @@ -1,7 +1,7 @@ package com.example.bak.user.infra.query; -import com.example.bak.feedcomment.application.command.port.UserDataPort; -import com.example.bak.feedcomment.application.command.port.dto.UserSnapShot; +import com.example.bak.comment.application.command.port.UserDataPort; +import com.example.bak.comment.application.command.port.dto.UserSnapShot; import com.example.bak.user.domain.UserRepository; import java.util.Optional; import lombok.RequiredArgsConstructor; diff --git a/src/test/java/com/example/bak/feedcomment/application/FeedCommentServiceUnitTest.java b/src/test/java/com/example/bak/comment/application/CommentServiceUnitTest.java similarity index 65% rename from src/test/java/com/example/bak/feedcomment/application/FeedCommentServiceUnitTest.java rename to src/test/java/com/example/bak/comment/application/CommentServiceUnitTest.java index a863351..c539a93 100644 --- a/src/test/java/com/example/bak/feedcomment/application/FeedCommentServiceUnitTest.java +++ b/src/test/java/com/example/bak/comment/application/CommentServiceUnitTest.java @@ -1,17 +1,18 @@ -package com.example.bak.feedcomment.application; +package com.example.bak.comment.application; import static com.example.bak.global.utils.AssertionsErrorCode.assertBusiness; import static org.assertj.core.api.Assertions.assertThat; +import com.example.bak.comment.application.command.CommentCommandService; +import com.example.bak.comment.application.command.port.UserDataPort; +import com.example.bak.comment.application.command.port.dto.UserSnapShot; +import com.example.bak.comment.application.query.CommentQueryService; +import com.example.bak.comment.domain.Comment; +import com.example.bak.comment.domain.CommentRepositoryStub; import com.example.bak.community.domain.Community; import com.example.bak.company.domain.Company; import com.example.bak.feed.domain.Feed; import com.example.bak.feed.domain.FeedRepositoryStub; -import com.example.bak.feedcomment.application.command.FeedCommentCommandService; -import com.example.bak.feedcomment.application.command.port.UserDataPort; -import com.example.bak.feedcomment.application.query.FeedCommentQueryService; -import com.example.bak.feedcomment.domain.FeedComment; -import com.example.bak.feedcomment.domain.FeedCommentRepositoryStub; import com.example.bak.global.exception.ErrorCode; import com.example.bak.user.domain.User; import java.util.List; @@ -23,8 +24,8 @@ import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; -@DisplayName("FeedCommentService 단위 테스트") -class FeedCommentServiceUnitTest { +@DisplayName("CommentService 단위 테스트") +class CommentServiceUnitTest { private static final Long EXISTING_USER_ID = 1L; private static final Long EXISTING_FEED_ID = 1L; @@ -54,9 +55,9 @@ private String nickname() { return testUser.getProfile().getNickname(); } - private List createComments(int count) { + private List createComments(int count) { return IntStream.rangeClosed(1, count) - .mapToObj(i -> FeedComment.testInstance( + .mapToObj(i -> Comment.testInstance( (long) i, testFeed.getId(), COMMENT_CONTENT + i, @@ -68,25 +69,25 @@ private List createComments(int count) { private static class StubUserDataPort implements UserDataPort { - private final ConcurrentHashMap store = new ConcurrentHashMap<>(); + private final ConcurrentHashMap store = new ConcurrentHashMap<>(); void save(User user) { store.put(user.getId(), - new UserSnapshot(user.getId(), user.getProfile().getNickname())); + new UserSnapShot(user.getId(), user.getProfile().getNickname())); } @Override - public Optional findById(Long userId) { + public Optional findById(Long userId) { return Optional.ofNullable(store.get(userId)); } } @Nested - @DisplayName("FeedCommentCommandService") - class FeedCommentCommandServiceTest { + @DisplayName("CommentCommandService") + class CommentCommandServiceTest { - private FeedCommentCommandService feedCommentCommandService; - private FeedCommentRepositoryStub feedCommentRepository; + private CommentCommandService commentCommandService; + private CommentRepositoryStub commentRepository; private StubUserDataPort userDataPort; @BeforeEach @@ -97,10 +98,10 @@ void setUp() { userDataPort = new StubUserDataPort(); userDataPort.save(testUser); - feedCommentRepository = new FeedCommentRepositoryStub(); + commentRepository = new CommentRepositoryStub(); - feedCommentCommandService = new FeedCommentCommandService( - feedCommentRepository, + commentCommandService = new CommentCommandService( + commentRepository, feedRepository, userDataPort ); @@ -109,21 +110,21 @@ void setUp() { @Test @DisplayName("피드 댓글 생성에 성공한다") void createComment_success() { - feedCommentCommandService.createComment(EXISTING_FEED_ID, COMMENT_CONTENT, + commentCommandService.createComment(EXISTING_FEED_ID, COMMENT_CONTENT, EXISTING_USER_ID); - var saved = feedCommentRepository.findAll().getFirst(); + var saved = commentRepository.findAll().getFirst(); assertThat(saved.getAuthorId()).isEqualTo(EXISTING_USER_ID); assertThat(saved.getAuthorNickname()).isEqualTo(nickname()); assertThat(saved.getFeedId()).isEqualTo(EXISTING_FEED_ID); - assertThat(saved.getComment()).isEqualTo(COMMENT_CONTENT); + assertThat(saved.getContent()).isEqualTo(COMMENT_CONTENT); } @Test @DisplayName("존재하지 않는 피드에 예외를 던진다") void createComment_when_feedNotFound() { assertBusiness( - () -> feedCommentCommandService.createComment( + () -> commentCommandService.createComment( NOT_FOUND_FEED_ID, COMMENT_CONTENT, EXISTING_USER_ID @@ -136,7 +137,7 @@ void createComment_when_feedNotFound() { @DisplayName("존재하지 않는 사용자에 예외를 던진다") void createComment_when_userNotFound() { assertBusiness( - () -> feedCommentCommandService.createComment( + () -> commentCommandService.createComment( EXISTING_FEED_ID, COMMENT_CONTENT, NOT_FOUND_USER_ID @@ -148,28 +149,28 @@ void createComment_when_userNotFound() { @Test @DisplayName("피드 댓글 업데이트에 성공한다") void updateComment_success() { - feedCommentRepository.save( - FeedComment.testInstance(1L, testFeed.getId(), COMMENT_CONTENT, + commentRepository.save( + Comment.testInstance(1L, testFeed.getId(), COMMENT_CONTENT, EXISTING_USER_ID, nickname()) ); - feedCommentCommandService.updateComment(1L, UPDATED_CONTENT, EXISTING_USER_ID); + commentCommandService.updateComment(1L, UPDATED_CONTENT, EXISTING_USER_ID); - var updated = feedCommentRepository.findById(1L); + var updated = commentRepository.findById(1L); assertThat(updated).isPresent(); - assertThat(updated.get().getComment()).isEqualTo(UPDATED_CONTENT); + assertThat(updated.get().getContent()).isEqualTo(UPDATED_CONTENT); } @Test @DisplayName("피드 댓글 업데이트 권한이 없을 때 예외를 던진다") void updateComment_when_isNotAuthor() { - feedCommentRepository.save( - FeedComment.testInstance(1L, testFeed.getId(), COMMENT_CONTENT, + commentRepository.save( + Comment.testInstance(1L, testFeed.getId(), COMMENT_CONTENT, EXISTING_USER_ID, nickname()) ); assertBusiness( - () -> feedCommentCommandService.updateComment(1L, UPDATED_CONTENT, + () -> commentCommandService.updateComment(1L, UPDATED_CONTENT, NOT_FOUND_USER_ID), ErrorCode.UNAUTHORIZED_ACTION ); @@ -179,7 +180,7 @@ EXISTING_USER_ID, nickname()) @DisplayName("존재하지 않는 댓글 업데이트 시 예외") void updateComment_when_commentNotFound() { assertBusiness( - () -> feedCommentCommandService.updateComment(1L, UPDATED_CONTENT, + () -> commentCommandService.updateComment(1L, UPDATED_CONTENT, EXISTING_USER_ID), ErrorCode.COMMENT_NOT_FOUND ); @@ -187,24 +188,24 @@ void updateComment_when_commentNotFound() { } @Nested - @DisplayName("FeedCommentQueryService") - class FeedCommentQueryServiceTest { + @DisplayName("CommentQueryService") + class CommentQueryServiceTest { - private FeedCommentQueryService feedCommentQueryService; - private FeedCommentRepositoryStub feedCommentRepository; + private CommentQueryService commentQueryService; + private CommentRepositoryStub commentRepository; @BeforeEach void setUp() { - feedCommentRepository = new FeedCommentRepositoryStub(); - feedCommentQueryService = new FeedCommentQueryService(feedCommentRepository); + commentRepository = new CommentRepositoryStub(); + commentQueryService = new CommentQueryService(commentRepository); } @Test @DisplayName("댓글 목록 조회 성공") void getComments_success() { - createComments(4).forEach(feedCommentRepository::save); + createComments(4).forEach(commentRepository::save); - var comments = feedCommentQueryService.getComments(EXISTING_FEED_ID); + var comments = commentQueryService.getComments(EXISTING_FEED_ID); assertThat(comments).hasSize(4); assertThat(comments.getFirst().authorId()).isEqualTo(EXISTING_USER_ID); diff --git a/src/test/java/com/example/bak/feedcomment/domain/FeedCommentRepositoryStub.java b/src/test/java/com/example/bak/comment/domain/CommentRepositoryStub.java similarity index 50% rename from src/test/java/com/example/bak/feedcomment/domain/FeedCommentRepositoryStub.java rename to src/test/java/com/example/bak/comment/domain/CommentRepositoryStub.java index 8dc8813..7e98bb5 100644 --- a/src/test/java/com/example/bak/feedcomment/domain/FeedCommentRepositoryStub.java +++ b/src/test/java/com/example/bak/comment/domain/CommentRepositoryStub.java @@ -1,20 +1,20 @@ -package com.example.bak.feedcomment.domain; +package com.example.bak.comment.domain; -import com.example.bak.feedcomment.application.command.port.FeedCommentCommandPort; -import com.example.bak.feedcomment.application.query.dto.CommentInfo; -import com.example.bak.feedcomment.application.query.port.FeedCommentQueryPort; +import com.example.bak.comment.application.command.port.CommentCommandPort; +import com.example.bak.comment.application.query.dto.CommentInfo; +import com.example.bak.comment.application.query.port.CommentQueryPort; import com.example.bak.global.support.AbstractStubRepository; import java.util.List; import java.util.Objects; import java.util.Optional; -public class FeedCommentRepositoryStub - extends AbstractStubRepository - implements FeedCommentCommandPort, FeedCommentQueryPort { +public class CommentRepositoryStub + extends AbstractStubRepository + implements CommentCommandPort, CommentQueryPort { @Override - protected Long getId(FeedComment feedComment) { - return feedComment.getId(); + protected Long getId(Comment comment) { + return comment.getId(); } @Override @@ -31,7 +31,7 @@ public List findByFeedId(Long feedId) { } @Override - public Optional findById(Long commentId) { + public Optional findById(Long commentId) { return super.findById(commentId); } } diff --git a/src/test/java/com/example/bak/feed/application/FeedServiceUnitTest.java b/src/test/java/com/example/bak/feed/application/FeedServiceUnitTest.java index 514d6cd..e85b2aa 100644 --- a/src/test/java/com/example/bak/feed/application/FeedServiceUnitTest.java +++ b/src/test/java/com/example/bak/feed/application/FeedServiceUnitTest.java @@ -5,11 +5,11 @@ import com.example.bak.community.application.query.dto.CommunityResult; import com.example.bak.feed.application.command.FeedCommandService; +import com.example.bak.feed.application.command.dto.FeedResult; import com.example.bak.feed.application.command.port.CommunityValidationPort; import com.example.bak.feed.application.command.port.FeedCommandPort; import com.example.bak.feed.application.query.FeedQueryService; import com.example.bak.feed.application.query.dto.FeedDetail; -import com.example.bak.feed.application.query.dto.FeedResult; import com.example.bak.feed.application.query.dto.FeedSummary; import com.example.bak.feed.application.query.port.FeedQueryPort; import com.example.bak.feed.domain.Feed; From af6d726305fc859a8b331c138d3edd017a561581 Mon Sep 17 00:00:00 2001 From: jbh010204 Date: Tue, 9 Dec 2025 18:50:23 +0900 Subject: [PATCH 16/29] =?UTF-8?q?feat:=20Feed=20=EC=88=98=EC=A0=95&?= =?UTF-8?q?=EC=82=AD=EC=A0=9C=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../command/FeedCommandService.java | 25 +++++++++++++++++++ .../command/port/FeedCommandPort.java | 2 ++ .../com/example/bak/feed/domain/Feed.java | 5 ++++ .../infra/command/FeedCommandAdapter.java | 5 ++++ .../bak/feed/presentation/FeedController.java | 24 ++++++++++++++++++ .../presentation/dto/FeedUpdateRequest.java | 8 ++++++ 6 files changed, 69 insertions(+) create mode 100644 src/main/java/com/example/bak/feed/presentation/dto/FeedUpdateRequest.java diff --git a/src/main/java/com/example/bak/feed/application/command/FeedCommandService.java b/src/main/java/com/example/bak/feed/application/command/FeedCommandService.java index 786680a..e685a2a 100644 --- a/src/main/java/com/example/bak/feed/application/command/FeedCommandService.java +++ b/src/main/java/com/example/bak/feed/application/command/FeedCommandService.java @@ -28,4 +28,29 @@ public FeedResult createFeed(String title, String content, Long communityId, Lon return FeedResult.of(savedFeed.getId()); } + + public void updateFeed(Long feedId, String title, String content, Long userId) { + Feed feed = feedCommandPort.findById(feedId) + .orElseThrow(() -> new BusinessException(ErrorCode.FEED_NOT_FOUND)); + + validateAuthor(feed, userId); + + feed.update(title, content); + feedCommandPort.save(feed); + } + + public void deleteFeed(Long feedId, Long userId) { + Feed feed = feedCommandPort.findById(feedId) + .orElseThrow(() -> new BusinessException(ErrorCode.FEED_NOT_FOUND)); + + validateAuthor(feed, userId); + + feedCommandPort.delete(feed); + } + + private void validateAuthor(Feed feed, Long userId) { + if (!feed.getAuthorId().equals(userId)) { + throw new BusinessException(ErrorCode.UNAUTHORIZED_ACTION); + } + } } diff --git a/src/main/java/com/example/bak/feed/application/command/port/FeedCommandPort.java b/src/main/java/com/example/bak/feed/application/command/port/FeedCommandPort.java index a1b2cec..ff4ff23 100644 --- a/src/main/java/com/example/bak/feed/application/command/port/FeedCommandPort.java +++ b/src/main/java/com/example/bak/feed/application/command/port/FeedCommandPort.java @@ -8,4 +8,6 @@ public interface FeedCommandPort { Feed save(Feed feed); Optional findById(Long id); + + void delete(Feed feed); } diff --git a/src/main/java/com/example/bak/feed/domain/Feed.java b/src/main/java/com/example/bak/feed/domain/Feed.java index c89cc60..aaef222 100644 --- a/src/main/java/com/example/bak/feed/domain/Feed.java +++ b/src/main/java/com/example/bak/feed/domain/Feed.java @@ -63,4 +63,9 @@ public static Feed testInstance( ) { return new Feed(id, title, content, communityId, userId); } + + public void update(String title, String content) { + this.title = title; + this.content = content; + } } diff --git a/src/main/java/com/example/bak/feed/infra/command/FeedCommandAdapter.java b/src/main/java/com/example/bak/feed/infra/command/FeedCommandAdapter.java index 95c5ff0..e66e639 100644 --- a/src/main/java/com/example/bak/feed/infra/command/FeedCommandAdapter.java +++ b/src/main/java/com/example/bak/feed/infra/command/FeedCommandAdapter.java @@ -21,4 +21,9 @@ public Feed save(Feed feed) { public Optional findById(Long id) { return feedJpaRepository.findById(id); } + + @Override + public void delete(Feed feed) { + feedJpaRepository.delete(feed); + } } diff --git a/src/main/java/com/example/bak/feed/presentation/FeedController.java b/src/main/java/com/example/bak/feed/presentation/FeedController.java index d547dd6..ba31371 100644 --- a/src/main/java/com/example/bak/feed/presentation/FeedController.java +++ b/src/main/java/com/example/bak/feed/presentation/FeedController.java @@ -6,6 +6,7 @@ import com.example.bak.feed.application.query.dto.FeedDetail; import com.example.bak.feed.application.query.dto.FeedSummary; import com.example.bak.feed.presentation.dto.FeedRequest; +import com.example.bak.feed.presentation.dto.FeedUpdateRequest; import com.example.bak.global.common.response.ApiResponse; import com.example.bak.global.common.response.ApiResponseFactory; import com.example.bak.global.common.utils.UriUtils; @@ -13,9 +14,11 @@ import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; @@ -66,4 +69,25 @@ public ResponseEntity getFeedDetail(@PathVariable Long feedId) { ApiResponse response = ApiResponseFactory.success("피드 상세를 성공적으로 조회하였습니다.", detail); return ResponseEntity.ok(response); } + + @PutMapping("/{feedId}") + public ResponseEntity updateFeed( + @PathVariable Long feedId, + @AuthUser Long userId, + @RequestBody FeedUpdateRequest request + ) { + feedCommandService.updateFeed(feedId, request.title(), request.content(), userId); + ApiResponse response = ApiResponseFactory.successVoid("피드를 성공적으로 수정하였습니다."); + return ResponseEntity.ok(response); + } + + @DeleteMapping("/{feedId}") + public ResponseEntity deleteFeed( + @PathVariable Long feedId, + @AuthUser Long userId + ) { + feedCommandService.deleteFeed(feedId, userId); + ApiResponse response = ApiResponseFactory.successVoid("피드를 성공적으로 삭제하였습니다."); + return ResponseEntity.ok(response); + } } diff --git a/src/main/java/com/example/bak/feed/presentation/dto/FeedUpdateRequest.java b/src/main/java/com/example/bak/feed/presentation/dto/FeedUpdateRequest.java new file mode 100644 index 0000000..97e89de --- /dev/null +++ b/src/main/java/com/example/bak/feed/presentation/dto/FeedUpdateRequest.java @@ -0,0 +1,8 @@ +package com.example.bak.feed.presentation.dto; + +public record FeedUpdateRequest( + String title, + String content +) { + +} From 0a43147eecd88ac3ef2ec4e006ad1194348e3f57 Mon Sep 17 00:00:00 2001 From: jbh010204 Date: Tue, 9 Dec 2025 18:50:50 +0900 Subject: [PATCH 17/29] =?UTF-8?q?feat:=20Feed=20=ED=86=B5=ED=95=A9=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EA=B5=AC=ED=98=84=20=EB=B0=8F=20?= =?UTF-8?q?=EB=8B=A8=EC=9C=84=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20command?= =?UTF-8?q?=EC=9A=A9=EC=9C=BC=EB=A1=9C=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../feed/application/FeedServiceUnitTest.java | 202 +++++------------- .../bak/feed/domain/FeedRepositoryStub.java | 29 +++ .../FeedJdbcRepositoryIntegrationTest.java | 73 +++++++ .../bak/global/JdbcRepositoryTestConfig.java | 7 + src/test/resources/sql/feed/data.sql | 33 +++ 5 files changed, 190 insertions(+), 154 deletions(-) create mode 100644 src/test/java/com/example/bak/feed/infra/query/FeedJdbcRepositoryIntegrationTest.java create mode 100644 src/test/resources/sql/feed/data.sql diff --git a/src/test/java/com/example/bak/feed/application/FeedServiceUnitTest.java b/src/test/java/com/example/bak/feed/application/FeedServiceUnitTest.java index e85b2aa..bb2069f 100644 --- a/src/test/java/com/example/bak/feed/application/FeedServiceUnitTest.java +++ b/src/test/java/com/example/bak/feed/application/FeedServiceUnitTest.java @@ -3,33 +3,18 @@ import static com.example.bak.global.utils.AssertionsErrorCode.assertBusiness; import static org.assertj.core.api.Assertions.assertThat; -import com.example.bak.community.application.query.dto.CommunityResult; import com.example.bak.feed.application.command.FeedCommandService; import com.example.bak.feed.application.command.dto.FeedResult; import com.example.bak.feed.application.command.port.CommunityValidationPort; -import com.example.bak.feed.application.command.port.FeedCommandPort; -import com.example.bak.feed.application.query.FeedQueryService; -import com.example.bak.feed.application.query.dto.FeedDetail; -import com.example.bak.feed.application.query.dto.FeedSummary; -import com.example.bak.feed.application.query.port.FeedQueryPort; import com.example.bak.feed.domain.Feed; +import com.example.bak.feed.domain.FeedRepositoryStub; import com.example.bak.global.exception.ErrorCode; -import com.example.bak.user.application.dto.UserInfo; -import java.util.ArrayList; -import java.util.HashMap; import java.util.HashSet; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.Optional; import java.util.Set; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.PageImpl; -import org.springframework.data.domain.Pageable; @DisplayName("FeedService 단위 테스트") class FeedServiceUnitTest { @@ -40,66 +25,6 @@ class FeedServiceUnitTest { private static final String TITLE = "title"; private static final String CONTENT = "content"; - private static final String NICKNAME = "nick"; - private static final String COMMUNITY_NAME = "community"; - private static final String JOB_GROUP = "jobGroup"; - - private FeedSummary summaryOf(Long id, int commentCount) { - return new FeedSummary( - id, - TITLE + id, - authorInfo(), - communityDetail(), - commentCount - ); - } - - private FeedDetail detailOf(Long id) { - return new FeedDetail( - id, - TITLE + id, - CONTENT + id, - authorInfo(), - communityDetail() - ); - } - - private UserInfo authorInfo() { - return new UserInfo(EXISTING_USER_ID, NICKNAME); - } - - private CommunityResult.Detail communityDetail() { - return new CommunityResult.Detail(EXISTING_COMMUNITY_ID, COMMUNITY_NAME, JOB_GROUP); - } - - private static class InMemoryFeedCommandPort implements FeedCommandPort { - - private final Map store = new HashMap<>(); - private long sequence = 1L; - - @Override - public Feed save(Feed feed) { - Long id = feed.getId(); - if (id == null) { - id = sequence++; - } - - Feed persisted = Feed.testInstance( - id, - feed.getTitle(), - feed.getContent(), - feed.getCommunityId(), - feed.getAuthorId() - ); - store.put(id, persisted); - return persisted; - } - - public Optional findById(Long id) { - return Optional.ofNullable(store.get(id)); - } - } - private static class StubCommunityValidationPort implements CommunityValidationPort { private final Set existingCommunityIds = new HashSet<>(); @@ -118,53 +43,20 @@ public boolean isCommunityExists(Long communityId) { } } - private static class StubFeedQueryPort implements FeedQueryPort { - - private final Map summaries = new LinkedHashMap<>(); - private final Map details = new LinkedHashMap<>(); - - void save(FeedSummary summary, FeedDetail detail) { - summaries.put(summary.id(), summary); - details.put(detail.id(), detail); - } - - @Override - public Page findAll(Pageable pageable) { - List list = new ArrayList<>(summaries.values()); - int start = (int) pageable.getOffset(); - if (start >= list.size()) { - return new PageImpl<>(List.of(), pageable, list.size()); - } - - int end = Math.min(start + pageable.getPageSize(), list.size()); - return new PageImpl<>(list.subList(start, end), pageable, list.size()); - } - - @Override - public Optional findSummaryById(Long feedId) { - return Optional.ofNullable(summaries.get(feedId)); - } - - @Override - public Optional findDetailById(Long feedId) { - return Optional.ofNullable(details.get(feedId)); - } - } - @Nested @DisplayName("FeedCommandService") class FeedCommandServiceTest { private FeedCommandService feedCommandService; - private InMemoryFeedCommandPort feedCommandPort; + private FeedRepositoryStub feedRepository; private StubCommunityValidationPort communityValidationPort; @BeforeEach void setUp() { - feedCommandPort = new InMemoryFeedCommandPort(); + feedRepository = new FeedRepositoryStub(); communityValidationPort = new StubCommunityValidationPort(); communityValidationPort.registerCommunity(EXISTING_COMMUNITY_ID); - feedCommandService = new FeedCommandService(feedCommandPort, communityValidationPort); + feedCommandService = new FeedCommandService(feedRepository, communityValidationPort); } @Test @@ -178,7 +70,7 @@ void createFeed_success() { ); assertThat(result).isNotNull(); - Feed saved = feedCommandPort.findById(result.id()).orElse(null); + Feed saved = feedRepository.findById(result.id()).orElse(null); assertThat(saved).isNotNull(); assertThat(saved.getTitle()).isEqualTo(TITLE); assertThat(saved.getContent()).isEqualTo(CONTENT); @@ -201,73 +93,75 @@ void createFeed_when_communityNotFound() { ErrorCode.COMMUNITY_NOT_FOUND ); } - } - @Nested - @DisplayName("FeedQueryService") - class FeedQueryServiceTest { + @Test + @DisplayName("피드를 수정한다") + void updateFeed_success() { + feedRepository.save(Feed.testInstance(1L, TITLE, CONTENT, EXISTING_COMMUNITY_ID, + EXISTING_USER_ID)); - private FeedQueryService feedQueryService; - private StubFeedQueryPort feedQueryPort; + feedCommandService.updateFeed(1L, "updatedTitle", "updatedContent", + EXISTING_USER_ID); - @BeforeEach - void setUp() { - feedQueryPort = new StubFeedQueryPort(); - feedQueryService = new FeedQueryService(feedQueryPort); + Feed updated = feedRepository.findById(1L).orElseThrow(); + assertThat(updated.getTitle()).isEqualTo("updatedTitle"); + assertThat(updated.getContent()).isEqualTo("updatedContent"); } @Test - @DisplayName("피드 상세 조회 성공") - void getFeedDetail_success() { - feedQueryPort.save(summaryOf(1L, 0), detailOf(1L)); + @DisplayName("피드 수정 시 작성자가 아니면 예외") + void updateFeed_when_notAuthor() { + feedRepository.save(Feed.testInstance(1L, TITLE, CONTENT, EXISTING_COMMUNITY_ID, + EXISTING_USER_ID)); - FeedDetail detail = feedQueryService.getFeedDetail(1L); - - assertThat(detail.id()).isEqualTo(1L); - assertThat(detail.title()).isEqualTo(TITLE + 1); + assertBusiness( + () -> feedCommandService.updateFeed(1L, "updatedTitle", "updatedContent", + 999L), + ErrorCode.UNAUTHORIZED_ACTION + ); } @Test - @DisplayName("피드 상세 조회 시 예외") - void getFeedDetail_when_notFound() { + @DisplayName("존재하지 않는 피드 수정 시 예외") + void updateFeed_when_notFound() { assertBusiness( - () -> feedQueryService.getFeedDetail(1L), + () -> feedCommandService.updateFeed(1L, "title", "content", + EXISTING_USER_ID), ErrorCode.FEED_NOT_FOUND ); } @Test - @DisplayName("피드 요약 조회 성공") - void getFeedSummary_success() { - feedQueryPort.save(summaryOf(2L, 3), detailOf(2L)); + @DisplayName("피드를 삭제한다") + void deleteFeed_success() { + feedRepository.save(Feed.testInstance(1L, TITLE, CONTENT, EXISTING_COMMUNITY_ID, + EXISTING_USER_ID)); - FeedSummary summary = feedQueryService.getFeedSummary(2L); + feedCommandService.deleteFeed(1L, EXISTING_USER_ID); - assertThat(summary.id()).isEqualTo(2L); - assertThat(summary.commentCount()).isEqualTo(3); + assertThat(feedRepository.findById(1L)).isEmpty(); } @Test - @DisplayName("피드 요약 조회 시 예외") - void getFeedSummary_when_notFound() { + @DisplayName("피드 삭제 시 작성자가 아니면 예외") + void deleteFeed_when_notAuthor() { + feedRepository.save(Feed.testInstance(1L, TITLE, CONTENT, EXISTING_COMMUNITY_ID, + EXISTING_USER_ID)); + assertBusiness( - () -> feedQueryService.getFeedSummary(2L), - ErrorCode.FEED_NOT_FOUND + () -> feedCommandService.deleteFeed(1L, 999L), + ErrorCode.UNAUTHORIZED_ACTION ); } @Test - @DisplayName("피드 목록 페이징 조회") - void getFeeds_success() { - for (long i = 1; i <= 5; i++) { - feedQueryPort.save(summaryOf(i, (int) i), detailOf(i)); - } - - List feeds = feedQueryService.getFeeds(0, 3); - - assertThat(feeds).hasSize(3); - assertThat(feeds.get(0).id()).isEqualTo(1L); + @DisplayName("존재하지 않는 피드 삭제 시 예외") + void deleteFeed_when_notFound() { + assertBusiness( + () -> feedCommandService.deleteFeed(1L, EXISTING_USER_ID), + ErrorCode.FEED_NOT_FOUND + ); } } -} +} diff --git a/src/test/java/com/example/bak/feed/domain/FeedRepositoryStub.java b/src/test/java/com/example/bak/feed/domain/FeedRepositoryStub.java index 05bb110..40cc163 100644 --- a/src/test/java/com/example/bak/feed/domain/FeedRepositoryStub.java +++ b/src/test/java/com/example/bak/feed/domain/FeedRepositoryStub.java @@ -2,7 +2,11 @@ import com.example.bak.feed.application.command.port.FeedCommandPort; import com.example.bak.global.support.AbstractStubRepository; +import java.util.List; import java.util.Objects; +import java.util.Optional; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; public class FeedRepositoryStub extends AbstractStubRepository @@ -17,4 +21,29 @@ protected Long getId(Feed feed) { protected boolean isSame(Long left, Long right) { return Objects.equals(left, right); } + + @Override + public List findAll() { + return super.findAll(); + } + + @Override + public Page findAll(Pageable pageable) { + return super.findAll(pageable); + } + + @Override + public Feed save(Feed feed) { + return super.save(feed); + } + + @Override + public Optional findById(Long id) { + return super.findById(id); + } + + @Override + public void delete(Feed feed) { + store.removeIf(it -> isSame(getId(it), getId(feed))); + } } diff --git a/src/test/java/com/example/bak/feed/infra/query/FeedJdbcRepositoryIntegrationTest.java b/src/test/java/com/example/bak/feed/infra/query/FeedJdbcRepositoryIntegrationTest.java new file mode 100644 index 0000000..3c19c58 --- /dev/null +++ b/src/test/java/com/example/bak/feed/infra/query/FeedJdbcRepositoryIntegrationTest.java @@ -0,0 +1,73 @@ +package com.example.bak.feed.infra.query; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.example.bak.feed.application.query.dto.FeedDetail; +import com.example.bak.feed.application.query.dto.FeedSummary; +import com.example.bak.feed.infra.query.jdbc.FeedJdbcRepository; +import com.example.bak.global.AbstractMySqlContainerTest; +import java.util.List; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.jdbc.JdbcTest; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.test.context.jdbc.Sql; + +@JdbcTest +@DisplayName("FeedJdbcRepository 통합 테스트") +@Sql(scripts = "/sql/feed/data.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD) +class FeedJdbcRepositoryIntegrationTest extends AbstractMySqlContainerTest { + + @Autowired + private FeedJdbcRepository feedJdbcRepository; + + @Nested + @DisplayName("findDetailById") + class FindDetailById { + + @Test + @DisplayName("피드 상세 정보를 조회한다") + void success() { + FeedDetail detail = feedJdbcRepository.findDetailById(1L).orElseThrow(); + + assertThat(detail.id()).isEqualTo(1L); + assertThat(detail.title()).isEqualTo("title1"); + assertThat(detail.author().nickname()).isEqualTo("nick1"); + assertThat(detail.community().name()).isEqualTo("backend"); + } + } + + @Nested + @DisplayName("findSummaryById") + class FindSummaryById { + + @Test + @DisplayName("피드 요약 정보를 조회하고 댓글 수를 포함한다") + void success() { + FeedSummary summary = feedJdbcRepository.findSummaryById(1L).orElseThrow(); + + assertThat(summary.id()).isEqualTo(1L); + assertThat(summary.commentCount()).isEqualTo(2); + assertThat(summary.author().nickname()).isEqualTo("nick1"); + } + } + + @Nested + @DisplayName("findAll") + class FindAll { + + @Test + @DisplayName("페이지네이션으로 피드 목록을 조회한다") + void success() { + Page page = feedJdbcRepository.findAll(PageRequest.of(0, 2)); + + assertThat(page.getTotalElements()).isEqualTo(3); + List content = page.getContent(); + assertThat(content).hasSize(2); + assertThat(content.getFirst().id()).isEqualTo(3L); // DESC order + } + } +} diff --git a/src/test/java/com/example/bak/global/JdbcRepositoryTestConfig.java b/src/test/java/com/example/bak/global/JdbcRepositoryTestConfig.java index 5a4c645..42918cd 100644 --- a/src/test/java/com/example/bak/global/JdbcRepositoryTestConfig.java +++ b/src/test/java/com/example/bak/global/JdbcRepositoryTestConfig.java @@ -1,5 +1,7 @@ package com.example.bak.global; +import com.example.bak.feed.infra.query.jdbc.FeedJdbcRepository; +import com.example.bak.feed.infra.query.jdbc.FeedJdbcRepositoryImpl; import com.example.bak.privatemessage.infra.query.jdbc.MessageJdbcRepository; import com.example.bak.privatemessage.infra.query.jdbc.MessageJdbcRepositoryImpl; import org.springframework.boot.test.context.TestConfiguration; @@ -13,4 +15,9 @@ public class JdbcRepositoryTestConfig { public MessageJdbcRepository messageJdbcRepository(NamedParameterJdbcTemplate jdbc) { return new MessageJdbcRepositoryImpl(jdbc); } + + @Bean + public FeedJdbcRepository feedJdbcRepository(NamedParameterJdbcTemplate jdbc) { + return new FeedJdbcRepositoryImpl(jdbc); + } } diff --git a/src/test/resources/sql/feed/data.sql b/src/test/resources/sql/feed/data.sql new file mode 100644 index 0000000..88a7d8f --- /dev/null +++ b/src/test/resources/sql/feed/data.sql @@ -0,0 +1,33 @@ +DELETE FROM feed_comments; +DELETE FROM feeds; +DELETE FROM communities; +DELETE FROM profiles; +DELETE FROM users; +DELETE FROM companies; + +INSERT INTO companies (id, name, career_link, logo_url, description) +VALUES (1, 'company', 'company.com', 'logo.png', 'desc'); + +INSERT INTO users (id, email, password) VALUES + (1, 'author1@test.com', 'pw'), + (2, 'author2@test.com', 'pw'), + (3, 'author3@test.com', 'pw'); + +INSERT INTO profiles (id, name, nickname, user_id) VALUES + (1, 'user1', 'nick1', 1), + (2, 'user2', 'nick2', 2), + (3, 'user3', 'nick3', 3); + +INSERT INTO communities (id, name, job_group, company_id) VALUES + (1, 'backend', 'dev', 1), + (2, 'frontend', 'dev', 1); + +INSERT INTO feeds (id, title, content, community_id, author_id) VALUES + (1, 'title1', 'content1', 1, 1), + (2, 'title2', 'content2', 1, 2), + (3, 'title3', 'content3', 2, 3); + +INSERT INTO feed_comments (id, comment, author_id, feed_id) VALUES + (1, 'c1', 2, 1), + (2, 'c2', 3, 1), + (3, 'c3', 1, 2); From fb1caef2ba12c79bb2833a265fc107d41eff0f06 Mon Sep 17 00:00:00 2001 From: jbh010204 Date: Tue, 9 Dec 2025 21:58:45 +0900 Subject: [PATCH 18/29] =?UTF-8?q?docs:=20Feed=20=EB=B0=8F=20Comment=20Swag?= =?UTF-8?q?geer=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../presentation/CommentController.java | 8 +- .../swagger/FeedCommentSwagger.java | 130 ++++++++-- .../bak/feed/presentation/FeedController.java | 6 +- .../presentation/swagger/FeedSwagger.java | 233 ++++++++++++++---- 4 files changed, 305 insertions(+), 72 deletions(-) diff --git a/src/main/java/com/example/bak/comment/presentation/CommentController.java b/src/main/java/com/example/bak/comment/presentation/CommentController.java index 635af2e..77d3b4e 100644 --- a/src/main/java/com/example/bak/comment/presentation/CommentController.java +++ b/src/main/java/com/example/bak/comment/presentation/CommentController.java @@ -4,6 +4,7 @@ import com.example.bak.comment.application.query.CommentQueryService; import com.example.bak.comment.application.query.dto.CommentInfo; import com.example.bak.comment.presentation.dto.CommentRequest; +import com.example.bak.comment.presentation.swagger.FeedCommentSwagger; import com.example.bak.global.common.response.ApiResponse; import com.example.bak.global.common.response.ApiResponseFactory; import com.example.bak.global.common.utils.UriUtils; @@ -22,11 +23,12 @@ @RestController @RequestMapping("/api/v1") @RequiredArgsConstructor -public class CommentController { +public class CommentController implements FeedCommentSwagger { private final CommentCommandService commentCommandService; private final CommentQueryService commentQueryService; + @Override @PostMapping("/feeds/{feedId}/comments") public ResponseEntity createComment( @PathVariable Long feedId, @@ -43,13 +45,15 @@ public ResponseEntity createComment( .body(response); } + @Override @GetMapping("/feeds/{feedId}/comments") - public ResponseEntity getComment(@PathVariable Long feedId) { + public ResponseEntity getComments(@PathVariable Long feedId) { List comments = commentQueryService.getComments(feedId); ApiResponse response = ApiResponseFactory.success("댓글을 성공적으로 조회하였습니다.", comments); return ResponseEntity.ok(response); } + @Override @PutMapping("/comments/{commentId}") public ResponseEntity updateComment( @PathVariable Long commentId, diff --git a/src/main/java/com/example/bak/comment/presentation/swagger/FeedCommentSwagger.java b/src/main/java/com/example/bak/comment/presentation/swagger/FeedCommentSwagger.java index 975c515..a13ff65 100644 --- a/src/main/java/com/example/bak/comment/presentation/swagger/FeedCommentSwagger.java +++ b/src/main/java/com/example/bak/comment/presentation/swagger/FeedCommentSwagger.java @@ -18,7 +18,7 @@ public interface FeedCommentSwagger { @Operation( summary = "댓글 생성", - description = "특정 피드에 새로운 댓글을 작성합니다." + description = "특정 피드에 인증된 사용자가 새로운 댓글을 작성합니다." ) @ApiResponses(value = { @io.swagger.v3.oas.annotations.responses.ApiResponse( @@ -28,6 +28,8 @@ public interface FeedCommentSwagger { mediaType = "application/json", schema = @Schema(implementation = ApiResponse.class), examples = @ExampleObject( + name = "CommentCreateSuccess", + summary = "댓글 생성 성공", value = """ { "status": "SUCCESS", @@ -38,16 +40,54 @@ public interface FeedCommentSwagger { ) ) ), + @io.swagger.v3.oas.annotations.responses.ApiResponse( + responseCode = "400", + description = "잘못된 요청 - 댓글 본문 누락", + content = @Content( + mediaType = "application/json", + examples = @ExampleObject( + name = "CommentCreateBadRequest", + summary = "댓글 내용 없음", + value = """ + { + "status": "ERROR", + "message": "댓글 내용은 필수입니다.", + "data": null + } + """ + ) + ) + ), + @io.swagger.v3.oas.annotations.responses.ApiResponse( + responseCode = "401", + description = "인증 실패", + content = @Content( + mediaType = "application/json", + examples = @ExampleObject( + name = "CommentCreateUnauthorized", + summary = "토큰 누락", + value = """ + { + "status": "ERROR", + "message": "토큰이 없습니다.", + "data": null + } + """ + ) + ) + ), @io.swagger.v3.oas.annotations.responses.ApiResponse( responseCode = "404", description = "피드를 찾을 수 없음", content = @Content( mediaType = "application/json", examples = @ExampleObject( + name = "CommentCreateFeedNotFound", + summary = "댓글 대상 피드 없음", value = """ { "status": "ERROR", - "message": "피드를 찾을 수 없습니다.", + "message": "피드 리소스를 찾을 수 없습니다.", "data": null } """ @@ -64,21 +104,24 @@ ResponseEntity createComment( content = @Content( schema = @Schema(implementation = CommentRequest.class), examples = @ExampleObject( + name = "CommentCreateRequest", + summary = "댓글 생성 요청", value = """ { - "content": "좋은 정보 감사합니다!", - "userId": 1 + "content": "좋은 정보 감사합니다!" } """ ) ) ) - @RequestBody CommentRequest request + @RequestBody CommentRequest request, + @Parameter(hidden = true, description = "인증된 사용자 ID", required = true) + Long userId ); @Operation( summary = "댓글 목록 조회", - description = "특정 피드의 모든 댓글을 조회합니다." + description = "특정 피드에 작성된 모든 댓글을 최신순으로 조회합니다." ) @ApiResponses(value = { @io.swagger.v3.oas.annotations.responses.ApiResponse( @@ -88,18 +131,18 @@ ResponseEntity createComment( mediaType = "application/json", schema = @Schema(implementation = ApiResponse.class), examples = @ExampleObject( + name = "CommentList", + summary = "댓글 목록 응답", value = """ { "status": "SUCCESS", "message": "댓글을 성공적으로 조회하였습니다.", "data": [ { - "commentId": 1, - "content": "좋은 정보 감사합니다!", - "authorId": 1, - "authorName": "홍길동", - "createdAt": "2024-01-15T11:00:00", - "updatedAt": "2024-01-15T11:00:00" + "id": 1, + "authorId": 5, + "authorName": "infra-cat", + "content": "좋은 정보 감사합니다!" } ] } @@ -113,10 +156,12 @@ ResponseEntity createComment( content = @Content( mediaType = "application/json", examples = @ExampleObject( + name = "CommentListFeedNotFound", + summary = "피드 미존재", value = """ { "status": "ERROR", - "message": "피드를 찾을 수 없습니다.", + "message": "피드 리소스를 찾을 수 없습니다.", "data": null } """ @@ -124,14 +169,14 @@ ResponseEntity createComment( ) ) }) - ResponseEntity getComment( + ResponseEntity getComments( @Parameter(description = "피드 ID", required = true, example = "1") @PathVariable Long feedId ); @Operation( summary = "댓글 수정", - description = "특정 댓글의 내용을 수정합니다." + description = "댓글 작성자가 본인의 댓글 본문을 수정합니다." ) @ApiResponses(value = { @io.swagger.v3.oas.annotations.responses.ApiResponse( @@ -141,6 +186,8 @@ ResponseEntity getComment( mediaType = "application/json", schema = @Schema(implementation = ApiResponse.class), examples = @ExampleObject( + name = "CommentUpdateSuccess", + summary = "수정 성공", value = """ { "status": "SUCCESS", @@ -151,12 +198,50 @@ ResponseEntity getComment( ) ) ), + @io.swagger.v3.oas.annotations.responses.ApiResponse( + responseCode = "400", + description = "잘못된 요청 - 본문 누락", + content = @Content( + mediaType = "application/json", + examples = @ExampleObject( + name = "CommentUpdateBadRequest", + summary = "본문 없음", + value = """ + { + "status": "ERROR", + "message": "댓글 내용은 필수입니다.", + "data": null + } + """ + ) + ) + ), + @io.swagger.v3.oas.annotations.responses.ApiResponse( + responseCode = "401", + description = "인증 실패", + content = @Content( + mediaType = "application/json", + examples = @ExampleObject( + name = "CommentUpdateUnauthorized", + summary = "토큰 없음", + value = """ + { + "status": "ERROR", + "message": "토큰이 없습니다.", + "data": null + } + """ + ) + ) + ), @io.swagger.v3.oas.annotations.responses.ApiResponse( responseCode = "403", - description = "권한 없음 - 댓글 작성자만 수정 가능", + description = "권한 없음 - 작성자 불일치", content = @Content( mediaType = "application/json", examples = @ExampleObject( + name = "CommentUpdateForbidden", + summary = "다른 사용자가 수정 시도", value = """ { "status": "ERROR", @@ -173,10 +258,12 @@ ResponseEntity getComment( content = @Content( mediaType = "application/json", examples = @ExampleObject( + name = "CommentUpdateNotFound", + summary = "댓글 미존재", value = """ { "status": "ERROR", - "message": "댓글을 찾을 수 없습니다.", + "message": "댓글 리소스를 찾을 수 없습니다.", "data": null } """ @@ -193,15 +280,18 @@ ResponseEntity updateComment( content = @Content( schema = @Schema(implementation = CommentRequest.class), examples = @ExampleObject( + name = "CommentUpdateRequest", + summary = "댓글 수정 요청", value = """ { - "content": "수정된 댓글 내용입니다.", - "userId": 1 + "content": "수정된 댓글 내용입니다." } """ ) ) ) - @RequestBody CommentRequest request + @RequestBody CommentRequest request, + @Parameter(hidden = true, description = "인증된 사용자 ID", required = true) + Long userId ); } diff --git a/src/main/java/com/example/bak/feed/presentation/FeedController.java b/src/main/java/com/example/bak/feed/presentation/FeedController.java index d1206ff..4ba411d 100644 --- a/src/main/java/com/example/bak/feed/presentation/FeedController.java +++ b/src/main/java/com/example/bak/feed/presentation/FeedController.java @@ -73,8 +73,8 @@ public ResponseEntity getFeedDetail(@PathVariable Long feedId) { @PutMapping("/{feedId}") public ResponseEntity updateFeed( - @PathVariable Long feedId, @AuthUser Long userId, + @PathVariable Long feedId, @RequestBody FeedUpdateRequest request ) { feedCommandService.updateFeed(feedId, request.title(), request.content(), userId); @@ -84,8 +84,8 @@ public ResponseEntity updateFeed( @DeleteMapping("/{feedId}") public ResponseEntity deleteFeed( - @PathVariable Long feedId, - @AuthUser Long userId + @AuthUser Long userId, + @PathVariable Long feedId ) { feedCommandService.deleteFeed(feedId, userId); ApiResponse response = ApiResponseFactory.successVoid("피드를 성공적으로 삭제하였습니다."); diff --git a/src/main/java/com/example/bak/feed/presentation/swagger/FeedSwagger.java b/src/main/java/com/example/bak/feed/presentation/swagger/FeedSwagger.java index 94aaf9c..9ea7e63 100644 --- a/src/main/java/com/example/bak/feed/presentation/swagger/FeedSwagger.java +++ b/src/main/java/com/example/bak/feed/presentation/swagger/FeedSwagger.java @@ -5,6 +5,7 @@ import com.example.bak.global.common.response.ApiResponse; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.headers.Header; import io.swagger.v3.oas.annotations.media.Content; import io.swagger.v3.oas.annotations.media.ExampleObject; import io.swagger.v3.oas.annotations.media.Schema; @@ -20,16 +21,19 @@ public interface FeedSwagger { @Operation( summary = "피드 생성", - description = "새로운 피드를 생성합니다. 제목, 내용, 커뮤니티 ID, 사용자 ID를 입력받습니다." + description = "인증된 사용자가 제목, 내용, 커뮤니티 정보를 입력하여 새 피드를 생성합니다." ) @ApiResponses(value = { @io.swagger.v3.oas.annotations.responses.ApiResponse( responseCode = "201", description = "피드 생성 성공", + headers = @Header(name = "Location", description = "생성된 피드 상세 조회 URI (/api/v1/feeds/{feedId})"), content = @Content( mediaType = "application/json", schema = @Schema(implementation = ApiResponse.class), examples = @ExampleObject( + name = "FeedCreateSuccess", + summary = "피드 생성 성공 응답", value = """ { "status": "SUCCESS", @@ -42,14 +46,34 @@ public interface FeedSwagger { ), @io.swagger.v3.oas.annotations.responses.ApiResponse( responseCode = "400", - description = "잘못된 요청", + description = "잘못된 요청 - 필수 입력 누락 또는 형식 오류", content = @Content( mediaType = "application/json", examples = @ExampleObject( + name = "FeedCreateInvalidRequest", + summary = "필수 값 누락 시 응답", value = """ { "status": "ERROR", - "message": "유효하지 않은 요청입니다.", + "message": "제목은 필수 입력입니다.", + "data": null + } + """ + ) + ) + ), + @io.swagger.v3.oas.annotations.responses.ApiResponse( + responseCode = "401", + description = "인증 실패 - 토큰이 없거나 만료됨", + content = @Content( + mediaType = "application/json", + examples = @ExampleObject( + name = "FeedCreateUnauthorized", + summary = "토큰 누락 시 응답", + value = """ + { + "status": "ERROR", + "message": "토큰이 없습니다.", "data": null } """ @@ -62,10 +86,12 @@ public interface FeedSwagger { content = @Content( mediaType = "application/json", examples = @ExampleObject( + name = "FeedCreateCommunityNotFound", + summary = "커뮤니티가 존재하지 않을 때", value = """ { "status": "ERROR", - "message": "커뮤니티를 찾을 수 없습니다.", + "message": "커뮤니티 리소스를 찾을 수 없습니다.", "data": null } """ @@ -74,19 +100,21 @@ public interface FeedSwagger { ) }) ResponseEntity createFeed( - @Parameter(hidden = true) Long userId, + @Parameter(hidden = true, description = "AccessToken으로 식별된 인증 사용자 ID", required = true) + Long userId, @io.swagger.v3.oas.annotations.parameters.RequestBody( description = "피드 생성 요청 정보", required = true, content = @Content( schema = @Schema(implementation = FeedRequest.class), examples = @ExampleObject( + name = "FeedCreateRequest", + summary = "피드 생성 요청", value = """ { - "title": "신입 개발자 채용 정보 공유", - "content": "우리 회사에서 신입 개발자를 채용합니다...", - "communityId": 1, - "userId": 1 + "title": "신입 백엔드 개발자 채용 정보 공유", + "content": "우리 팀에서 진행 중인 채용 공고와 준비 TIP을 공유합니다.", + "communityId": 1 } """ ) @@ -97,7 +125,7 @@ ResponseEntity createFeed( @Operation( summary = "피드 목록 조회", - description = "페이지네이션을 적용하여 피드 목록을 조회합니다." + description = "페이지 번호와 페이지 크기를 지정해 최신 피드 목록을 조회합니다." ) @ApiResponses(value = { @io.swagger.v3.oas.annotations.responses.ApiResponse( @@ -107,37 +135,64 @@ ResponseEntity createFeed( mediaType = "application/json", schema = @Schema(implementation = ApiResponse.class), examples = @ExampleObject( + name = "FeedList", + summary = "피드 목록 응답", value = """ { "status": "SUCCESS", "message": "피드 목록을 성공적으로 조회하였습니다.", "data": [ { - "feedId": 1, - "title": "신입 개발자 채용 정보 공유", - "authorName": "홍길동", - "communityName": "백엔드 개발자", - "createdAt": "2024-01-15T10:30:00", - "commentCount": 5, - "likeCount": 10 + "id": 7, + "title": "Infra 스터디 회고", + "author": { + "id": 21, + "nickname": "infra-cat" + }, + "community": { + "id": 3, + "name": "백엔드 개발자", + "jobGroup": "개발" + }, + "commentCount": 2 } ] } """ ) ) + ), + @io.swagger.v3.oas.annotations.responses.ApiResponse( + responseCode = "400", + description = "잘못된 요청 - page 또는 size 값 오류", + content = @Content( + mediaType = "application/json", + examples = @ExampleObject( + name = "FeedListBadRequest", + summary = "page/size 값이 음수일 때", + value = """ + { + "status": "ERROR", + "message": "페이지 번호와 크기는 음수가 될 수 없습니다.", + "data": null + } + """ + ) + ) ) }) ResponseEntity getFeeds( - @Parameter(description = "페이지 번호 (0부터 시작)", example = "0") + @Parameter(description = "페이지 번호 (0부터 시작)", example = "0", + schema = @Schema(minimum = "0", defaultValue = "0")) @RequestParam(defaultValue = "0") int page, - @Parameter(description = "페이지 크기", example = "10") + @Parameter(description = "페이지 크기 (1 이상)", example = "10", + schema = @Schema(minimum = "1", defaultValue = "10")) @RequestParam(defaultValue = "10") int size ); @Operation( summary = "피드 요약 조회", - description = "특정 피드의 요약 정보를 조회합니다." + description = "피드 카드 노출용으로 필요한 최소 정보(제목, 작성자, 커뮤니티, 댓글 수)를 조회합니다." ) @ApiResponses(value = { @io.swagger.v3.oas.annotations.responses.ApiResponse( @@ -147,18 +202,25 @@ ResponseEntity getFeeds( mediaType = "application/json", schema = @Schema(implementation = ApiResponse.class), examples = @ExampleObject( + name = "FeedSummary", + summary = "요약 응답", value = """ { "status": "SUCCESS", "message": "피드 요약을 성공적으로 조회하였습니다.", "data": { - "feedId": 1, + "id": 1, "title": "신입 개발자 채용 정보 공유", - "authorName": "홍길동", - "communityName": "백엔드 개발자", - "createdAt": "2024-01-15T10:30:00", - "commentCount": 5, - "likeCount": 10 + "author": { + "id": 5, + "nickname": "backend-dev" + }, + "community": { + "id": 2, + "name": "백엔드 개발자", + "jobGroup": "개발" + }, + "commentCount": 5 } } """ @@ -171,10 +233,12 @@ ResponseEntity getFeeds( content = @Content( mediaType = "application/json", examples = @ExampleObject( + name = "FeedSummaryNotFound", + summary = "요약 대상 피드 없음", value = """ { "status": "ERROR", - "message": "피드를 찾을 수 없습니다.", + "message": "피드 리소스를 찾을 수 없습니다.", "data": null } """ @@ -189,7 +253,7 @@ ResponseEntity getFeedSummary( @Operation( summary = "피드 상세 조회", - description = "특정 피드의 상세 정보를 조회합니다." + description = "본문과 커뮤니티 정보까지 포함한 피드 전체 정보를 조회합니다." ) @ApiResponses(value = { @io.swagger.v3.oas.annotations.responses.ApiResponse( @@ -199,22 +263,25 @@ ResponseEntity getFeedSummary( mediaType = "application/json", schema = @Schema(implementation = ApiResponse.class), examples = @ExampleObject( + name = "FeedDetail", + summary = "상세 응답", value = """ { "status": "SUCCESS", "message": "피드 상세를 성공적으로 조회하였습니다.", "data": { - "feedId": 1, + "id": 1, "title": "신입 개발자 채용 정보 공유", - "content": "우리 회사에서 신입 개발자를 채용합니다...", - "authorId": 1, - "authorName": "홍길동", - "communityId": 1, - "communityName": "백엔드 개발자", - "createdAt": "2024-01-15T10:30:00", - "updatedAt": "2024-01-15T10:30:00", - "commentCount": 5, - "likeCount": 10 + "content": "채용 절차와 준비 Tip을 정리했습니다.", + "author": { + "id": 5, + "nickname": "backend-dev" + }, + "community": { + "id": 2, + "name": "백엔드 개발자", + "jobGroup": "개발" + } } } """ @@ -227,10 +294,12 @@ ResponseEntity getFeedSummary( content = @Content( mediaType = "application/json", examples = @ExampleObject( + name = "FeedDetailNotFound", + summary = "존재하지 않는 피드", value = """ { "status": "ERROR", - "message": "피드를 찾을 수 없습니다.", + "message": "피드 리소스를 찾을 수 없습니다.", "data": null } """ @@ -245,7 +314,7 @@ ResponseEntity getFeedDetail( @Operation( summary = "피드 수정", - description = "작성자가 피드의 제목과 내용을 수정합니다." + description = "작성자가 본인이 작성한 피드의 제목과 내용을 수정합니다." ) @ApiResponses(value = { @io.swagger.v3.oas.annotations.responses.ApiResponse( @@ -255,6 +324,8 @@ ResponseEntity getFeedDetail( mediaType = "application/json", schema = @Schema(implementation = ApiResponse.class), examples = @ExampleObject( + name = "FeedUpdateSuccess", + summary = "수정 성공 응답", value = """ { "status": "SUCCESS", @@ -265,12 +336,50 @@ ResponseEntity getFeedDetail( ) ) ), + @io.swagger.v3.oas.annotations.responses.ApiResponse( + responseCode = "400", + description = "잘못된 요청 - 수정 값 누락", + content = @Content( + mediaType = "application/json", + examples = @ExampleObject( + name = "FeedUpdateBadRequest", + summary = "수정 본문 없음", + value = """ + { + "status": "ERROR", + "message": "수정할 제목 또는 내용이 필요합니다.", + "data": null + } + """ + ) + ) + ), + @io.swagger.v3.oas.annotations.responses.ApiResponse( + responseCode = "401", + description = "인증 실패", + content = @Content( + mediaType = "application/json", + examples = @ExampleObject( + name = "FeedUpdateUnauthorized", + summary = "토큰 없음", + value = """ + { + "status": "ERROR", + "message": "토큰이 없습니다.", + "data": null + } + """ + ) + ) + ), @io.swagger.v3.oas.annotations.responses.ApiResponse( responseCode = "403", description = "작성자가 아닌 경우", content = @Content( mediaType = "application/json", examples = @ExampleObject( + name = "FeedUpdateForbidden", + summary = "다른 사용자의 피드 수정", value = """ { "status": "ERROR", @@ -287,10 +396,12 @@ ResponseEntity getFeedDetail( content = @Content( mediaType = "application/json", examples = @ExampleObject( + name = "FeedUpdateNotFound", + summary = "수정 대상 없음", value = """ { "status": "ERROR", - "message": "피드를 찾을 수 없습니다.", + "message": "피드 리소스를 찾을 수 없습니다.", "data": null } """ @@ -299,7 +410,8 @@ ResponseEntity getFeedDetail( ) }) ResponseEntity updateFeed( - @Parameter(hidden = true) Long userId, + @Parameter(hidden = true, description = "인증된 사용자 ID", required = true) + Long userId, @Parameter(description = "피드 ID", required = true, example = "1") @PathVariable Long feedId, @io.swagger.v3.oas.annotations.parameters.RequestBody( @@ -308,10 +420,12 @@ ResponseEntity updateFeed( content = @Content( schema = @Schema(implementation = FeedUpdateRequest.class), examples = @ExampleObject( + name = "FeedUpdateRequest", + summary = "피드 수정 요청", value = """ { - "title": "수정된 제목", - "content": "수정된 내용" + "title": "제목을 업데이트합니다", + "content": "본문을 최신 정보로 업데이트합니다." } """ ) @@ -322,7 +436,7 @@ ResponseEntity updateFeed( @Operation( summary = "피드 삭제", - description = "작성자가 피드를 삭제합니다." + description = "작성자가 본인이 작성한 피드를 영구 삭제합니다." ) @ApiResponses(value = { @io.swagger.v3.oas.annotations.responses.ApiResponse( @@ -332,6 +446,8 @@ ResponseEntity updateFeed( mediaType = "application/json", schema = @Schema(implementation = ApiResponse.class), examples = @ExampleObject( + name = "FeedDeleteSuccess", + summary = "삭제 성공 응답", value = """ { "status": "SUCCESS", @@ -342,12 +458,32 @@ ResponseEntity updateFeed( ) ) ), + @io.swagger.v3.oas.annotations.responses.ApiResponse( + responseCode = "401", + description = "인증 실패", + content = @Content( + mediaType = "application/json", + examples = @ExampleObject( + name = "FeedDeleteUnauthorized", + summary = "토큰 누락", + value = """ + { + "status": "ERROR", + "message": "토큰이 없습니다.", + "data": null + } + """ + ) + ) + ), @io.swagger.v3.oas.annotations.responses.ApiResponse( responseCode = "403", description = "작성자가 아닌 경우", content = @Content( mediaType = "application/json", examples = @ExampleObject( + name = "FeedDeleteForbidden", + summary = "다른 사용자의 피드 삭제", value = """ { "status": "ERROR", @@ -364,10 +500,12 @@ ResponseEntity updateFeed( content = @Content( mediaType = "application/json", examples = @ExampleObject( + name = "FeedDeleteNotFound", + summary = "삭제 대상 없음", value = """ { "status": "ERROR", - "message": "피드를 찾을 수 없습니다.", + "message": "피드 리소스를 찾을 수 없습니다.", "data": null } """ @@ -376,7 +514,8 @@ ResponseEntity updateFeed( ) }) ResponseEntity deleteFeed( - @Parameter(hidden = true) Long userId, + @Parameter(hidden = true, description = "인증된 사용자 ID", required = true) + Long userId, @Parameter(description = "피드 ID", required = true, example = "1") @PathVariable Long feedId ); From 79261407e446d67f6e949dabdfe3af87f0a63adf Mon Sep 17 00:00:00 2001 From: jbh010204 Date: Wed, 10 Dec 2025 21:21:34 +0900 Subject: [PATCH 19/29] =?UTF-8?q?refactor:=20test=EB=82=B4=EB=B6=80=20?= =?UTF-8?q?=ED=81=B4=EB=9E=98=EC=8A=A4=20stub=20=EC=99=B8=EB=B6=80?= =?UTF-8?q?=EB=A1=9C=20=EA=B5=AC=EC=A1=B0=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../query/jdbc/FeedJdbcRepositoryImpl.java | 10 ++++---- .../application/CommentServiceUnitTest.java | 24 +++--------------- .../command/port/UserDataPortStub.java | 21 ++++++++++++++++ .../feed/application/FeedServiceUnitTest.java | 25 +++---------------- .../port/CommunityValidationPortStub.java | 22 ++++++++++++++++ 5 files changed, 54 insertions(+), 48 deletions(-) create mode 100644 src/test/java/com/example/bak/comment/application/command/port/UserDataPortStub.java create mode 100644 src/test/java/com/example/bak/feed/application/command/port/CommunityValidationPortStub.java diff --git a/src/main/java/com/example/bak/feed/infra/query/jdbc/FeedJdbcRepositoryImpl.java b/src/main/java/com/example/bak/feed/infra/query/jdbc/FeedJdbcRepositoryImpl.java index 579d0bd..e1f19b3 100644 --- a/src/main/java/com/example/bak/feed/infra/query/jdbc/FeedJdbcRepositoryImpl.java +++ b/src/main/java/com/example/bak/feed/infra/query/jdbc/FeedJdbcRepositoryImpl.java @@ -20,8 +20,8 @@ @RequiredArgsConstructor public class FeedJdbcRepositoryImpl implements FeedJdbcRepository { - private static final FeedSummaryRowMapper FEED_SUMMARY_MAPPER = new FeedSummaryRowMapper(); - private static final FeedDetailRowMapper FEED_DETAIL_MAPPER = new FeedDetailRowMapper(); + private static final FeedSummaryRowMapper feedSummaryMapper = new FeedSummaryRowMapper(); + private static final FeedDetailRowMapper feedDetailMapper = new FeedDetailRowMapper(); private static final String SUMMARY_SELECT = """ SELECT f.id AS id, @@ -75,7 +75,7 @@ public Page findAll(Pageable pageable) { + buildOrderByClause(pageable) + '\n' + "LIMIT :limit OFFSET :offset"; - List content = jdbc.query(sql, params, FEED_SUMMARY_MAPPER); + List content = jdbc.query(sql, params, feedSummaryMapper); long total = countFeeds(); return new PageImpl<>(content, pageable, total); @@ -90,7 +90,7 @@ public Optional findSummaryById(Long feedId) { + "WHERE f.id = :feedId\n" + SUMMARY_GROUP_BY; - return jdbc.query(sql, params, FEED_SUMMARY_MAPPER).stream().findFirst(); + return jdbc.query(sql, params, feedSummaryMapper).stream().findFirst(); } @Override @@ -98,7 +98,7 @@ public Optional findDetailById(Long feedId) { MapSqlParameterSource params = new MapSqlParameterSource() .addValue("feedId", feedId); - return jdbc.query(DETAIL_SELECT, params, FEED_DETAIL_MAPPER).stream().findFirst(); + return jdbc.query(DETAIL_SELECT, params, feedDetailMapper).stream().findFirst(); } private long countFeeds() { diff --git a/src/test/java/com/example/bak/comment/application/CommentServiceUnitTest.java b/src/test/java/com/example/bak/comment/application/CommentServiceUnitTest.java index 482ae62..7f04532 100644 --- a/src/test/java/com/example/bak/comment/application/CommentServiceUnitTest.java +++ b/src/test/java/com/example/bak/comment/application/CommentServiceUnitTest.java @@ -4,8 +4,7 @@ import static org.assertj.core.api.Assertions.assertThat; import com.example.bak.comment.application.command.CommentCommandService; -import com.example.bak.comment.application.command.port.UserDataPort; -import com.example.bak.comment.application.command.port.dto.UserSnapShot; +import com.example.bak.comment.application.command.port.UserDataPortStub; import com.example.bak.comment.application.query.CommentQueryService; import com.example.bak.comment.domain.Comment; import com.example.bak.comment.domain.CommentRepositoryStub; @@ -17,8 +16,6 @@ import com.example.bak.user.domain.Profile; import com.example.bak.user.domain.User; import java.util.List; -import java.util.Optional; -import java.util.concurrent.ConcurrentHashMap; import java.util.stream.IntStream; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; @@ -85,35 +82,20 @@ private List createComments(int count) { .toList(); } - private static class StubUserDataPort implements UserDataPort { - - private final ConcurrentHashMap store = new ConcurrentHashMap<>(); - - void save(User user) { - store.put(user.getId(), - new UserSnapShot(user.getId(), user.getProfile().getNickname())); - } - - @Override - public Optional findById(Long userId) { - return Optional.ofNullable(store.get(userId)); - } - } - @Nested @DisplayName("CommentCommandService") class CommentCommandServiceTest { private CommentCommandService commentCommandService; private CommentRepositoryStub commentRepository; - private StubUserDataPort userDataPort; + private UserDataPortStub userDataPort; @BeforeEach void setUp() { FeedRepositoryStub feedRepository = new FeedRepositoryStub(); feedRepository.save(testFeed); - userDataPort = new StubUserDataPort(); + userDataPort = new UserDataPortStub(); userDataPort.save(testUser); commentRepository = new CommentRepositoryStub(); diff --git a/src/test/java/com/example/bak/comment/application/command/port/UserDataPortStub.java b/src/test/java/com/example/bak/comment/application/command/port/UserDataPortStub.java new file mode 100644 index 0000000..2321535 --- /dev/null +++ b/src/test/java/com/example/bak/comment/application/command/port/UserDataPortStub.java @@ -0,0 +1,21 @@ +package com.example.bak.comment.application.command.port; + +import com.example.bak.comment.application.command.port.dto.UserSnapShot; +import com.example.bak.user.domain.User; +import java.util.Optional; +import java.util.concurrent.ConcurrentHashMap; + +public class UserDataPortStub implements UserDataPort { + + private final ConcurrentHashMap store = new ConcurrentHashMap<>(); + + public void save(User user) { + store.put(user.getId(), + new UserSnapShot(user.getId(), user.getProfile().getNickname())); + } + + @Override + public Optional findById(Long userId) { + return Optional.ofNullable(store.get(userId)); + } +} diff --git a/src/test/java/com/example/bak/feed/application/FeedServiceUnitTest.java b/src/test/java/com/example/bak/feed/application/FeedServiceUnitTest.java index bb2069f..17058be 100644 --- a/src/test/java/com/example/bak/feed/application/FeedServiceUnitTest.java +++ b/src/test/java/com/example/bak/feed/application/FeedServiceUnitTest.java @@ -5,12 +5,10 @@ import com.example.bak.feed.application.command.FeedCommandService; import com.example.bak.feed.application.command.dto.FeedResult; -import com.example.bak.feed.application.command.port.CommunityValidationPort; +import com.example.bak.feed.application.command.port.CommunityValidationPortStub; import com.example.bak.feed.domain.Feed; import com.example.bak.feed.domain.FeedRepositoryStub; import com.example.bak.global.exception.ErrorCode; -import java.util.HashSet; -import java.util.Set; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; @@ -25,23 +23,6 @@ class FeedServiceUnitTest { private static final String TITLE = "title"; private static final String CONTENT = "content"; - private static class StubCommunityValidationPort implements CommunityValidationPort { - - private final Set existingCommunityIds = new HashSet<>(); - - void registerCommunity(Long communityId) { - existingCommunityIds.add(communityId); - } - - void removeCommunity(Long communityId) { - existingCommunityIds.remove(communityId); - } - - @Override - public boolean isCommunityExists(Long communityId) { - return existingCommunityIds.contains(communityId); - } - } @Nested @DisplayName("FeedCommandService") @@ -49,12 +30,12 @@ class FeedCommandServiceTest { private FeedCommandService feedCommandService; private FeedRepositoryStub feedRepository; - private StubCommunityValidationPort communityValidationPort; + private CommunityValidationPortStub communityValidationPort; @BeforeEach void setUp() { feedRepository = new FeedRepositoryStub(); - communityValidationPort = new StubCommunityValidationPort(); + communityValidationPort = new CommunityValidationPortStub(); communityValidationPort.registerCommunity(EXISTING_COMMUNITY_ID); feedCommandService = new FeedCommandService(feedRepository, communityValidationPort); } diff --git a/src/test/java/com/example/bak/feed/application/command/port/CommunityValidationPortStub.java b/src/test/java/com/example/bak/feed/application/command/port/CommunityValidationPortStub.java new file mode 100644 index 0000000..efa3988 --- /dev/null +++ b/src/test/java/com/example/bak/feed/application/command/port/CommunityValidationPortStub.java @@ -0,0 +1,22 @@ +package com.example.bak.feed.application.command.port; + +import java.util.HashSet; +import java.util.Set; + +public class CommunityValidationPortStub implements CommunityValidationPort { + + private final Set existingCommunityIds = new HashSet<>(); + + public void registerCommunity(Long communityId) { + existingCommunityIds.add(communityId); + } + + public void removeCommunity(Long communityId) { + existingCommunityIds.remove(communityId); + } + + @Override + public boolean isCommunityExists(Long communityId) { + return existingCommunityIds.contains(communityId); + } +} From e7eebd9a65da4e8888a27fc6a8a24cd7d23c5b81 Mon Sep 17 00:00:00 2001 From: jbh010204 Date: Fri, 12 Dec 2025 21:10:01 +0900 Subject: [PATCH 20/29] =?UTF-8?q?refactor:=20User=EC=99=80=20Profile=20?= =?UTF-8?q?=EA=B0=84=EC=A0=91=20=EC=B0=B8=EC=A1=B0=20=ED=98=95=ED=83=9C?= =?UTF-8?q?=EB=A1=9C=20=EB=B6=84=EB=A6=AC=20=EB=B0=8F=20=EC=97=B0=EA=B4=80?= =?UTF-8?q?=EB=90=9C=20=EC=BD=94=EB=93=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../command/CommentCommandService.java | 8 +-- .../command/port/UserDataPort.java | 4 +- ...UserSnapShot.java => ProfileSnapShot.java} | 2 +- .../application/query/dto/UserResult.java | 10 +-- .../com/example/bak/user/domain/Profile.java | 11 +-- .../com/example/bak/user/domain/User.java | 23 +++---- .../bak/user/domain/UserRepository.java | 12 ---- .../command/ProfileJpaRepository.java | 12 ++++ .../command/UserCommandAdaptor.java | 6 +- .../command/UserJpaRepository.java | 10 +-- .../persistence/query/UserDataAdapter.java | 21 ++++++ .../query/jdbc/ProfileJdbcRepository.java | 15 ++++ .../query/jdbc/ProfileJdbcRepositoryImpl.java | 68 +++++++++++++++++++ .../bak/user/infra/query/UserDataAdapter.java | 24 ------- .../application/CommentServiceUnitTest.java | 9 +-- .../command/port/UserDataPortStub.java | 12 ++-- .../jdbc/ProfileJdbcRepositoryImplTest.java | 67 ++++++++++++++++++ .../UserJdbcRepositoryIntegrationTest.java | 3 +- 18 files changed, 213 insertions(+), 104 deletions(-) rename src/main/java/com/example/bak/comment/application/command/port/dto/{UserSnapShot.java => ProfileSnapShot.java} (51%) delete mode 100644 src/main/java/com/example/bak/user/domain/UserRepository.java create mode 100644 src/main/java/com/example/bak/user/infra/persistence/command/ProfileJpaRepository.java create mode 100644 src/main/java/com/example/bak/user/infra/persistence/query/UserDataAdapter.java create mode 100644 src/main/java/com/example/bak/user/infra/persistence/query/jdbc/ProfileJdbcRepository.java create mode 100644 src/main/java/com/example/bak/user/infra/persistence/query/jdbc/ProfileJdbcRepositoryImpl.java delete mode 100644 src/main/java/com/example/bak/user/infra/query/UserDataAdapter.java create mode 100644 src/test/java/com/example/bak/user/infra/persistence/query/jdbc/ProfileJdbcRepositoryImplTest.java rename src/test/java/com/example/bak/user/infra/{query => persistence/query/jdbc}/UserJdbcRepositoryIntegrationTest.java (94%) diff --git a/src/main/java/com/example/bak/comment/application/command/CommentCommandService.java b/src/main/java/com/example/bak/comment/application/command/CommentCommandService.java index bc7ba31..2a764b8 100644 --- a/src/main/java/com/example/bak/comment/application/command/CommentCommandService.java +++ b/src/main/java/com/example/bak/comment/application/command/CommentCommandService.java @@ -2,7 +2,7 @@ import com.example.bak.comment.application.command.port.CommentCommandPort; import com.example.bak.comment.application.command.port.UserDataPort; -import com.example.bak.comment.application.command.port.dto.UserSnapShot; +import com.example.bak.comment.application.command.port.dto.ProfileSnapShot; import com.example.bak.comment.domain.Comment; import com.example.bak.feed.application.command.port.FeedCommandPort; import com.example.bak.global.exception.BusinessException; @@ -24,14 +24,14 @@ public void createComment(Long feedId, String content, Long userId) { feedCommandPort.findById(feedId) .orElseThrow(() -> new BusinessException(ErrorCode.FEED_NOT_FOUND)); - UserSnapShot user = userDataPort.findById(userId) + ProfileSnapShot userProfile = userDataPort.findById(userId) .orElseThrow(() -> new BusinessException(ErrorCode.USER_NOT_FOUND)); Comment newComment = Comment.create( feedId, content, - user.id(), - user.nickname() + userProfile.userId(), + userProfile.nickname() ); commentCommandPort.save(newComment); diff --git a/src/main/java/com/example/bak/comment/application/command/port/UserDataPort.java b/src/main/java/com/example/bak/comment/application/command/port/UserDataPort.java index 82e5624..9b968f5 100644 --- a/src/main/java/com/example/bak/comment/application/command/port/UserDataPort.java +++ b/src/main/java/com/example/bak/comment/application/command/port/UserDataPort.java @@ -1,9 +1,9 @@ package com.example.bak.comment.application.command.port; -import com.example.bak.comment.application.command.port.dto.UserSnapShot; +import com.example.bak.comment.application.command.port.dto.ProfileSnapShot; import java.util.Optional; public interface UserDataPort { - Optional findById(Long userId); + Optional findById(Long userId); } diff --git a/src/main/java/com/example/bak/comment/application/command/port/dto/UserSnapShot.java b/src/main/java/com/example/bak/comment/application/command/port/dto/ProfileSnapShot.java similarity index 51% rename from src/main/java/com/example/bak/comment/application/command/port/dto/UserSnapShot.java rename to src/main/java/com/example/bak/comment/application/command/port/dto/ProfileSnapShot.java index 1de1bbf..e8063ca 100644 --- a/src/main/java/com/example/bak/comment/application/command/port/dto/UserSnapShot.java +++ b/src/main/java/com/example/bak/comment/application/command/port/dto/ProfileSnapShot.java @@ -1,5 +1,5 @@ package com.example.bak.comment.application.command.port.dto; -public record UserSnapShot(Long id, String nickname) { +public record ProfileSnapShot(Long userId, String nickname) { } diff --git a/src/main/java/com/example/bak/user/application/query/dto/UserResult.java b/src/main/java/com/example/bak/user/application/query/dto/UserResult.java index e1d061c..2c910a7 100644 --- a/src/main/java/com/example/bak/user/application/query/dto/UserResult.java +++ b/src/main/java/com/example/bak/user/application/query/dto/UserResult.java @@ -1,12 +1,10 @@ package com.example.bak.user.application.query.dto; -import com.example.bak.user.domain.User; - public record UserResult( Long id, String nickname ) { - + public static UserResult from(Long id, String nickname) { return new UserResult( id, @@ -14,10 +12,4 @@ public static UserResult from(Long id, String nickname) { ); } - public static UserResult from(User user) { - return new UserResult( - user.getId(), - user.getProfile().getNickname() - ); - } } diff --git a/src/main/java/com/example/bak/user/domain/Profile.java b/src/main/java/com/example/bak/user/domain/Profile.java index c47db8f..a7cd109 100644 --- a/src/main/java/com/example/bak/user/domain/Profile.java +++ b/src/main/java/com/example/bak/user/domain/Profile.java @@ -1,13 +1,10 @@ package com.example.bak.user.domain; -import jakarta.persistence.CascadeType; import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; -import jakarta.persistence.JoinColumn; -import jakarta.persistence.OneToOne; import lombok.AccessLevel; import lombok.AllArgsConstructor; import lombok.Getter; @@ -29,9 +26,7 @@ public class Profile { @Column(nullable = false) private String nickname; - @OneToOne(cascade = CascadeType.ALL) - @JoinColumn(name = "profile") - private User user; + private Long userId; private Profile(Long id, String name, String nickname) { this.id = id; @@ -55,7 +50,7 @@ public static Profile create(String name, String nickname) { return new Profile(name, nickname); } - public void assignUser(User user) { - this.user = user; + public void assignUser(Long userId) { + this.userId = userId; } } diff --git a/src/main/java/com/example/bak/user/domain/User.java b/src/main/java/com/example/bak/user/domain/User.java index e187e2f..608a2b5 100644 --- a/src/main/java/com/example/bak/user/domain/User.java +++ b/src/main/java/com/example/bak/user/domain/User.java @@ -2,7 +2,6 @@ import com.example.bak.global.exception.BusinessException; import com.example.bak.global.exception.ErrorCode; -import jakarta.persistence.CascadeType; import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.EnumType; @@ -10,8 +9,6 @@ import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; -import jakarta.persistence.JoinColumn; -import jakarta.persistence.OneToOne; import lombok.AccessLevel; import lombok.AllArgsConstructor; import lombok.Getter; @@ -33,9 +30,7 @@ public class User { @Column(nullable = false) private String password; - @OneToOne(cascade = CascadeType.ALL) - @JoinColumn(name = "user", nullable = false) - private Profile profile; + private Long profileId; @Enumerated(EnumType.STRING) private UserRole role = UserRole.NORMAL; @@ -51,12 +46,6 @@ private User(Long id, String email, String password) { this.password = password; } - public void matchPassword(String newPassword) { - if (!newPassword.equals(this.password)) { - throw new BusinessException(ErrorCode.INCORRECT_PASSWORD); - } - } - public static User createInstance(Long id, String email, String password) { return new User(id, email, password); } @@ -69,7 +58,13 @@ public static User testInstance(Long id, String email, String password) { return new User(id, email, password); } - public void addProfile(Profile profile) { - this.profile = profile; + public void matchPassword(String newPassword) { + if (!newPassword.equals(this.password)) { + throw new BusinessException(ErrorCode.INCORRECT_PASSWORD); + } + } + + public void addProfile(Long profileId) { + this.profileId = profileId; } } diff --git a/src/main/java/com/example/bak/user/domain/UserRepository.java b/src/main/java/com/example/bak/user/domain/UserRepository.java deleted file mode 100644 index 255945b..0000000 --- a/src/main/java/com/example/bak/user/domain/UserRepository.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.example.bak.user.domain; - -import java.util.Optional; - -public interface UserRepository { - - User save(User user); - - Optional findById(Long userId); - - Optional findByEmail(String email); -} diff --git a/src/main/java/com/example/bak/user/infra/persistence/command/ProfileJpaRepository.java b/src/main/java/com/example/bak/user/infra/persistence/command/ProfileJpaRepository.java new file mode 100644 index 0000000..98e11b5 --- /dev/null +++ b/src/main/java/com/example/bak/user/infra/persistence/command/ProfileJpaRepository.java @@ -0,0 +1,12 @@ +package com.example.bak.user.infra.persistence.command; + +import com.example.bak.comment.application.command.port.dto.ProfileSnapShot; +import com.example.bak.user.domain.Profile; +import java.util.Optional; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface ProfileJpaRepository extends JpaRepository { + + Optional findProfileSnapShotByUserId(Long userId); +} + diff --git a/src/main/java/com/example/bak/user/infra/persistence/command/UserCommandAdaptor.java b/src/main/java/com/example/bak/user/infra/persistence/command/UserCommandAdaptor.java index 8ba76e3..b02cba2 100644 --- a/src/main/java/com/example/bak/user/infra/persistence/command/UserCommandAdaptor.java +++ b/src/main/java/com/example/bak/user/infra/persistence/command/UserCommandAdaptor.java @@ -1,7 +1,5 @@ package com.example.bak.user.infra.persistence.command; -import com.example.bak.global.exception.BusinessException; -import com.example.bak.global.exception.ErrorCode; import com.example.bak.user.application.command.port.UserCommandPort; import com.example.bak.user.application.query.dto.ProfileResult; import com.example.bak.user.domain.Profile; @@ -22,10 +20,8 @@ public User save(User user) { @Override public ProfileResult createProfile(Long userId, String name, String nickname) { - User user = userJpaRepository.findById(userId).orElseThrow(() -> new BusinessException( - ErrorCode.USER_NOT_FOUND)); Profile profile = Profile.create(name, nickname); - user.addProfile(profile); + profile.assignUser(userId); return ProfileResult.from(profile.getName(), profile.getNickname()); } } diff --git a/src/main/java/com/example/bak/user/infra/persistence/command/UserJpaRepository.java b/src/main/java/com/example/bak/user/infra/persistence/command/UserJpaRepository.java index a3ff40e..12ba2f6 100644 --- a/src/main/java/com/example/bak/user/infra/persistence/command/UserJpaRepository.java +++ b/src/main/java/com/example/bak/user/infra/persistence/command/UserJpaRepository.java @@ -1,18 +1,10 @@ package com.example.bak.user.infra.persistence.command; import com.example.bak.user.domain.User; -import com.example.bak.user.domain.UserRepository; import java.util.Optional; import org.springframework.data.jpa.repository.JpaRepository; -public interface UserJpaRepository extends JpaRepository, UserRepository { +public interface UserJpaRepository extends JpaRepository { - @Override - User save(User user); - - @Override - Optional findById(Long id); - - @Override Optional findByEmail(String email); } diff --git a/src/main/java/com/example/bak/user/infra/persistence/query/UserDataAdapter.java b/src/main/java/com/example/bak/user/infra/persistence/query/UserDataAdapter.java new file mode 100644 index 0000000..0e9775e --- /dev/null +++ b/src/main/java/com/example/bak/user/infra/persistence/query/UserDataAdapter.java @@ -0,0 +1,21 @@ +package com.example.bak.user.infra.persistence.query; + +import com.example.bak.comment.application.command.port.UserDataPort; +import com.example.bak.comment.application.command.port.dto.ProfileSnapShot; +import com.example.bak.user.infra.persistence.command.ProfileJpaRepository; +import java.util.Optional; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Repository; + +@Repository +@RequiredArgsConstructor +public class UserDataAdapter implements UserDataPort { + + private final ProfileJpaRepository profileJpaRepository; + + @Override + public Optional findById(Long userId) { + return profileJpaRepository.findProfileSnapShotByUserId(userId); + } + +} \ No newline at end of file diff --git a/src/main/java/com/example/bak/user/infra/persistence/query/jdbc/ProfileJdbcRepository.java b/src/main/java/com/example/bak/user/infra/persistence/query/jdbc/ProfileJdbcRepository.java new file mode 100644 index 0000000..369fea5 --- /dev/null +++ b/src/main/java/com/example/bak/user/infra/persistence/query/jdbc/ProfileJdbcRepository.java @@ -0,0 +1,15 @@ +package com.example.bak.user.infra.persistence.query.jdbc; + +import com.example.bak.user.domain.Profile; +import com.example.bak.user.domain.User; +import java.util.Optional; + +public interface ProfileJdbcRepository { + + Optional findById(Long id); + + Optional findByName(String name); + + Optional findUserById(Long userId); +} + diff --git a/src/main/java/com/example/bak/user/infra/persistence/query/jdbc/ProfileJdbcRepositoryImpl.java b/src/main/java/com/example/bak/user/infra/persistence/query/jdbc/ProfileJdbcRepositoryImpl.java new file mode 100644 index 0000000..37329d4 --- /dev/null +++ b/src/main/java/com/example/bak/user/infra/persistence/query/jdbc/ProfileJdbcRepositoryImpl.java @@ -0,0 +1,68 @@ +package com.example.bak.user.infra.persistence.query.jdbc; + +import com.example.bak.user.domain.Profile; +import com.example.bak.user.domain.User; +import com.example.bak.user.infra.persistence.query.jdbc.mapper.ProfileMapper; +import com.example.bak.user.infra.persistence.query.jdbc.mapper.UserMapper; +import java.util.Optional; +import lombok.RequiredArgsConstructor; +import org.springframework.jdbc.core.namedparam.MapSqlParameterSource; +import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; +import org.springframework.stereotype.Repository; + +@Repository +@RequiredArgsConstructor +public class ProfileJdbcRepositoryImpl implements ProfileJdbcRepository { + + private final NamedParameterJdbcTemplate repository; + + @Override + public Optional findById(Long id) { + String sql = """ + SELECT + p.id as profile_id, + p.name as profile_name, + p.nickname as profile_nickname + FROM profiles p + WHERE p.id = :id + """; + MapSqlParameterSource params = new MapSqlParameterSource(); + params.addValue("id", id); + Profile profile = repository.queryForObject(sql, params, new ProfileMapper()); + return Optional.ofNullable(profile); + } + + @Override + public Optional findByName(String name) { + String sql = """ + SELECT + p.id as profile_id, + p.name as profile_name, + p.nickname as profile_nickname + FROM profiles p + WHERE p.name = :name + """; + MapSqlParameterSource params = new MapSqlParameterSource(); + params.addValue("name", name); + Profile profile = repository.queryForObject(sql, params, new ProfileMapper()); + return Optional.ofNullable(profile); + } + + @Override + public Optional findUserById(Long userId) { + String sql = """ + select + u.id as user_id, + u.email as user_email, + u.password as user_password + from users u + left join profiles p on p.user_id = u.id + where u.id = :userId + """; + MapSqlParameterSource params = new MapSqlParameterSource(); + params.addValue("userId", userId); + User profile = repository.queryForObject(sql, params, new UserMapper()); + return Optional.empty(); + } +} + diff --git a/src/main/java/com/example/bak/user/infra/query/UserDataAdapter.java b/src/main/java/com/example/bak/user/infra/query/UserDataAdapter.java deleted file mode 100644 index ba01dba..0000000 --- a/src/main/java/com/example/bak/user/infra/query/UserDataAdapter.java +++ /dev/null @@ -1,24 +0,0 @@ -package com.example.bak.user.infra.query; - -import com.example.bak.comment.application.command.port.UserDataPort; -import com.example.bak.comment.application.command.port.dto.UserSnapShot; -import com.example.bak.user.domain.UserRepository; -import java.util.Optional; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Repository; - -@Repository -@RequiredArgsConstructor -public class UserDataAdapter implements UserDataPort { - - private final UserRepository userRepository; - - @Override - public Optional findById(Long userId) { - return userRepository.findById(userId) - .map(user -> new UserSnapShot( - user.getId(), - user.getProfile().getNickname() - )); - } -} diff --git a/src/test/java/com/example/bak/comment/application/CommentServiceUnitTest.java b/src/test/java/com/example/bak/comment/application/CommentServiceUnitTest.java index 7f04532..ff191f2 100644 --- a/src/test/java/com/example/bak/comment/application/CommentServiceUnitTest.java +++ b/src/test/java/com/example/bak/comment/application/CommentServiceUnitTest.java @@ -13,7 +13,6 @@ import com.example.bak.feed.domain.Feed; import com.example.bak.feed.domain.FeedRepositoryStub; import com.example.bak.global.exception.ErrorCode; -import com.example.bak.user.domain.Profile; import com.example.bak.user.domain.User; import java.util.List; import java.util.stream.IntStream; @@ -35,7 +34,6 @@ class CommentServiceUnitTest { private static final String USER_EMAIL = "test@test.com"; private static final String USER_PASSWORD = "password"; - private static final String USER_NAME = "name"; private static final String USER_NICKNAME = "nickname"; private final Company company = @@ -60,10 +58,7 @@ void initFixtures() { } private User createUser() { - User user = User.testInstance(EXISTING_USER_ID, USER_EMAIL, USER_PASSWORD); - Profile profile = Profile.createInstance(1L, USER_NAME, USER_NICKNAME); - user.addProfile(profile); - return user; + return User.testInstance(EXISTING_USER_ID, USER_EMAIL, USER_PASSWORD); } private String nickname() { @@ -96,7 +91,7 @@ void setUp() { feedRepository.save(testFeed); userDataPort = new UserDataPortStub(); - userDataPort.save(testUser); + userDataPort.save(testUser.getId(), nickname()); commentRepository = new CommentRepositoryStub(); diff --git a/src/test/java/com/example/bak/comment/application/command/port/UserDataPortStub.java b/src/test/java/com/example/bak/comment/application/command/port/UserDataPortStub.java index 2321535..4b6e92d 100644 --- a/src/test/java/com/example/bak/comment/application/command/port/UserDataPortStub.java +++ b/src/test/java/com/example/bak/comment/application/command/port/UserDataPortStub.java @@ -1,21 +1,19 @@ package com.example.bak.comment.application.command.port; -import com.example.bak.comment.application.command.port.dto.UserSnapShot; -import com.example.bak.user.domain.User; +import com.example.bak.comment.application.command.port.dto.ProfileSnapShot; import java.util.Optional; import java.util.concurrent.ConcurrentHashMap; public class UserDataPortStub implements UserDataPort { - private final ConcurrentHashMap store = new ConcurrentHashMap<>(); + private final ConcurrentHashMap store = new ConcurrentHashMap<>(); - public void save(User user) { - store.put(user.getId(), - new UserSnapShot(user.getId(), user.getProfile().getNickname())); + public void save(Long userId, String nickname) { + store.put(userId, new ProfileSnapShot(userId, nickname)); } @Override - public Optional findById(Long userId) { + public Optional findById(Long userId) { return Optional.ofNullable(store.get(userId)); } } diff --git a/src/test/java/com/example/bak/user/infra/persistence/query/jdbc/ProfileJdbcRepositoryImplTest.java b/src/test/java/com/example/bak/user/infra/persistence/query/jdbc/ProfileJdbcRepositoryImplTest.java new file mode 100644 index 0000000..d38cff3 --- /dev/null +++ b/src/test/java/com/example/bak/user/infra/persistence/query/jdbc/ProfileJdbcRepositoryImplTest.java @@ -0,0 +1,67 @@ +package com.example.bak.user.infra.persistence.query.jdbc; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.example.bak.user.domain.Profile; +import com.example.bak.user.domain.User; +import java.util.Optional; +import lombok.extern.slf4j.Slf4j; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.jdbc.JdbcTest; +import org.springframework.test.context.jdbc.Sql; + +@Slf4j +@JdbcTest +@DisplayName("ProfileJdbcRepository 통합 테스트") +@Sql(scripts = "/sql/user/data.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD) +class ProfileJdbcRepositoryImplTest { + + @Autowired + private ProfileJdbcRepository profileJdbcRepository; + + @Nested + @DisplayName("Profile 조회 관련 Tests") + class ProfileFind { + + @Test + void findById() { + Long profileId = 1L; + Optional profile = profileJdbcRepository.findById(profileId); + + assertThat(profile).isNotEmpty(); + + assertThat(profile.get().getId()).isEqualTo(profileId); + } + + @Test + void findByName() { + String username = "User One"; + Optional profile = profileJdbcRepository.findByName(username); + + assertThat(profile).isNotEmpty(); + + assertThat(profile.get().getName()).isEqualTo(username); + } + } + + @Nested + @DisplayName("User 조회 관련 Test") + class UserFind { + + @Test + void findUserById() { + Long userId = 1L; + + Optional user = profileJdbcRepository.findUserById(userId); + + assertThat(user).isNotEmpty(); + + assertThat(user.get().getId()).isEqualTo(userId); + } + } + + +} \ No newline at end of file diff --git a/src/test/java/com/example/bak/user/infra/query/UserJdbcRepositoryIntegrationTest.java b/src/test/java/com/example/bak/user/infra/persistence/query/jdbc/UserJdbcRepositoryIntegrationTest.java similarity index 94% rename from src/test/java/com/example/bak/user/infra/query/UserJdbcRepositoryIntegrationTest.java rename to src/test/java/com/example/bak/user/infra/persistence/query/jdbc/UserJdbcRepositoryIntegrationTest.java index d2091ee..a35122a 100644 --- a/src/test/java/com/example/bak/user/infra/query/UserJdbcRepositoryIntegrationTest.java +++ b/src/test/java/com/example/bak/user/infra/persistence/query/jdbc/UserJdbcRepositoryIntegrationTest.java @@ -1,4 +1,4 @@ -package com.example.bak.user.infra.query; +package com.example.bak.user.infra.persistence.query.jdbc; import static org.assertj.core.api.Assertions.assertThat; @@ -6,7 +6,6 @@ import com.example.bak.global.exception.BusinessException; import com.example.bak.global.exception.ErrorCode; import com.example.bak.user.domain.Profile; -import com.example.bak.user.infra.persistence.query.jdbc.UserJdbcRepository; import lombok.extern.slf4j.Slf4j; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; From d3a902273503c5ac634be8374179fac09a82b2b7 Mon Sep 17 00:00:00 2001 From: jbh010204 Date: Fri, 12 Dec 2025 21:45:39 +0900 Subject: [PATCH 21/29] feat: Replace UserDataPort with ProfileDataPort for user profile handling --- .../command/CommentCommandService.java | 6 ++-- ...UserDataPort.java => ProfileDataPort.java} | 4 +-- ...taAdapter.java => ProfileDataAdapter.java} | 8 ++--- .../query/jdbc/ProfileJdbcRepositoryImpl.java | 5 ++- .../application/CommentServiceUnitTest.java | 20 ++++++++--- .../command/port/UserDataPortStub.java | 19 ---------- .../bak/global/JdbcRepositoryTestConfig.java | 7 ++++ .../user/domain/ProfileRepositoryStub.java | 35 +++++++++++++++++++ .../jdbc/ProfileJdbcRepositoryImplTest.java | 5 +-- 9 files changed, 71 insertions(+), 38 deletions(-) rename src/main/java/com/example/bak/comment/application/command/port/{UserDataPort.java => ProfileDataPort.java} (62%) rename src/main/java/com/example/bak/user/infra/persistence/query/{UserDataAdapter.java => ProfileDataAdapter.java} (71%) delete mode 100644 src/test/java/com/example/bak/comment/application/command/port/UserDataPortStub.java create mode 100644 src/test/java/com/example/bak/user/domain/ProfileRepositoryStub.java diff --git a/src/main/java/com/example/bak/comment/application/command/CommentCommandService.java b/src/main/java/com/example/bak/comment/application/command/CommentCommandService.java index 2a764b8..d86431c 100644 --- a/src/main/java/com/example/bak/comment/application/command/CommentCommandService.java +++ b/src/main/java/com/example/bak/comment/application/command/CommentCommandService.java @@ -1,7 +1,7 @@ package com.example.bak.comment.application.command; import com.example.bak.comment.application.command.port.CommentCommandPort; -import com.example.bak.comment.application.command.port.UserDataPort; +import com.example.bak.comment.application.command.port.ProfileDataPort; import com.example.bak.comment.application.command.port.dto.ProfileSnapShot; import com.example.bak.comment.domain.Comment; import com.example.bak.feed.application.command.port.FeedCommandPort; @@ -18,13 +18,13 @@ public class CommentCommandService { private final CommentCommandPort commentCommandPort; private final FeedCommandPort feedCommandPort; - private final UserDataPort userDataPort; + private final ProfileDataPort profileDataPort; public void createComment(Long feedId, String content, Long userId) { feedCommandPort.findById(feedId) .orElseThrow(() -> new BusinessException(ErrorCode.FEED_NOT_FOUND)); - ProfileSnapShot userProfile = userDataPort.findById(userId) + ProfileSnapShot userProfile = profileDataPort.findSnapshotByUserId(userId) .orElseThrow(() -> new BusinessException(ErrorCode.USER_NOT_FOUND)); Comment newComment = Comment.create( diff --git a/src/main/java/com/example/bak/comment/application/command/port/UserDataPort.java b/src/main/java/com/example/bak/comment/application/command/port/ProfileDataPort.java similarity index 62% rename from src/main/java/com/example/bak/comment/application/command/port/UserDataPort.java rename to src/main/java/com/example/bak/comment/application/command/port/ProfileDataPort.java index 9b968f5..02ce66f 100644 --- a/src/main/java/com/example/bak/comment/application/command/port/UserDataPort.java +++ b/src/main/java/com/example/bak/comment/application/command/port/ProfileDataPort.java @@ -3,7 +3,7 @@ import com.example.bak.comment.application.command.port.dto.ProfileSnapShot; import java.util.Optional; -public interface UserDataPort { +public interface ProfileDataPort { - Optional findById(Long userId); + Optional findSnapshotByUserId(Long userId); } diff --git a/src/main/java/com/example/bak/user/infra/persistence/query/UserDataAdapter.java b/src/main/java/com/example/bak/user/infra/persistence/query/ProfileDataAdapter.java similarity index 71% rename from src/main/java/com/example/bak/user/infra/persistence/query/UserDataAdapter.java rename to src/main/java/com/example/bak/user/infra/persistence/query/ProfileDataAdapter.java index 0e9775e..e296066 100644 --- a/src/main/java/com/example/bak/user/infra/persistence/query/UserDataAdapter.java +++ b/src/main/java/com/example/bak/user/infra/persistence/query/ProfileDataAdapter.java @@ -1,6 +1,6 @@ package com.example.bak.user.infra.persistence.query; -import com.example.bak.comment.application.command.port.UserDataPort; +import com.example.bak.comment.application.command.port.ProfileDataPort; import com.example.bak.comment.application.command.port.dto.ProfileSnapShot; import com.example.bak.user.infra.persistence.command.ProfileJpaRepository; import java.util.Optional; @@ -9,13 +9,13 @@ @Repository @RequiredArgsConstructor -public class UserDataAdapter implements UserDataPort { +public class ProfileDataAdapter implements ProfileDataPort { private final ProfileJpaRepository profileJpaRepository; @Override - public Optional findById(Long userId) { + public Optional findSnapshotByUserId(Long userId) { return profileJpaRepository.findProfileSnapShotByUserId(userId); } -} \ No newline at end of file +} diff --git a/src/main/java/com/example/bak/user/infra/persistence/query/jdbc/ProfileJdbcRepositoryImpl.java b/src/main/java/com/example/bak/user/infra/persistence/query/jdbc/ProfileJdbcRepositoryImpl.java index 37329d4..f918622 100644 --- a/src/main/java/com/example/bak/user/infra/persistence/query/jdbc/ProfileJdbcRepositoryImpl.java +++ b/src/main/java/com/example/bak/user/infra/persistence/query/jdbc/ProfileJdbcRepositoryImpl.java @@ -61,8 +61,7 @@ public Optional findUserById(Long userId) { """; MapSqlParameterSource params = new MapSqlParameterSource(); params.addValue("userId", userId); - User profile = repository.queryForObject(sql, params, new UserMapper()); - return Optional.empty(); + User user = repository.queryForObject(sql, params, new UserMapper()); + return Optional.ofNullable(user); } } - diff --git a/src/test/java/com/example/bak/comment/application/CommentServiceUnitTest.java b/src/test/java/com/example/bak/comment/application/CommentServiceUnitTest.java index ff191f2..1cf09bc 100644 --- a/src/test/java/com/example/bak/comment/application/CommentServiceUnitTest.java +++ b/src/test/java/com/example/bak/comment/application/CommentServiceUnitTest.java @@ -4,7 +4,6 @@ import static org.assertj.core.api.Assertions.assertThat; import com.example.bak.comment.application.command.CommentCommandService; -import com.example.bak.comment.application.command.port.UserDataPortStub; import com.example.bak.comment.application.query.CommentQueryService; import com.example.bak.comment.domain.Comment; import com.example.bak.comment.domain.CommentRepositoryStub; @@ -13,6 +12,8 @@ import com.example.bak.feed.domain.Feed; import com.example.bak.feed.domain.FeedRepositoryStub; import com.example.bak.global.exception.ErrorCode; +import com.example.bak.user.domain.Profile; +import com.example.bak.user.domain.ProfileRepositoryStub; import com.example.bak.user.domain.User; import java.util.List; import java.util.stream.IntStream; @@ -34,6 +35,7 @@ class CommentServiceUnitTest { private static final String USER_EMAIL = "test@test.com"; private static final String USER_PASSWORD = "password"; + private static final String PROFILE_NAME = "name"; private static final String USER_NICKNAME = "nickname"; private final Company company = @@ -43,11 +45,13 @@ class CommentServiceUnitTest { Community.testInstance(1L, "name", "jobGroup", 1L); private User testUser; + private Profile testProfile; private Feed testFeed; @BeforeEach void initFixtures() { testUser = createUser(); + testProfile = createProfile(); testFeed = Feed.testInstance( EXISTING_FEED_ID, "title", @@ -61,6 +65,12 @@ private User createUser() { return User.testInstance(EXISTING_USER_ID, USER_EMAIL, USER_PASSWORD); } + private Profile createProfile() { + Profile profile = Profile.createInstance(1L, PROFILE_NAME, USER_NICKNAME); + profile.assignUser(EXISTING_USER_ID); + return profile; + } + private String nickname() { return USER_NICKNAME; } @@ -83,22 +93,22 @@ class CommentCommandServiceTest { private CommentCommandService commentCommandService; private CommentRepositoryStub commentRepository; - private UserDataPortStub userDataPort; + private ProfileRepositoryStub profileDataPort; @BeforeEach void setUp() { FeedRepositoryStub feedRepository = new FeedRepositoryStub(); feedRepository.save(testFeed); - userDataPort = new UserDataPortStub(); - userDataPort.save(testUser.getId(), nickname()); + profileDataPort = new ProfileRepositoryStub(); + profileDataPort.save(testProfile); commentRepository = new CommentRepositoryStub(); commentCommandService = new CommentCommandService( commentRepository, feedRepository, - userDataPort + profileDataPort ); } diff --git a/src/test/java/com/example/bak/comment/application/command/port/UserDataPortStub.java b/src/test/java/com/example/bak/comment/application/command/port/UserDataPortStub.java deleted file mode 100644 index 4b6e92d..0000000 --- a/src/test/java/com/example/bak/comment/application/command/port/UserDataPortStub.java +++ /dev/null @@ -1,19 +0,0 @@ -package com.example.bak.comment.application.command.port; - -import com.example.bak.comment.application.command.port.dto.ProfileSnapShot; -import java.util.Optional; -import java.util.concurrent.ConcurrentHashMap; - -public class UserDataPortStub implements UserDataPort { - - private final ConcurrentHashMap store = new ConcurrentHashMap<>(); - - public void save(Long userId, String nickname) { - store.put(userId, new ProfileSnapShot(userId, nickname)); - } - - @Override - public Optional findById(Long userId) { - return Optional.ofNullable(store.get(userId)); - } -} diff --git a/src/test/java/com/example/bak/global/JdbcRepositoryTestConfig.java b/src/test/java/com/example/bak/global/JdbcRepositoryTestConfig.java index de24c04..ff6b800 100644 --- a/src/test/java/com/example/bak/global/JdbcRepositoryTestConfig.java +++ b/src/test/java/com/example/bak/global/JdbcRepositoryTestConfig.java @@ -4,6 +4,8 @@ import com.example.bak.feed.infra.query.jdbc.FeedJdbcRepositoryImpl; import com.example.bak.privatemessage.infra.query.jdbc.MessageJdbcRepository; import com.example.bak.privatemessage.infra.query.jdbc.MessageJdbcRepositoryImpl; +import com.example.bak.user.infra.persistence.query.jdbc.ProfileJdbcRepository; +import com.example.bak.user.infra.persistence.query.jdbc.ProfileJdbcRepositoryImpl; import com.example.bak.user.infra.persistence.query.jdbc.UserJdbcRepository; import com.example.bak.user.infra.persistence.query.jdbc.UserJdbcRepositoryImpl; import org.springframework.boot.test.context.TestConfiguration; @@ -27,4 +29,9 @@ public FeedJdbcRepository feedJdbcRepository(NamedParameterJdbcTemplate jdbc) { public UserJdbcRepository userJdbcRepository(NamedParameterJdbcTemplate jdbc) { return new UserJdbcRepositoryImpl(jdbc); } + + @Bean + public ProfileJdbcRepository profileJdbcRepository(NamedParameterJdbcTemplate jdbc) { + return new ProfileJdbcRepositoryImpl(jdbc); + } } diff --git a/src/test/java/com/example/bak/user/domain/ProfileRepositoryStub.java b/src/test/java/com/example/bak/user/domain/ProfileRepositoryStub.java new file mode 100644 index 0000000..39c4dde --- /dev/null +++ b/src/test/java/com/example/bak/user/domain/ProfileRepositoryStub.java @@ -0,0 +1,35 @@ +package com.example.bak.user.domain; + +import com.example.bak.comment.application.command.port.ProfileDataPort; +import com.example.bak.comment.application.command.port.dto.ProfileSnapShot; +import com.example.bak.global.support.AbstractStubRepository; +import java.util.Objects; +import java.util.Optional; + +public class ProfileRepositoryStub extends AbstractStubRepository + implements ProfileDataPort { + + @Override + protected Long getId(Profile profile) { + return profile.getUserId(); + } + + @Override + protected boolean isSame(Long left, Long right) { + return Objects.equals(left, right); + } + + @Override + public Profile save(Profile profile) { + if (profile.getUserId() == null) { + throw new IllegalArgumentException("Profile must have assigned userId"); + } + return super.save(profile); + } + + @Override + public Optional findSnapshotByUserId(Long userId) { + return super.findById(userId) + .map(profile -> new ProfileSnapShot(profile.getUserId(), profile.getNickname())); + } +} diff --git a/src/test/java/com/example/bak/user/infra/persistence/query/jdbc/ProfileJdbcRepositoryImplTest.java b/src/test/java/com/example/bak/user/infra/persistence/query/jdbc/ProfileJdbcRepositoryImplTest.java index d38cff3..8729de8 100644 --- a/src/test/java/com/example/bak/user/infra/persistence/query/jdbc/ProfileJdbcRepositoryImplTest.java +++ b/src/test/java/com/example/bak/user/infra/persistence/query/jdbc/ProfileJdbcRepositoryImplTest.java @@ -2,6 +2,7 @@ import static org.assertj.core.api.Assertions.assertThat; +import com.example.bak.global.AbstractMySqlContainerTest; import com.example.bak.user.domain.Profile; import com.example.bak.user.domain.User; import java.util.Optional; @@ -17,7 +18,7 @@ @JdbcTest @DisplayName("ProfileJdbcRepository 통합 테스트") @Sql(scripts = "/sql/user/data.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD) -class ProfileJdbcRepositoryImplTest { +class ProfileJdbcRepositoryImplTest extends AbstractMySqlContainerTest { @Autowired private ProfileJdbcRepository profileJdbcRepository; @@ -64,4 +65,4 @@ void findUserById() { } -} \ No newline at end of file +} From 436784487e53a51293088077e741f871f3f32413 Mon Sep 17 00:00:00 2001 From: jbh010204 Date: Fri, 12 Dec 2025 21:46:48 +0900 Subject: [PATCH 22/29] refactor: Rename entity from 'feed_comments' to 'comments' for clarity --- src/main/java/com/example/bak/comment/domain/Comment.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/example/bak/comment/domain/Comment.java b/src/main/java/com/example/bak/comment/domain/Comment.java index d40f347..b9d42e1 100644 --- a/src/main/java/com/example/bak/comment/domain/Comment.java +++ b/src/main/java/com/example/bak/comment/domain/Comment.java @@ -11,7 +11,7 @@ import lombok.Getter; import lombok.NoArgsConstructor; -@Entity(name = "feed_comments") +@Entity(name = "comments") @Getter @NoArgsConstructor(access = AccessLevel.PROTECTED) @AllArgsConstructor(access = AccessLevel.PROTECTED) From c6aa091db58ae31d29ba57d670c6fcfb41a1cff5 Mon Sep 17 00:00:00 2001 From: jbh010204 Date: Sat, 13 Dec 2025 23:48:01 +0900 Subject: [PATCH 23/29] =?UTF-8?q?refactor:=20ProfileSnapShot=20domain=20?= =?UTF-8?q?=ED=8C=A8=ED=82=A4=EC=A7=80=20=EA=B5=AC=EC=A1=B0=EB=A1=9C=20?= =?UTF-8?q?=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../bak/comment/application/command/CommentCommandService.java | 2 +- .../bak/comment/application/command/port/ProfileDataPort.java | 2 +- .../command/port/dto => domain}/ProfileSnapShot.java | 2 +- .../user/infra/persistence/command/ProfileJpaRepository.java | 2 +- .../bak/user/infra/persistence/query/ProfileDataAdapter.java | 2 +- .../java/com/example/bak/user/domain/ProfileRepositoryStub.java | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) rename src/main/java/com/example/bak/comment/{application/command/port/dto => domain}/ProfileSnapShot.java (51%) diff --git a/src/main/java/com/example/bak/comment/application/command/CommentCommandService.java b/src/main/java/com/example/bak/comment/application/command/CommentCommandService.java index d86431c..831f774 100644 --- a/src/main/java/com/example/bak/comment/application/command/CommentCommandService.java +++ b/src/main/java/com/example/bak/comment/application/command/CommentCommandService.java @@ -2,8 +2,8 @@ import com.example.bak.comment.application.command.port.CommentCommandPort; import com.example.bak.comment.application.command.port.ProfileDataPort; -import com.example.bak.comment.application.command.port.dto.ProfileSnapShot; import com.example.bak.comment.domain.Comment; +import com.example.bak.comment.domain.ProfileSnapShot; import com.example.bak.feed.application.command.port.FeedCommandPort; import com.example.bak.global.exception.BusinessException; import com.example.bak.global.exception.ErrorCode; diff --git a/src/main/java/com/example/bak/comment/application/command/port/ProfileDataPort.java b/src/main/java/com/example/bak/comment/application/command/port/ProfileDataPort.java index 02ce66f..3001eda 100644 --- a/src/main/java/com/example/bak/comment/application/command/port/ProfileDataPort.java +++ b/src/main/java/com/example/bak/comment/application/command/port/ProfileDataPort.java @@ -1,6 +1,6 @@ package com.example.bak.comment.application.command.port; -import com.example.bak.comment.application.command.port.dto.ProfileSnapShot; +import com.example.bak.comment.domain.ProfileSnapShot; import java.util.Optional; public interface ProfileDataPort { diff --git a/src/main/java/com/example/bak/comment/application/command/port/dto/ProfileSnapShot.java b/src/main/java/com/example/bak/comment/domain/ProfileSnapShot.java similarity index 51% rename from src/main/java/com/example/bak/comment/application/command/port/dto/ProfileSnapShot.java rename to src/main/java/com/example/bak/comment/domain/ProfileSnapShot.java index e8063ca..07374ac 100644 --- a/src/main/java/com/example/bak/comment/application/command/port/dto/ProfileSnapShot.java +++ b/src/main/java/com/example/bak/comment/domain/ProfileSnapShot.java @@ -1,4 +1,4 @@ -package com.example.bak.comment.application.command.port.dto; +package com.example.bak.comment.domain; public record ProfileSnapShot(Long userId, String nickname) { diff --git a/src/main/java/com/example/bak/user/infra/persistence/command/ProfileJpaRepository.java b/src/main/java/com/example/bak/user/infra/persistence/command/ProfileJpaRepository.java index 98e11b5..8ac5441 100644 --- a/src/main/java/com/example/bak/user/infra/persistence/command/ProfileJpaRepository.java +++ b/src/main/java/com/example/bak/user/infra/persistence/command/ProfileJpaRepository.java @@ -1,6 +1,6 @@ package com.example.bak.user.infra.persistence.command; -import com.example.bak.comment.application.command.port.dto.ProfileSnapShot; +import com.example.bak.comment.domain.ProfileSnapShot; import com.example.bak.user.domain.Profile; import java.util.Optional; import org.springframework.data.jpa.repository.JpaRepository; diff --git a/src/main/java/com/example/bak/user/infra/persistence/query/ProfileDataAdapter.java b/src/main/java/com/example/bak/user/infra/persistence/query/ProfileDataAdapter.java index e296066..78201b7 100644 --- a/src/main/java/com/example/bak/user/infra/persistence/query/ProfileDataAdapter.java +++ b/src/main/java/com/example/bak/user/infra/persistence/query/ProfileDataAdapter.java @@ -1,7 +1,7 @@ package com.example.bak.user.infra.persistence.query; import com.example.bak.comment.application.command.port.ProfileDataPort; -import com.example.bak.comment.application.command.port.dto.ProfileSnapShot; +import com.example.bak.comment.domain.ProfileSnapShot; import com.example.bak.user.infra.persistence.command.ProfileJpaRepository; import java.util.Optional; import lombok.RequiredArgsConstructor; diff --git a/src/test/java/com/example/bak/user/domain/ProfileRepositoryStub.java b/src/test/java/com/example/bak/user/domain/ProfileRepositoryStub.java index 39c4dde..e1796e0 100644 --- a/src/test/java/com/example/bak/user/domain/ProfileRepositoryStub.java +++ b/src/test/java/com/example/bak/user/domain/ProfileRepositoryStub.java @@ -1,7 +1,7 @@ package com.example.bak.user.domain; import com.example.bak.comment.application.command.port.ProfileDataPort; -import com.example.bak.comment.application.command.port.dto.ProfileSnapShot; +import com.example.bak.comment.domain.ProfileSnapShot; import com.example.bak.global.support.AbstractStubRepository; import java.util.Objects; import java.util.Optional; From a37f38d25b0fdb35d3d74c4c8c7fd4ac1bdc552a Mon Sep 17 00:00:00 2001 From: jbh010204 Date: Sat, 13 Dec 2025 23:58:15 +0900 Subject: [PATCH 24/29] =?UTF-8?q?refactor:=20=ED=94=BC=EB=93=9C=20?= =?UTF-8?q?=EA=B2=80=EC=A6=9D=EC=9D=84=20=EC=9C=84=ED=95=9C=20FeedValidati?= =?UTF-8?q?onPort=20and=20adapter=20=EB=8F=84=EC=9E=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../command/CommentCommandService.java | 9 +++++---- .../command/port/FeedValidationPort.java | 6 ++++++ .../infra/command/FeedValidationAdapter.java | 17 +++++++++++++++++ 3 files changed, 28 insertions(+), 4 deletions(-) create mode 100644 src/main/java/com/example/bak/feed/application/command/port/FeedValidationPort.java create mode 100644 src/main/java/com/example/bak/feed/infra/command/FeedValidationAdapter.java diff --git a/src/main/java/com/example/bak/comment/application/command/CommentCommandService.java b/src/main/java/com/example/bak/comment/application/command/CommentCommandService.java index 831f774..292d635 100644 --- a/src/main/java/com/example/bak/comment/application/command/CommentCommandService.java +++ b/src/main/java/com/example/bak/comment/application/command/CommentCommandService.java @@ -4,7 +4,7 @@ import com.example.bak.comment.application.command.port.ProfileDataPort; import com.example.bak.comment.domain.Comment; import com.example.bak.comment.domain.ProfileSnapShot; -import com.example.bak.feed.application.command.port.FeedCommandPort; +import com.example.bak.feed.application.command.port.FeedValidationPort; import com.example.bak.global.exception.BusinessException; import com.example.bak.global.exception.ErrorCode; import lombok.RequiredArgsConstructor; @@ -17,12 +17,13 @@ public class CommentCommandService { private final CommentCommandPort commentCommandPort; - private final FeedCommandPort feedCommandPort; + private final FeedValidationPort feedValidationPort; private final ProfileDataPort profileDataPort; public void createComment(Long feedId, String content, Long userId) { - feedCommandPort.findById(feedId) - .orElseThrow(() -> new BusinessException(ErrorCode.FEED_NOT_FOUND)); + if (!feedValidationPort.existsById(feedId)) { + throw new BusinessException(ErrorCode.FEED_NOT_FOUND); + } ProfileSnapShot userProfile = profileDataPort.findSnapshotByUserId(userId) .orElseThrow(() -> new BusinessException(ErrorCode.USER_NOT_FOUND)); diff --git a/src/main/java/com/example/bak/feed/application/command/port/FeedValidationPort.java b/src/main/java/com/example/bak/feed/application/command/port/FeedValidationPort.java new file mode 100644 index 0000000..beb0383 --- /dev/null +++ b/src/main/java/com/example/bak/feed/application/command/port/FeedValidationPort.java @@ -0,0 +1,6 @@ +package com.example.bak.feed.application.command.port; + +public interface FeedValidationPort { + + boolean existsById(Long feedId); +} diff --git a/src/main/java/com/example/bak/feed/infra/command/FeedValidationAdapter.java b/src/main/java/com/example/bak/feed/infra/command/FeedValidationAdapter.java new file mode 100644 index 0000000..d4b47d8 --- /dev/null +++ b/src/main/java/com/example/bak/feed/infra/command/FeedValidationAdapter.java @@ -0,0 +1,17 @@ +package com.example.bak.feed.infra.command; + +import com.example.bak.feed.application.command.port.FeedValidationPort; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Repository; + +@Repository +@RequiredArgsConstructor +public class FeedValidationAdapter implements FeedValidationPort { + + private final FeedJpaRepository feedJpaRepository; + + @Override + public boolean existsById(Long feedId) { + return feedJpaRepository.existsById(feedId); + } +} From 2a54f69c01e50fe958b2fec3fe609665ec2f9b50 Mon Sep 17 00:00:00 2001 From: jbh010204 Date: Sun, 14 Dec 2025 00:03:07 +0900 Subject: [PATCH 25/29] =?UTF-8?q?refactor:=20author=20validation=20in=20Fe?= =?UTF-8?q?ed=20=EB=8F=84=EB=A9=94=EC=9D=B8=20=EB=82=B4=EB=B6=80=20?= =?UTF-8?q?=EB=A1=9C=EC=A7=81=EC=9C=BC=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../command/FeedCommandService.java | 10 ++----- .../com/example/bak/feed/domain/Feed.java | 8 ++++++ .../bak/feed/domain/FeedRepositoryStub.java | 8 +++++- .../com/example/bak/feed/domain/FeedTest.java | 28 +++++++++++++++++++ 4 files changed, 45 insertions(+), 9 deletions(-) create mode 100644 src/test/java/com/example/bak/feed/domain/FeedTest.java diff --git a/src/main/java/com/example/bak/feed/application/command/FeedCommandService.java b/src/main/java/com/example/bak/feed/application/command/FeedCommandService.java index e685a2a..3976565 100644 --- a/src/main/java/com/example/bak/feed/application/command/FeedCommandService.java +++ b/src/main/java/com/example/bak/feed/application/command/FeedCommandService.java @@ -33,7 +33,7 @@ public void updateFeed(Long feedId, String title, String content, Long userId) { Feed feed = feedCommandPort.findById(feedId) .orElseThrow(() -> new BusinessException(ErrorCode.FEED_NOT_FOUND)); - validateAuthor(feed, userId); + feed.validateAuthor(userId); feed.update(title, content); feedCommandPort.save(feed); @@ -43,14 +43,8 @@ public void deleteFeed(Long feedId, Long userId) { Feed feed = feedCommandPort.findById(feedId) .orElseThrow(() -> new BusinessException(ErrorCode.FEED_NOT_FOUND)); - validateAuthor(feed, userId); + feed.validateAuthor(userId); feedCommandPort.delete(feed); } - - private void validateAuthor(Feed feed, Long userId) { - if (!feed.getAuthorId().equals(userId)) { - throw new BusinessException(ErrorCode.UNAUTHORIZED_ACTION); - } - } } diff --git a/src/main/java/com/example/bak/feed/domain/Feed.java b/src/main/java/com/example/bak/feed/domain/Feed.java index aaef222..0096e0b 100644 --- a/src/main/java/com/example/bak/feed/domain/Feed.java +++ b/src/main/java/com/example/bak/feed/domain/Feed.java @@ -1,5 +1,7 @@ package com.example.bak.feed.domain; +import com.example.bak.global.exception.BusinessException; +import com.example.bak.global.exception.ErrorCode; import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.GeneratedValue; @@ -68,4 +70,10 @@ public void update(String title, String content) { this.title = title; this.content = content; } + + public void validateAuthor(Long userId) { + if (!this.authorId.equals(userId)) { + throw new BusinessException(ErrorCode.UNAUTHORIZED_ACTION); + } + } } diff --git a/src/test/java/com/example/bak/feed/domain/FeedRepositoryStub.java b/src/test/java/com/example/bak/feed/domain/FeedRepositoryStub.java index 40cc163..476b8f4 100644 --- a/src/test/java/com/example/bak/feed/domain/FeedRepositoryStub.java +++ b/src/test/java/com/example/bak/feed/domain/FeedRepositoryStub.java @@ -1,6 +1,7 @@ package com.example.bak.feed.domain; import com.example.bak.feed.application.command.port.FeedCommandPort; +import com.example.bak.feed.application.command.port.FeedValidationPort; import com.example.bak.global.support.AbstractStubRepository; import java.util.List; import java.util.Objects; @@ -10,7 +11,7 @@ public class FeedRepositoryStub extends AbstractStubRepository - implements FeedRepository, FeedCommandPort { + implements FeedRepository, FeedCommandPort, FeedValidationPort { @Override protected Long getId(Feed feed) { @@ -46,4 +47,9 @@ public Optional findById(Long id) { public void delete(Feed feed) { store.removeIf(it -> isSame(getId(it), getId(feed))); } + + @Override + public boolean existsById(Long feedId) { + return findById(feedId).isPresent(); + } } diff --git a/src/test/java/com/example/bak/feed/domain/FeedTest.java b/src/test/java/com/example/bak/feed/domain/FeedTest.java new file mode 100644 index 0000000..2519f5f --- /dev/null +++ b/src/test/java/com/example/bak/feed/domain/FeedTest.java @@ -0,0 +1,28 @@ +package com.example.bak.feed.domain; + +import static com.example.bak.global.utils.AssertionsErrorCode.assertBusiness; +import static org.assertj.core.api.Assertions.assertThatCode; + +import com.example.bak.global.exception.ErrorCode; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +@DisplayName("Feed 도메인 테스트") +class FeedTest { + + @Test + @DisplayName("작성자 검증에 성공한다") + void validateAuthor_success() { + Feed feed = Feed.testInstance(1L, "title", "content", 1L, 10L); + + assertThatCode(() -> feed.validateAuthor(10L)).doesNotThrowAnyException(); + } + + @Test + @DisplayName("작성자 검증 실패 시 예외") + void validateAuthor_when_unauthorized() { + Feed feed = Feed.testInstance(1L, "title", "content", 1L, 10L); + + assertBusiness(() -> feed.validateAuthor(99L), ErrorCode.UNAUTHORIZED_ACTION); + } +} From f2d679292f9a0022eb7d04f2f72d4dc7b164c649 Mon Sep 17 00:00:00 2001 From: jbh010204 Date: Sun, 14 Dec 2025 00:09:22 +0900 Subject: [PATCH 26/29] =?UTF-8?q?refactor:=20DB=20=EC=8A=A4=ED=82=A4?= =?UTF-8?q?=EB=A7=88=20=EB=B3=80=EA=B2=BD=EC=97=90=20=EB=94=B0=EB=A5=B8=20?= =?UTF-8?q?SQL=20feed=5Fcomments=20to=20comments=20=EC=9C=BC=EB=A1=9C=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../bak/feed/infra/query/jdbc/FeedJdbcRepositoryImpl.java | 4 ++-- src/test/resources/sql/feed/data.sql | 4 ++-- src/test/resources/sql/schema.sql | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/main/java/com/example/bak/feed/infra/query/jdbc/FeedJdbcRepositoryImpl.java b/src/main/java/com/example/bak/feed/infra/query/jdbc/FeedJdbcRepositoryImpl.java index e1f19b3..68d2745 100644 --- a/src/main/java/com/example/bak/feed/infra/query/jdbc/FeedJdbcRepositoryImpl.java +++ b/src/main/java/com/example/bak/feed/infra/query/jdbc/FeedJdbcRepositoryImpl.java @@ -31,12 +31,12 @@ public class FeedJdbcRepositoryImpl implements FeedJdbcRepository { c.id AS community_id, c.name AS community_name, c.job_group AS community_job_group, - COUNT(fc.id) AS comment_count + COUNT(cm.id) AS comment_count FROM feeds f JOIN users u ON f.author_id = u.id JOIN profiles p ON p.user_id = u.id JOIN communities c ON f.community_id = c.id - LEFT JOIN feed_comments fc ON fc.feed_id = f.id + LEFT JOIN comments cm ON cm.feed_id = f.id """; private static final String SUMMARY_GROUP_BY = """ diff --git a/src/test/resources/sql/feed/data.sql b/src/test/resources/sql/feed/data.sql index 88a7d8f..dfc496d 100644 --- a/src/test/resources/sql/feed/data.sql +++ b/src/test/resources/sql/feed/data.sql @@ -1,4 +1,4 @@ -DELETE FROM feed_comments; +DELETE FROM comments; DELETE FROM feeds; DELETE FROM communities; DELETE FROM profiles; @@ -27,7 +27,7 @@ INSERT INTO feeds (id, title, content, community_id, author_id) VALUES (2, 'title2', 'content2', 1, 2), (3, 'title3', 'content3', 2, 3); -INSERT INTO feed_comments (id, comment, author_id, feed_id) VALUES +INSERT INTO comments (id, comment, author_id, feed_id) VALUES (1, 'c1', 2, 1), (2, 'c2', 3, 1), (3, 'c3', 1, 2); diff --git a/src/test/resources/sql/schema.sql b/src/test/resources/sql/schema.sql index 25cce40..b6292bb 100644 --- a/src/test/resources/sql/schema.sql +++ b/src/test/resources/sql/schema.sql @@ -1,6 +1,6 @@ -- Drop tables if they exist to start with a clean slate -DROP TABLE IF EXISTS feed_comments; +DROP TABLE IF EXISTS comments; DROP TABLE IF EXISTS feeds; DROP TABLE IF EXISTS communities; DROP TABLE IF EXISTS profiles; @@ -53,8 +53,8 @@ CREATE TABLE feeds ( FOREIGN KEY (author_id) REFERENCES users(id) ); --- Table for Feed Comments -CREATE TABLE feed_comments ( +-- Table for Comments +CREATE TABLE comments ( id BIGINT AUTO_INCREMENT PRIMARY KEY, comment TEXT NOT NULL, author_id BIGINT, From 43a92c55e44e39e9de90d2338501f229cb6526c7 Mon Sep 17 00:00:00 2001 From: jbh010204 Date: Sun, 14 Dec 2025 00:17:06 +0900 Subject: [PATCH 27/29] =?UTF-8?q?refactor:=20validation=20adapters?= =?UTF-8?q?=EC=9D=98=20=EC=8A=A4=ED=85=8C=EB=A0=88=EC=98=A4=ED=83=80?= =?UTF-8?q?=EC=9E=85=EC=9D=84=20Component=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../community/infra/command/CommunityValidationAdapter.java | 4 ++-- .../example/bak/feed/infra/command/FeedValidationAdapter.java | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/example/bak/community/infra/command/CommunityValidationAdapter.java b/src/main/java/com/example/bak/community/infra/command/CommunityValidationAdapter.java index 0f18961..6f3f9c3 100644 --- a/src/main/java/com/example/bak/community/infra/command/CommunityValidationAdapter.java +++ b/src/main/java/com/example/bak/community/infra/command/CommunityValidationAdapter.java @@ -2,9 +2,9 @@ import com.example.bak.feed.application.command.port.CommunityValidationPort; import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Repository; +import org.springframework.stereotype.Component; -@Repository +@Component @RequiredArgsConstructor public class CommunityValidationAdapter implements CommunityValidationPort { diff --git a/src/main/java/com/example/bak/feed/infra/command/FeedValidationAdapter.java b/src/main/java/com/example/bak/feed/infra/command/FeedValidationAdapter.java index d4b47d8..b68833d 100644 --- a/src/main/java/com/example/bak/feed/infra/command/FeedValidationAdapter.java +++ b/src/main/java/com/example/bak/feed/infra/command/FeedValidationAdapter.java @@ -2,9 +2,9 @@ import com.example.bak.feed.application.command.port.FeedValidationPort; import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Repository; +import org.springframework.stereotype.Component; -@Repository +@Component @RequiredArgsConstructor public class FeedValidationAdapter implements FeedValidationPort { From 44ba7dd9573b6eeae776afc4d0b9dfbc322a0a71 Mon Sep 17 00:00:00 2001 From: jbh010204 Date: Sun, 14 Dec 2025 00:36:42 +0900 Subject: [PATCH 28/29] =?UTF-8?q?refactor:=20ProfileJpaRepository=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20=EB=B0=8F=20=ED=94=84=EB=A1=9C=ED=95=84=20?= =?UTF-8?q?=EC=A0=80=EC=9E=A5=20=EB=A1=9C=EC=A7=81=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../bak/user/infra/persistence/command/UserCommandAdaptor.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/com/example/bak/user/infra/persistence/command/UserCommandAdaptor.java b/src/main/java/com/example/bak/user/infra/persistence/command/UserCommandAdaptor.java index b02cba2..e1724b8 100644 --- a/src/main/java/com/example/bak/user/infra/persistence/command/UserCommandAdaptor.java +++ b/src/main/java/com/example/bak/user/infra/persistence/command/UserCommandAdaptor.java @@ -12,6 +12,7 @@ public class UserCommandAdaptor implements UserCommandPort { private final UserJpaRepository userJpaRepository; + private final ProfileJpaRepository profileJpaRepository; @Override public User save(User user) { @@ -22,6 +23,7 @@ public User save(User user) { public ProfileResult createProfile(Long userId, String name, String nickname) { Profile profile = Profile.create(name, nickname); profile.assignUser(userId); + profileJpaRepository.save(profile); return ProfileResult.from(profile.getName(), profile.getNickname()); } } From 94e485befd86e424cb9e1af7fe1f21828f7f3357 Mon Sep 17 00:00:00 2001 From: KNU-K Date: Sun, 14 Dec 2025 14:26:36 +0900 Subject: [PATCH 29/29] =?UTF-8?q?fix=20:=20comments=20=EA=B0=9D=EC=B2=B4?= =?UTF-8?q?=EC=99=80=20=EB=8B=A4=EB=A5=B4=EA=B2=8C=20=EA=B5=AC=EC=84=B1?= =?UTF-8?q?=EB=90=9C=20sql=EB=AC=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/test/resources/sql/feed/data.sql | 56 ++++++++++-------- src/test/resources/sql/schema.sql | 87 +++++++++++++++------------- 2 files changed, 78 insertions(+), 65 deletions(-) diff --git a/src/test/resources/sql/feed/data.sql b/src/test/resources/sql/feed/data.sql index dfc496d..c6e54ba 100644 --- a/src/test/resources/sql/feed/data.sql +++ b/src/test/resources/sql/feed/data.sql @@ -1,33 +1,39 @@ -DELETE FROM comments; -DELETE FROM feeds; -DELETE FROM communities; -DELETE FROM profiles; -DELETE FROM users; -DELETE FROM companies; +DELETE +FROM comments; +DELETE +FROM feeds; +DELETE +FROM communities; +DELETE +FROM profiles; +DELETE +FROM users; +DELETE +FROM companies; INSERT INTO companies (id, name, career_link, logo_url, description) VALUES (1, 'company', 'company.com', 'logo.png', 'desc'); -INSERT INTO users (id, email, password) VALUES - (1, 'author1@test.com', 'pw'), - (2, 'author2@test.com', 'pw'), - (3, 'author3@test.com', 'pw'); +INSERT INTO users (id, email, password) +VALUES (1, 'author1@test.com', 'pw'), + (2, 'author2@test.com', 'pw'), + (3, 'author3@test.com', 'pw'); -INSERT INTO profiles (id, name, nickname, user_id) VALUES - (1, 'user1', 'nick1', 1), - (2, 'user2', 'nick2', 2), - (3, 'user3', 'nick3', 3); +INSERT INTO profiles (id, name, nickname, user_id) +VALUES (1, 'user1', 'nick1', 1), + (2, 'user2', 'nick2', 2), + (3, 'user3', 'nick3', 3); -INSERT INTO communities (id, name, job_group, company_id) VALUES - (1, 'backend', 'dev', 1), - (2, 'frontend', 'dev', 1); +INSERT INTO communities (id, name, job_group, company_id) +VALUES (1, 'backend', 'dev', 1), + (2, 'frontend', 'dev', 1); -INSERT INTO feeds (id, title, content, community_id, author_id) VALUES - (1, 'title1', 'content1', 1, 1), - (2, 'title2', 'content2', 1, 2), - (3, 'title3', 'content3', 2, 3); +INSERT INTO feeds (id, title, content, community_id, author_id) +VALUES (1, 'title1', 'content1', 1, 1), + (2, 'title2', 'content2', 1, 2), + (3, 'title3', 'content3', 2, 3); -INSERT INTO comments (id, comment, author_id, feed_id) VALUES - (1, 'c1', 2, 1), - (2, 'c2', 3, 1), - (3, 'c3', 1, 2); +INSERT INTO comments (id, content, author_id, author_nickname, feed_id) +VALUES (1, 'c1', 2, 'nick2', 1), + (2, 'c2', 3, 'nick3', 1), + (3, 'c3', 1, 'nick1', 2); \ No newline at end of file diff --git a/src/test/resources/sql/schema.sql b/src/test/resources/sql/schema.sql index b6292bb..0b8924d 100644 --- a/src/test/resources/sql/schema.sql +++ b/src/test/resources/sql/schema.sql @@ -1,4 +1,3 @@ - -- Drop tables if they exist to start with a clean slate DROP TABLE IF EXISTS comments; DROP TABLE IF EXISTS feeds; @@ -9,68 +8,76 @@ DROP TABLE IF EXISTS companies; DROP TABLE IF EXISTS private_messages; -- Table for Companies -CREATE TABLE companies ( - id BIGINT AUTO_INCREMENT PRIMARY KEY, - name VARCHAR(255) NOT NULL, +CREATE TABLE companies +( + id BIGINT AUTO_INCREMENT PRIMARY KEY, + name VARCHAR(255) NOT NULL, career_link VARCHAR(255) NOT NULL, - logo_url VARCHAR(255) NOT NULL, + logo_url VARCHAR(255) NOT NULL, description VARCHAR(255) NOT NULL ); -- Table for Users -CREATE TABLE users ( - id BIGINT AUTO_INCREMENT PRIMARY KEY, - email VARCHAR(255) NOT NULL UNIQUE, +CREATE TABLE users +( + id BIGINT AUTO_INCREMENT PRIMARY KEY, + email VARCHAR(255) NOT NULL UNIQUE, password VARCHAR(255) NOT NULL ); -- Table for Profiles -CREATE TABLE profiles ( - id BIGINT AUTO_INCREMENT PRIMARY KEY, - name VARCHAR(255) NOT NULL, +CREATE TABLE profiles +( + id BIGINT AUTO_INCREMENT PRIMARY KEY, + name VARCHAR(255) NOT NULL, nickname VARCHAR(255) NOT NULL, - user_id BIGINT, - FOREIGN KEY (user_id) REFERENCES users(id) + user_id BIGINT, + FOREIGN KEY (user_id) REFERENCES users (id) ); -- Table for Communities -CREATE TABLE communities ( - id BIGINT AUTO_INCREMENT PRIMARY KEY, - name VARCHAR(255) NOT NULL, - job_group VARCHAR(255) NOT NULL, +CREATE TABLE communities +( + id BIGINT AUTO_INCREMENT PRIMARY KEY, + name VARCHAR(255) NOT NULL, + job_group VARCHAR(255) NOT NULL, company_id BIGINT, - FOREIGN KEY (company_id) REFERENCES companies(id) + FOREIGN KEY (company_id) REFERENCES companies (id) ); -- Table for Feeds -CREATE TABLE feeds ( - id BIGINT AUTO_INCREMENT PRIMARY KEY, - title VARCHAR(255) NOT NULL, - content TEXT NOT NULL, +CREATE TABLE feeds +( + id BIGINT AUTO_INCREMENT PRIMARY KEY, + title VARCHAR(255) NOT NULL, + content TEXT NOT NULL, community_id BIGINT, - author_id BIGINT, - FOREIGN KEY (community_id) REFERENCES communities(id), - FOREIGN KEY (author_id) REFERENCES users(id) + author_id BIGINT, + FOREIGN KEY (community_id) REFERENCES communities (id), + FOREIGN KEY (author_id) REFERENCES users (id) ); -- Table for Comments -CREATE TABLE comments ( - id BIGINT AUTO_INCREMENT PRIMARY KEY, - comment TEXT NOT NULL, - author_id BIGINT, - feed_id BIGINT, - FOREIGN KEY (author_id) REFERENCES users(id), - FOREIGN KEY (feed_id) REFERENCES feeds(id) +CREATE TABLE comments +( + id BIGINT AUTO_INCREMENT PRIMARY KEY, + content TEXT NOT NULL, + author_id BIGINT, + author_nickname VARCHAR(255), + feed_id BIGINT, + FOREIGN KEY (author_id) REFERENCES users (id), + FOREIGN KEY (feed_id) REFERENCES feeds (id) ); -- Table for Private Messages -CREATE TABLE private_messages ( - id BIGINT AUTO_INCREMENT PRIMARY KEY, - sender_id BIGINT NOT NULL, - receiver_id BIGINT NOT NULL, - content TEXT NOT NULL, - read_at DATETIME, - deleted_at_by_sender DATETIME, +CREATE TABLE private_messages +( + id BIGINT AUTO_INCREMENT PRIMARY KEY, + sender_id BIGINT NOT NULL, + receiver_id BIGINT NOT NULL, + content TEXT NOT NULL, + read_at DATETIME, + deleted_at_by_sender DATETIME, deleted_at_by_receiver DATETIME, - created_at DATETIME DEFAULT CURRENT_TIMESTAMP + created_at DATETIME DEFAULT CURRENT_TIMESTAMP );