Skip to content

Commit

Permalink
refactor: 게시글 조회 서비스 위치변경
Browse files Browse the repository at this point in the history
  • Loading branch information
youngreal committed Feb 21, 2024
1 parent b8c3738 commit f06f5d9
Show file tree
Hide file tree
Showing 5 changed files with 105 additions and 67 deletions.
1 change: 1 addition & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ dependencies {
// async test
testImplementation 'org.awaitility:awaitility:4.2.0'

// querydsl
implementation 'com.querydsl:querydsl-jpa:5.0.0:jakarta'
implementation "com.querydsl:querydsl-core"
implementation "com.querydsl:querydsl-collections"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ select post from post postId in (100 null 99)
*/
@GetMapping("/posts/{postId}")
public PostDetailPageResponse postDetail(@PathVariable long postId) {
return PostDetailPageResponse.from(postQueryService.postDetail(postId));
return PostDetailPageResponse.from(postService.postDetail(postId));
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,8 @@

import static java.util.stream.Collectors.toMap;

import com.example.inflearn.common.exception.DoesNotExistPostException;
import com.example.inflearn.common.exception.SearchWordLengthException;
import com.example.inflearn.domain.post.PostDto;
import com.example.inflearn.domain.post.domain.Post;
import com.example.inflearn.infra.redis.LikeCountRedisRepository;
import com.example.inflearn.infra.repository.dto.projection.PostHashtagDto;
import com.example.inflearn.infra.mapper.post.PostMapper;
import com.example.inflearn.infra.repository.post.PostRepository;
Expand All @@ -25,49 +23,26 @@
@Service
public class PostQueryService {

private static final int SEARCH_WORD_MIN_LENGTH = 2;
private final PaginationService paginationService;
private final PostRepository postRepository;
private final PostMapper postMapper;
private final LikeCountRedisRepository likeCountRedisRepository;

//todo 게시글 조회와 조회수가 +1 되는 로직은 트랜잭션 분리되어도 될것같은데..? 분리를 고려해보는게 맞을까?
@Transactional
public PostDto postDetail(long postId) {
// 게시글 존재여부 검증
Post post = postRepository.findById(postId).orElseThrow(DoesNotExistPostException::new);

// 조회수 업데이트
// addViewCount(post);

// 게시글 상세 내용 조회(해시태그, 댓글)
PostDto postDetail = postRepository.postDetail(postId);
postDetail.inputHashtags(postRepository.postHashtagsBy(postDetail));
postDetail.inputComments(postRepository.commentsBy(postDetail));
return postDetail;
}

// @Transactional
// public PostDto postDetail2(long postId) {
// // 게시글 존재여부 검증
// Post post = postRepository.findById(postId).orElseThrow(DoesNotExistPostException::new);
//
// // 조회수 업데이트
// addViewCount(post);
//
// // 게시글 상세 내용 조회(해시태그, 댓글)
// PostDto postDetail = postRepository.postDetail(postId);
// postDetail.inputHashtags(postRepository.postHashtagsBy(postDetail));
// postDetail.inputComments(postRepository.commentsBy(postDetail));
// return postDetail;
// }

public List<PostDto> searchPost(PostSearch postSearch) {
if (postSearch.searchWord().length() < SEARCH_WORD_MIN_LENGTH) {
throw new SearchWordLengthException();
}

List<PostDto> postDtos = postMapper.search(postSearch.searchWord(), paginationService.calculateOffSet(postSearch.page()), postSearch.size(), postSearch.sort());
setHashtagsWithJoin(postDtos);
return postDtos;
}

public List<PostDto> searchPostWithHashtag(PostSearch postSearch) {
if (postSearch.searchWord().length() < SEARCH_WORD_MIN_LENGTH) {
throw new SearchWordLengthException();
}

List<Long> postIds = postRepository.findPostIdsByHashtagSearchWord(postSearch.searchWord());
List<PostDto> postDtos = postRepository.searchWithHashtag(postSearch.searchWord(), paginationService.calculateOffSet(postSearch.page()), postSearch.size(), postSearch.sort(), postIds);
setHashtagsWithJoin(postDtos);
Expand Down Expand Up @@ -99,16 +74,6 @@ public long getPageCount(int pageNumber, int postQuantityPerPage) {
return pageCount.size();
}

// 현재 시간으로부터 -7일 사이에 있는 게시글중 좋아요 개수가 가장 많은 게시글을 5개까지만 가져온다
public void updatePopularPosts() {
Map<Long, Long> popularPosts = popularPosts().stream()
.collect(toMap(PostDto::getPostId, PostDto::getLikeCount));

// 레디스에 있는 게시글과 popularPosts의 likeCount들을 비교해서 5개만 레디스에 업데이트한다

likeCountRedisRepository.updatePopularPosts(popularPosts);
}

public Map<Long, Long> updatePopularPostsForSchedulerTest() {
return popularPosts().stream()
.collect(toMap(PostDto::getPostId, PostDto::getLikeCount));
Expand All @@ -127,17 +92,4 @@ private void setHashtagsWithJoin(List<PostDto> postDtos) {

postDtos.forEach(postDto -> postDto.inputHashtags(postHashtagMap.get(postDto.getPostId())));
}

// private void addViewCount(Post post) {
// // validation: 레디스에서 인기글을 가져오고, 레디스에 없다면 DB에서 가져오자
// // 인기글이아니라면(레디스에없다면) 조회수 +1 업데이트, 레디스에있으면 레디스에 조회수 카운팅
// if (likeCountRedisRepository.getViewCount(post.getId()) == null) {
// log.info("v1 direct");
// post.plusViewCount();
// } else {
// log.info("v1");
// likeCountRedisRepository.addViewCount(post.getId());
// }
// }
//
}
50 changes: 42 additions & 8 deletions src/main/java/com/example/inflearn/service/post/PostService.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import com.example.inflearn.common.exception.DoesNotExistPostException;
import com.example.inflearn.common.exception.UnAuthorizationException;
import com.example.inflearn.domain.post.domain.PostHashtag;
import com.example.inflearn.infra.repository.post.PopularPostRepository;
import com.example.inflearn.service.hashtag.HashtagService;
import com.example.inflearn.domain.member.Member;
import com.example.inflearn.domain.post.domain.Post;
Expand Down Expand Up @@ -34,6 +35,7 @@ public class PostService {
private final HashtagService hashtagService;
private final PostRepository postRepository;
private final MemberRepository memberRepository;
private final PopularPostRepository popularPostRepository;
private final PostMemoryService postMemoryService;

public void write(PostDto dto, long id) {
Expand Down Expand Up @@ -64,15 +66,17 @@ public void update(PostDto dto, long memberId, long postId) {
post.updateTitleAndContents(dto.getTitle(), dto.getContents());
}

//todo 만약 관리해야할 인기글이 엄청많아진다면? => 성능을 테스트해보고 벌크업데이트성 로직이 추가될것같다.
// public void updateViewCountForPopularPosts(Map<Object, Long> popularPostEntries) {
// for (Entry<Object, Long> entry : popularPostEntries.entrySet()) {
// log.info("entry = {}", entry);
// Post post = postRepository.findById((Long) entry.getKey()).orElseThrow();
// post.updateViewCountFromCache(entry.getValue());
// }
// }
public PostDto postDetail(long postId) {
if (isPopularPost(postId)) {
updateViewCountInMemory(postId);
return popularPostDetail(postId);
} else {
updateViewCountInDb(postId);
return regularPostDetail(postId);
}
}

//todo 만약 관리해야할 인기글이 엄청많아진다면? => 성능을 테스트해보고 벌크업데이트성 로직이 추가될것같다.
public void updateViewCountForPopularPosts() {
for (Entry<Long, Long> memoryCacheEntry : postMemoryService.getViewCountStore().entrySet()) {
log.info("entry = {}", memoryCacheEntry);
Expand All @@ -81,4 +85,34 @@ public void updateViewCountForPopularPosts() {
postMemoryService.initViewCount(memoryCacheEntry.getKey());
}
}


private boolean isPopularPost(long postId) {
return popularPostRepository.findByPostId(postId) != null;
}

private void updateViewCountInMemory(long postId) {
postMemoryService.addViewCount(postId);
}

private void updateViewCountInDb(long postId) {
Post post = postRepository.findById(postId).orElseThrow(DoesNotExistPostException::new);
post.addViewCount();
}

private PostDto regularPostDetail(long postId) {
PostDto postDetail = postRepository.postDetail(postId);
postDetail.inputHashtags(postRepository.postHashtagsBy(postDetail));
postDetail.inputComments(postRepository.commentsBy(postDetail));
return postDetail;
}

private PostDto popularPostDetail(long postId) {
PostDto postDetail = postRepository.postDetail2(postId);
postDetail.inputLikeCount(postMemoryService.likeCount(postId));
postDetail.inputCommentCount(postMemoryService.commentCount(postId));
postDetail.inputHashtags(postRepository.postHashtagsBy(postDetail));
postDetail.inputComments(postRepository.commentsBy(postDetail));
return postDetail;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@
import com.example.inflearn.common.exception.DoesNotExistMemberException;
import com.example.inflearn.common.exception.DoesNotExistPostException;
import com.example.inflearn.common.exception.UnAuthorizationException;
import com.example.inflearn.dto.CommentDto;
import com.example.inflearn.infra.repository.dto.projection.PostCommentDto;
import com.example.inflearn.infra.repository.dto.projection.PostHashtagDto;
import com.example.inflearn.infra.repository.post.PopularPostRepository;
import com.example.inflearn.service.hashtag.HashtagService;
import com.example.inflearn.domain.member.Member;
import com.example.inflearn.domain.post.domain.Post;
Expand Down Expand Up @@ -52,6 +56,12 @@ class PostServiceTest {
@Mock
private PostRepository postRepository;

@Mock
private PopularPostRepository popularPostRepository;

@Mock
private PostMemoryService postMemoryService;

@Mock
private HashtagService hashtagService;

Expand Down Expand Up @@ -172,6 +182,47 @@ class PostServiceTest {
assertThrows(UnAuthorizationException.class, () -> sut.update(dto.toDto(), member.getId(), requestPostId));
}

@Test
void 게시글_상세정보_조회시_조회수가_상승하고_해시태그와_댓글이_객체에_입력된다() {
// given
long postId = 1;
PostDto postDto = createDto("글제목", "글내용", new HashSet<>(), new ArrayList<>());
Post post = createPost(null, "글제목", "글내용");
List<PostHashtagDto> postHashtagDtos = new ArrayList<>(List.of(PostHashtagDto.create("자바"), PostHashtagDto.create("스프링")));
List<PostCommentDto> postCommentDtos = new ArrayList<>(List.of(PostCommentDto.create("댓글1"), PostCommentDto.create("댓글2")));

given(postRepository.findById(postId)).willReturn(Optional.of(post));
given(postRepository.postDetail(postId)).willReturn(postDto);
given(postRepository.postHashtagsBy(postDto)).willReturn(postHashtagDtos);
given(postRepository.commentsBy(postDto)).willReturn(postCommentDtos);

// when
PostDto actual = sut.postDetail(postId);

// then
assertThat(actual.getHashtags().size()).isEqualTo(postHashtagDtos.size());
assertThat(actual.getComments().size()).isEqualTo(postCommentDtos.size());
}

@Test
void 게시글_상세정보_조회시_게시글이_존재하지않으면_예외가_발생한다 () {
// given
long postId = 1;
given(postRepository.findById(postId)).willReturn(Optional.empty());

// when & then
assertThrows(DoesNotExistPostException.class, () -> sut.postDetail(postId));
}

private PostDto createDto(String title, String contents, Set<String> hashtags, List<CommentDto> comments) {
return PostDto.builder()
.title(title)
.contents(contents)
.hashtags(hashtags)
.comments(comments)
.build();
}

private Post createPost(Member member, String title, String contents) {
return Post.builder()
.id(1L)
Expand Down

0 comments on commit f06f5d9

Please sign in to comment.