지난번에 이어 이번에는 피드 좋아요 부분을 작성할 예정이다.
다양한 웹사이트에서 좋아요 토글을 테스트해본 결과, 큰 서비스 들은 대부분 좋아요 쿼리와 좋아요, 싫어요 쿼리 이렇게 두 개가 있어서 우리도 그렇게 분리하기로 했다.
노션은 추후 업데이트가 필요한 부분이다.
이 api 가 호출 되면 무엇부터 해주어야 할까?
1. like 부분의 엔티티를 생성해서 인서트 해주면 될 거 같다.
음? 지난번보다 생각 보다 간단하다.
예외처리를 생각해보자.
정책에 명시된 것부터 나열해보자.
1. 토큰과 로그인 유저가 일치하지 않는 경우,
2. 피드 가 존재하지 않는경우?,3. 피드가 이미 삭제된 경우 ?
위 2 피드는 하나의 예외처리로 묶을 수 있을 거 같다.
음 이미 좋아요 가 눌러진 경우라면? 좋아요 테이블 내에서 중복 여부도 체크해야 할 거 같다.
마땅히 생각나는 추가 예외적인 경우가 없는 것 같다.
우리 동료분은 그룹 내에서 좋아요 여부를 판별해야 해서 생각보다 예외처리 가 많다. 하 복 받았네 ㅋㅋ
바로 지난번 날려준 pr 머지해주고 컨트롤러 테스트 코드부터 작성해보자.
modified: src/main/java/com/workduo/member/content/service/impl/MemberContentServiceImpl.java
modified: src/main/resources/application-dev.properties
modified: src/test/java/com/workduo/member/content/controller/MemberContentControllerTest.java
브런치 생성해주고 , 스테이터스 찍어보니 impl 이랑 Test 가 약간 수정이 되어있다. 단순한 줄 바꿈 정도이니 바로 테스트 코드 작성하러 가자.
지난번 테스트와 다를 바 없다. 네스티드를 이용해 새로운 api 테스트임을 명시해주고, 바로 컨트롤러 테스트를 위한 코드를 작성했다.
컨트롤러 작성하러 가 보자.
컨트롤러 위에 저렇게 코멘트를 박아주면 타인이 읽을 때 보다 빠르게 파악하기 좋다.
물론 함수 명도 동일한 의미를 지녀야 해서 굳이 필요한가 싶지만 한국인이라 그런지 나는 한글이 가장 먼저 눈에 들어온다. ㅎㅎㅎ
서비스 코드는 미리 이름을 정해 놓고, 주석처리했다. 안 하고 돌리면 컴파일 에러니 주석 처리를 해주자.
기분 좋게 컨트롤러 테스트가 통과된다 ㅎㅎ
예외 처리 부분은 서비스 쪽에서 테스트할 것이니 바로 서비스 테스트 코드를 작성하러 가 보자.
@Test
@DisplayName("멤버 피드 좋아요 실패 [로그인 유저 미 일치]")
public void doesNotMatchUser() throws Exception{
//given
//when
//then
}
@Test
@DisplayName("멤버 피드 좋아요 실패 [피드가 삭제된 게시글 인 경우]")
public void feedDeleted() throws Exception{
//given
//when
//then
}
@Test
@DisplayName("멤버 피드 좋아요 실패 [피드가 존재하지 않는 경우]")
public void feedDoesNotExist() throws Exception{
//given
//when
//then
}
@Test
@DisplayName("멤버 피드 좋아요 실패 [이미 피드 를 좋아요 한 경우]")
public void feedLikedAlready() throws Exception{
//given
//when
//then
}
@Test
@DisplayName("멤버 피드 좋아요 성공")
public void feedLikeSuccess() throws Exception{
//given
//when
//then
}
총 5개 정도 테스트하면 될꺼같다 이 테스트 코드를 작성하면서 의문이 들었다.
이미 좋아요 를 한 경우를 예외처리 가 굳이 필요한가 이다.
Return으로 함수를 종료하고 200으로 리턴하는 건 어떤가? 왜냐하면 프런트에서는 좋아요 캐시 처리한다고 가정해보자.
그렇다면? 이미 화면에 업데이트하고 쿼리가 날아간다. 쿼리의 리턴 값 여부에 상관없이 미리 예상해서 업데이트를 해버린 것이다.
이렇게 되면 굳이 예외처리가 필요한가 부분에서 의문이 든다. 이 부분은 pr에 포함해서 날려야겠다.
일단 정책 이 저렇게 정해져 있으니 동료분 코드와 통일성 있게 예외처리를 진행 해주자.
1. 토큰 인증 메일과 로그인 유저 미일치
지난번 테스트 코드와 동일하다
딱히 뭐 추가해줄 부분이 없다 서비스 코드 부분에도 이미 유저 발리 데이팅 이 프라이빗 함수로 구현되어 있으니 그냥 그거 가져다가 사용하면 된다.
@Override
public void contentLike(Long contentId) {
Member m = validCheckLoggedInUser();
}
ㅎㅎ 특별할 것 없는 member의 이메일은 매번 jwt 인증을 태울 때마다 컨택스트 홀더에 저장해놓고 사용한다.
따라서 별다른 파라미터 없이 저렇게 멤버 검증이 가능해진다.
스무스하게 테스트가 바로 통과된다.
2. 피드 가 존재하지 않는 경우
지난번 테스트 코드와 동일하다
딱히 뭐 추가해줄 부분이 없다 서비스 코드 부분에도 이미 멤버 컨 탠트 가져오는 함수가 구현되어 있으니 그냥 그거 가져다가 사용하면 된다
이런 멤버나 컨 탠트 가져오는 부분은 나중에 join 쿼리로 가져오게 리팩터링을 해줘야겠다. 굳이 2개로 나눠서 보낼 필요가 없어 보인다.
@Override
public void contentLike(Long contentId) {
Member m = validCheckLoggedInUser();
MemberContent mc = getContent(contentId);
}
딱히 뭐 유별난 게 없어 바로 다음 테스트 코드 작성하러 가자.
3. 피드 가 삭제된 경우
삭제된 경우 우리 워크 듀오 같은 경우는 피드 가 삭제되어도 테이블 상에서 지우지 않는다.
유저의 복구 신청이 있다면?이라는 가정을 두고 테이블 상에서 지우지 않는다. 따라서 칼럼 하나를 따로 지정해주어
삭제 여부를 판단한다.
deleteYn 저부 분만 트루가 되어서 들어간다면 정확하게 삭제된 피드 에러를 터트려야 한다.
@Override
public void contentLike(Long contentId) {
Member m = validCheckLoggedInUser();
MemberContent mc = getContent(contentId);
if(mc.isDeletedYn()){
throw new MemberException(MEMBER_CONTENT_DELETED);
}
}
ㅋㅋㅋㅋ 매우 심플하다. 코드를 작성하면서 함수를 작성한 나 매우 칭찬한다 ㅎㅎ
가볍게 3번째 테스트 케이스 또한 통과된다.
4. 이미 좋아요 가 된 경우
단순한 중복 여부의 체크를 위해 다양하게 쿼리를 짠다 단순히 구글 검색으로 duplicate check performance in sql로 검색만 하더라도 정말 다양한 방법으로 구현한다. find count exist 다양한 포스트를 읽다 보면 마무리 글에 강조하는 부분이 있다. exist 체크를 위해 count를 사용하지 말라는 글들이다. 궁금하면 한번 검색해서 읽어보는 것을 권장한다. 한글 영어 다양한 자료들이 있다.
레포지토리 함수 와, ErrorCode 작성이 필요하다 저 빨간불부터 지우자.
//Repository
boolean existsByMemberAndMemberContent(Member m,MemberContent mc);
//ErrorCode
MEMBER_CONTENT_LIKE_ALREADY(HttpStatus.BAD_REQUEST,"❌ 이미 좋아요 한 게시글 입니다."),
//Service
@Override
public void contentLike(Long contentId) {
Member m = validCheckLoggedInUser();
MemberContent mc = getContent(contentId);
if(mc.isDeletedYn()){
throw new MemberException(MEMBER_CONTENT_DELETED);
}
boolean exists = memberContentLikeRepository.existsByMemberAndMemberContent(m, mc);
if(exists){
throw new MemberException(MEMBER_CONTENT_LIKE_ALREADY);
}
}
기분 좋게 테스트가 통과 가 된다 ㅎㅎ 자 대망의 성공 케이스를 작성하자.
5. 멤버 피드 좋아요 성공
음 좋아요는 마지막에 저장되는 레포지토리가 실행되는 부분만을 확인하고 싶다.
뭐 정확히 데이터가 들어가는지 확인하고 싶으면 캡처를 써서 메서드의 저장의 데이터가 올바르게 들어가는지 테스트 확인하는 것도
하나의 방법이 될 수 있으니 다음 테스트 세이브에서 활용해볼까 한다.
@Override
public void contentLike(Long contentId) {
Member m = validCheckLoggedInUser();
MemberContent mc = getContent(contentId);
if(mc.isDeletedYn()){
throw new MemberException(MEMBER_CONTENT_DELETED);
}
boolean exists = memberContentLikeRepository.existsByMemberAndMemberContent(m, mc);
if(exists){
throw new MemberException(MEMBER_CONTENT_LIKE_ALREADY);
}
memberContentLikeRepository.save(MemberContentLike.builder()
.member(m)
.memberContent(mc)
.build());
}
크 테스트 도 모두 통과한다.
자 서버를 띄워서 테스트해보기 전 총 나가야 하는 쿼리 수를 알아보자.
멤버를 가져오기 위한 쿼리 1방, 컨 탠트 확인 쿼리 1방, 컨탠트 라이크 쿼리 존재여부 1방, 컨탠트 라이크 세이브 1방 총 4개이다.
//멤버 확인
select
member0_.member_id as member_i1_12_,
member0_.created_at as created_2_12_,
member0_.updated_at as updated_3_12_,
member0_.deleted_at as deleted_4_12_,
member0_.email as email5_12_,
member0_.member_status as member_s6_12_,
member0_.nickname as nickname7_12_,
member0_.password as password8_12_,
member0_.phone_number as phone_nu9_12_,
member0_.profile_img as profile10_12_,
member0_.status as status11_12_,
member0_.username as usernam12_12_
from
member member0_
where
member0_.email=?
// 멤버 컨탠트 가져오기
select
membercont0_.member_content_id as member_c1_15_0_,
membercont0_.created_at as created_2_15_0_,
membercont0_.updated_at as updated_3_15_0_,
membercont0_.content as content4_15_0_,
membercont0_.deleted_at as deleted_5_15_0_,
membercont0_.deleted_yn as deleted_6_15_0_,
membercont0_.member_id as member_10_15_0_,
membercont0_.notice_yn as notice_y7_15_0_,
membercont0_.sort_value as sort_val8_15_0_,
membercont0_.title as title9_15_0_
from
member_content membercont0_
where
membercont0_.member_content_id=?
// 라이크 존재여부 exsit 체크여서 limit 1 로나감
select
membercont0_.member_content_like_id as col_0_0_
from
member_content_like membercont0_
where
membercont0_.member_id=?
and membercont0_.member_content_id=? limit ?
// 인서트
insert
into
member_content_like
(member_id, member_content_id)
values
(?, ?)
서비스 부분의 코드가 살짝 더러우니 리팩터링을 진행하자.
조금 더 괜찮아진 것 같다. 멤버와 컨 탠트 그리고 검증 이후 저장 깔끔하다. 의심의 여지없이 저장하는 함수이다.
매우 만족스럽게 이번에도 구현 완료 , 내일 취소까지 마무리해서 pr 올려야겠다.
긴 글 읽어주셔서 감사합니다.
'사이드 프로젝트 > 워크듀오-개발일지' 카테고리의 다른 글
멤버 피드 댓글 삭제 API (1) | 2022.09.28 |
---|---|
멤버 피드 댓글 업데이트 API (0) | 2022.09.28 |
멤버 피드 댓글 조회 API (1) | 2022.09.28 |
멤버 피드 댓글 작성 API (0) | 2022.09.28 |
멤버 피드 삭제 API 구현 (1) | 2022.09.25 |