Jinnie devlog

좋아요 토글 API, REST스럽게 만들기 본문

교육

좋아요 토글 API, REST스럽게 만들기

Jinnnie 2025. 7. 25. 15:52

 

e-커머스 시스템을 만들기 위한 설계 문서를 작성하다가, 좋아요(Likes) 도메인의 요구사항에 "상품에 대한 좋아요 추가/해제 기능은 멱등하게 동작하여야 합니다." 조건이 있는 것을 확인하였다.

멱등하게 라는 용어가 생소하여 찾아보다가 "좋아요" 기능에 생각보다 많은 고민이 들어가야 된다는 것을 알게되었다.

예전에(5년 전) 친구들과 토이 프로젝트를 진행하며, 커뮤니티 서비스에 댓글과 좋아요 기능을 추가했던 경험이 떠올라서 깃허브를 뒤져보았다... 공부하는 김에 이 코드를 기준으로 간단하게 수정 해보아야겠다 생각했다.

    //좋아요 등록
    @ResponseBody
    @RequestMapping(value = "/insertPick", method = RequestMethod.POST)
    public void insertPickPlace(HttpSession session,PickVO pick) throws Exception {
      int userId = Integer.parseInt(String.valueOf(session.getAttribute("userId")));
      pick.setUserId(userId);
      pickService.register(pick);
    }

    //좋아요 취소
    @ResponseBody
    @RequestMapping(value = "/deletePick", method = RequestMethod.POST)
    public void deletePickPlace(HttpSession session,PickVO pick) throws Exception {
      int userId = Integer.parseInt(String.valueOf(session.getAttribute("userId")));
      pick.setUserId(userId);
      pickService.remove(pick);
    }

 

 

위에 코드에는 많은 문제점들이 있는데...

  • setter 사용으로 불변성 깨짐
  • 성공 실패 여부를 알 수 있는 응답이 없고, 예외 핸들링이 없다.
  • 세션에서 사용자 번호를 꺼내서 바로 사용하고 있고, 검증 로직이 없다.
  • REST 원칙상 등록은 POST, 취소는 DELETE로 처리하는게 적절하다. API 경로가 RESTful 하지 않다. (/likes)
  • 좋아요 추가/삭제 시 이미 등록된 좋아요를 중복 등록하거나, 없는 좋아요를 삭제해도 무조건 처리하는 구조이다. (멱등성 고려 안 됨)

좋아요 기능, 토글로 처리하면 될까?

보통 좋아요 기능은 프론트에서 한 번 누르면 좋아요 되고, 다시 누르면 취소된다.

즉, 하나의 버튼이 등록/취소를 토글하는 역할을 한다. 그러다보면 아래와 같이 API를 만들고 싶어진다.

POST /api/likes

 

  • 아직 좋아요를 안 한 상태면 -> 좋아요 등록
  • 이미 좋아요를 했으면 -> 좋아요 취소

이렇게 하나의 API로 둘 다 처리하면, 프론트 입장에서도 간단하고, 빠르게 구현할 수 있다.

 

REST스럽게 생각해본다면?

REST API는 자원의 상태를 HTTP 메서드로 표현한다.

"좋아요는 User가 특정 Product에 대해 좋아요를 눌렀다." 는 관계를 나타낸다.

Restful하게 접근해보자면,

  • 좋아요 등록 → POST /likes
  • 좋아요 취소 → DELETE /likes/{likeId} 혹은 DELETE /likes?userId=1&productId=5

하나의 API로 처리하기 보다는, 두 개의 API로 분리하는 게 REST스럽다.

 

멱등성(Idempotency)

멱등성은 같은 요청을 여러 번 보내도 결과가 같아야 하는 성질을 의미한다.

  • DELETE는 멱등해야 한다. → 이미 좋아요가 없어도 삭제 요청을 여러 번 보내면 "성공"이어야 한다.
  • POST는 멱등하지 않다. → 같은 요청을 여러 번 보내면 중복 데이터가 생길 수 있다.
요청 횟수 결과 멱등성
DELETE /likes/1 1회 좋아요 삭제됨
DELETE /likes/1 2회 여전히 삭제 상태 ✅ 멱등
POST /likes 1회 좋아요 생성됨
POST /likes 2회 좋아요 중복될 수 있음 ❌ 비멱등

 

그런데 토글 API는 한 번 호출하면 좋아요 → 또 호출하면 좋아요 취소 → 또 호출하면 다시 좋아요

만약 네트워크 문제로 프론트에서 같은 요청이 두 번 보내지면 의도하지 않게 좋아요가 사라지거나 생길 수 있다.


코드 수정

위에 작성했던 코드를 수정해보았다.

새로 좋아요가 생성된 경우, 201 Created 상태 코드 반환

이미 좋아요가 존재해서 중복 등록은 하지 않은 경우, 200 OK 코드 반환 

 

좋아요 삭제 요청은 멱등성이 보장되어 이미 없더라도 항상 204 No Content 반환

  // 좋아요 등록 (멱등성 보장)
    @PostMapping
    public ResponseEntity<Void> addLike(HttpSession session, @RequestBody PickRequest request) {
        int userId = getUserIdFromSession(session);
        PickVO pick = PickVO.of(userId, request.productId());

        boolean created = pickService.register(pick) > 0;
        return created ? ResponseEntity.status(HttpStatus.CREATED).build()
                       : ResponseEntity.ok().build();
    }
    
    // 좋아요 취소 (멱등성 보장)
    @DeleteMapping
    public ResponseEntity<Void> removeLike(HttpSession session, @RequestBody PickRequest request) {
        int userId = getUserIdFromSession(session);
        PickVO pick = PickVO.of(userId, request.productId());

        pickService.remove(pick);
        return ResponseEntity.noContent().build();
    }

 

 

정리

좋아요 기능을 생각할 때 REST스럽게 설계하고, 멱등성을 고려한다면 다음과 같은 장점이 있다.

  • 같은 요청을 여러 번 보내도 결과가 일정하기 때문에 API 사용법이 직관적이고 예측 가능하다.
  • 서버는 멱등성 보장을 통해 상태 관리와 데이터 무결성을 책임지고, 클라이언트는 단순히 원하는 동작을 요청하기만 하면 된다.
  • 네트워크 문제로 인해 동일 요청이 중복해서 전달되거나 재전송되어도 안정적이다.

하지만 멘토링을 통해 토글 대신 이와 같이 처리하는 방법이 꼭 장점만 있지는 않다는 것을 깨달았다.

 

좋아요 등록과 취소가 각각 별도의 API로 분리되면, 프론트에서 상태 관리를 더 신경 써야 한다.

예를 들어, 토글 버튼을 누를 때마다 등록/취소 API를 나눠서 호출하고, 실패 시 적절히 복구해야 하므로 구현이 복잡해 질 수 있다.

이로 인해  UX가 일관되지 않거나, 네트워크 지연 시 버튼 상태와 실제 서버 상태가 달라지는 문제가 발생할 수 있어 사용자 경험에 영향을 줄 수도 있다는 생각이 들었다. 

 

결과적으로, RESTful과 멱등성을 지키는 것이 물론 중요하지만 사용자 경험을 고려하여 백엔드와 프론트엔드 간의 협의를 통해 유연하게 적용해야할 것 같다고 생각했다.

 

멘토님이 알려주셨던 사용자 경험과 관련된 프론트 기술

https://tecoble.techcourse.co.kr/post/2023-08-15-how-to-improve-ux-with-optimistic-update/

 

이후에 더 생각해 볼 사항들

좋아요 기능을 구현할 때, 상태 값을 true/false로 관리할지, 아니면 좋아요 생성과 삭제를 위한 insert/delete 작업을 반복할지에 대한 고민

 

'교육' 카테고리의 다른 글

@Transactional 남용 줄이기 도전기 - 두 번의 삽질  (4) 2025.08.08
WIL - 3주차 (Domain Modeling)  (1) 2025.08.03
WIL - 2주차 (Software Design)  (0) 2025.07.25
WIL - 1주차 (TDD)  (0) 2025.07.18
클린아키텍처가 도대체 뭔데...  (0) 2025.07.18