카테고리 없음

2024-05-31 [Programmers newsfeed Project (Update HeartStatus)]

Glen_check 2024. 5. 31. 23:14

Problem

금일 팀 프로젝트 진행 중 맞이한 문제는 Post 의 Like 상태를 User 에 맞춰 다르게 반환해주는 것이었습니다.

예시를 들어 문제를 설명해 보면

A 라는 유저가 있고 애플리케이션 내 로그인을 하여 접속한 후 피드를 구경할 때

A 유저가 좋아요를 누른 게시글은 True 를, 좋아요를 누르지 않은 게시글은 False 를 반환합니다.

 

사실 PostLike 관련 비즈니스 로직은 정말 단순하다고 생각 했으나, 의외로 생각해야 하는 부분들이 정말 많았습니다.

 

"Post 내 좋아요를 누른 후 취소 할 때, CRUD 중 어떤 Annotation 을 활용하는 것이 가장 적절한 방법일까?"

"Post 전체 조회, 상세 조회 시 좋아요 수를 보여주는 코드를 어떻게, 어느 도메인의 서비스에 작성하는 것이 좋을까?"

 

이 중 금일 자세히 다루어 볼 문제는 HeartStatus 관련 문제입니다!

 

접근 방식

작성해야 할 코드를 먼저 글로 정리해 보면,

 

1. True, False 를 User 에게 보여주는 곳은 Post Entity 입니다.

2. Post Entity 에 HeartStatus Column 을 Boolean Type 으로 추가하고, 기본 값을 False 로 설정합니다.

3. PostId 내 좋아요를 누른 UserId 를 체크, 현재 사용자의 UserId 가 포함된다면 True 를 반환합니다.

 

"JpaRepository 를 상속받은 PostLikeRepository Interface 안 'findByPostIdAndUserId' 메소드를 정의한 후,

PostService 내 해당 메소드를 활용하여 Post 내 좋아요를 누른 UserId 를 찾아 True 를 반환해주자!"

 

작성한 코드 및 풀이

fun getAllPosts(authUser: AuthUser): List<PostsResponse> {
        val posts = postRepository.findAllByOrderByCreatedAtDesc()

        posts.forEach { post ->
            val like = postLikeRepository.findByPostIdAndUserId(post.id!!, authUser.id)
            post.heartStatus = like != null
        }
        return posts.map { PostsResponse.from(it) }
    }

 

모든 Post 들을 리스트 형태로 가져와 애플리케이션 유저들에게 보여줄 때 HeartStatus 도 함께 반환할 것이기 때문에 'getAllPosts' 메소드 코드를 가져온 점 참고 부탁드리며, getAllPosts 메서드를 자세히 풀어보려 합니다.

 

parameter

 

'authUser: AuthUser' 현재 인증된 사용자 객체를 의미합니다.

 

Return Type

'List<PostsResponse>' 모든 Posts 를 유저들에게 보여줘야 하기 때문에, 최종적으로 PostsResponse 객체들의 리스트를 반환합니다.

 

Get Posts

'val posts = postRepository.findAllByOrderByCreatedAtDesc(): postRepository' 를 사용하여 모든 게시물을 조회합니다. 이 때 게시물들은 생성일자(createdAt)를 기준 내림차순으로 정렬됩니다.

 

좋아요 상태 확인 및 설정

'posts.forEach { post -> ... }' 각 게시물에 대해 반복 작업을 수행,

'val like = postLikeRepository.findByPostIdAndUserId(post.id!!, authUser.id): postLikeRepository' 를 사용하여 '현재 사용자(authUser.id)가 현재 확인중인 게시물(post.id)에 좋아요를 눌렀는지 조회'합니다.

(like가 null이 아니라면 현재 사용자가 반복문을 통해 받아온 Post 를 Like 한 것입니다.)

 

'post.heartStatus = like != null' like 가 null이 아니라면 post의 heartStatus를 true로 설정합니다. 그렇지 않으면 false 로 설정합니다.

(해당 코드를 if 문으로 풀어쓰면 다음과 같습니다!)

if (like != null) {
    post.heartStatus = true
} else {
    post.heartStatus = false
}

 

PostsResponse 변환 및 반환

'return posts.map { PostsResponse.from(it) }' posts 리스트의 각 post 객체를 PostsResponse 로 변환하여 새로운 리스트로 반환합니다. map 함수는 리스트의 각 요소를 변환하여 새로운 리스트를 생성합니다.

 

자체 리팩토링

Post Entity 에서 Id 를 아래와 같이 받다 보니 위 코드에서 '!!' 연산자를 사용하여 강제로 'nullable' 타입을 'non-nullable' 타입으로 변환을 시켜야만 했습니다..

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
val id: Long? = null

 

그런데 해당 연산자 사용은 최대한 지양하라고 익히 들어왔기에 자세히 찾아보니 이는 런타임에 'NullPointerException' 을 유발할 수 있으므로 안전하지 않다고 합니다.

그래서 생각한 방법이 'null 값이 없는 것만 받아온다는 조건을 걸 수 없을까?' 였고, if (variable != null) 과 같은 표현으로 쓰인다는 let 을 활용하여 코드를 리팩토링 해보았습니다.

 

   posts.forEach { post ->
  		// post 중 Id가 있는 것들만 let을 실행
        post.id?.let { postId ->
        	// 각 postId 좋아요 목록에 현재 사용자의 Id 가 있는지 확인하는 작업
            val like = postLikeRepository.findByPostIdAndUserId(postId, authUser.id)
            // like 가 null 이 아닐 경우 true, null 일 경우 false
            post.heartStatus = like != null
        } ?: throw IllegalStateException("Post ID should not be null")
    }
    return posts.map { PostsResponse.from(it) }

 

위의 코드에서는 foundPost.id가 null이 아닌 경우에만 let 이 실행되며, 그렇지 않은 경우에는 예외가 발생합니다.

이렇게 하면 '!!' 연산자를 사용하지 않고도 foundPost.id가 null 이 아님을 보장하여 해당 연산자를 사용하지 않아도 컴파일이 가능해졌습니다.

 

그런데 이게 잘 맞는지는 모르겠습니다.. 혹여나 더 좋은 코드가 있다면 조언 부탁드리겠습니다.