좋아요 멱등성: 올리브영, 무신사, 컬리는 어떻게 다르게 구현했을까?

유수민·2025년 7월 25일
5

지식창고

목록 보기
65/65

개발자라면 한번쯤 들어봤을 멱등성~! 최근에 좋아요를 구현해보려고 하는데 각기 다른 서비스들은 과연 어떻게 구현하고 있을까가 궁금했다. 그래서 오늘은 '좋아요' 기능을 예시로 파고 들어가보려고 한다. 특히 올리브영,무신사,컬리가 멱등성을 보장하는 방식이 다른 것을 확인할 수 있었다.

멱등성이란?

멱등성은 같은 작업을 여러번 수행하더라도 항상 동일하게 유지되는 성질이다. 사용자의 실수로 버튼을 여러번 누르거나, 네트워크 불안정으로 요청이 중복 전송될때 시스템이 오작동하지 않도록 방지하는 중요한 개념이다. 즉, 좋아요 기능을 예시로 들자면, 좋아요를 여러번 눌러도 좋아요 개수가 계속 늘어나거나, 이미 좋아요를 취소했는데 다시 취소 요청이 들어와도 오류가 나지 않아야 한다는 의미이다.

비교

올리브영

: POST (개별 액션 엔드포인트)
올리브영은 좋아요(찜하기) 등록과 취소를 각각 다른 POST 엔드포인트를 통해 명확하게 구분하여 처리한다.

구현 방식 분석

장점

  • 명확한 액션 구분: 각 API가 수행하는 액션(등록/삭제)이 URL에 명시적으로 드러나 개발자가 이해하기 쉽다. API의 목적이 URL에 담겨 직관적이다.

  • 클라이언트의 명시적 제어: 클라이언트가 '등록'과 '취소'를 명확히 구분하여 요청을 보낼 수 있다.

멱등성 보장

POST는 본래 멱등성이 보장되지 않는다. 따라서 올리브영은 서버 측에서 다음과 같은 방식으로 멱등성을 보장할 것이라 '예상' 한다.

  • 등록 요청 시: 이미 해당 상품이 사용자의 찜 목록에 있다면, 중복으로 추가하지 않고 성공 응답을 반환한다. 데이터베이스의 고유 제약 조건(Unique Constraint)을 활용하여 중복 저장을 방지하는 것이 일반적이다.

  • 취소 요청 시: 해당 상품이 찜 목록에 없다면, 삭제할 것이 없으므로 아무것도 하지 않고 성공 응답을 반환한다.

  • 동시성 제어: 여러 번의 요청이 거의 동시에 들어올 경우, 서버는 내부적으로 트랜잭션 또는 락(Lock)을 통해 한 번의 처리만 보장한다.

무신사

: POST (단일 엔드포인트 & Body로 액션 구분)
무신사는 '좋아요(찜하기)' 등록과 취소를 하나의 POST 엔드포인트로 처리하되, 요청 본문(Body)의 데이터로 실제 수행할 액션(등록 또는 취소)을 구분한다.

구현 방식 분석

  • 액션 구분: 등록시
    - 요청 본문 data 내에 event_name: "add_to_wishlist" 포함

  • 액션 구분: 취소 시
    - 요청 본문 data 내에 event_name: "remove_from_wishlist" 포함

장점

  • 단일 API 엔드포인트: 클라이언트가 '찜하기' 관련 기능을 하나의 URL로 관리할 수 있다.

멱등성 보장

POST는 멱등성을 가지지 않으므로, 무신사도 서버 측에서 멱등성을 보장해야 한다. 이는 올리브영의 개별 POST 엔드포인트 방식과 유사할 것이라 예상한다.

  • 요청 본문 분석: 서버는 event_name 값을 확인하여 'add' 요청인지 'remove' 요청인지 파악한다.

  • 현재 상태 확인 및 조건부 처리:
    - add_to_wishlist 요청 시: 이미 찜 목록에 있다면 중복 추가하지 않고 성공 응답.
    - remove_from_wishlist 요청 시: 찜 목록에 없다면 삭제하지 않고 성공 응답.

  • 동시성 제어: 여러 번의 중복 요청에 대해 데이터 정합성을 유지하기 위한 서버 내부 로직(고유 제약, 락 등)이 필요하다.

컬리

: PUT (단일 엔드포인트 & Body로 상태 변경)
컬리는 '좋아요(찜하기)' 상태 변경을 PUT 메서드를 사용하고, 요청 본문(Body)에 최종적인 찜 상태를 true 또는 false로 명시하는 방식을 사용한다.

구현 방식 분석

찜하기 설정 시: 요청 본문 {"is_pick": true}

찜하기 해제 시: 요청 본문 {"is_pick": false}

장점

PUT 메서드의 멱등성 활용: PUT 메서드는 자원의 전체를 갱신하는 의미를 가지며 그 자체가 멱등성을 보장한다. 같은 요청을 여러 번 보내도 자원의 최종 상태(찜 여부)는 항상 동일하게 유지된다. 이는 서버 측 멱등성 구현 부담을 줄여준다.

단일 API 엔드포인트 & 상태 주도: 클라이언트는 현재 찜 상태와 관계없이 원하는 최종 상태만 is_pick: true 또는 is_pick: false로 전달하면 된다. 클라이언트 로직이 비교적 간결하다.

멱등성 보장

PUT 메서드 자체가 멱등성을 보장하므로, 서버는 요청 본문의 is_pick 값에 따라 단순히 해당 상품의 찜 상태를 '설정' 또는 '해제'하면 된다. 중복 요청이 들어와도 결과는 동일하다.

그렇다면, 어떤 방법이 '더' 나을까?

세 가지 방법 모두 멱등성을 성공적으로 보장한다. 어떤 방법이 '더 좋다'고 단정하기는 어려우며, 이는 API 설계 철학, 개발팀의 선호도, 그리고 서비스의 특성에 따라 달라질 수 있다.

  • 올리브영의 POST (개별 액션): API의 명시성과 직관성을 중요하게 생각하는 경우 유리하다. 각 URL이 어떤 액션을 수행하는지 명확하다. POST의 멱등성은 서버 측에서 견고하게 처리해야 한다.

  • 무신사의 POST (단일 & Body 구분): 단일 엔드포인트를 선호하면서도 POST 메서드를 사용해야 할 때 선택할 수 있는 방식이다. 특히 이벤트 로깅 시스템과 연동이 필요할 때 유용할 수 있다. 역시 POST 멱등성은 서버가 보장한다.

  • 컬리의 PUT (단일 & Body로 상태 변경): HTTP 메서드의 멱등성 속성을 적극적으로 활용하여 서버 측 구현의 복잡성을 줄이고 싶을 때 이상적이다. '찜하기' 상태를 특정 리소스의 속성으로 보고 이를 업데이트하는 RESTful 한 방식에 가깝다.

궁극적으로 중요한 것은 어떤 방식을 선택하든 시스템의 신뢰성을 위해 멱등성이 완벽하게 보장되도록 설계하고 구현하는 것이라 생각한다. 다만 흥미로웠던 점은 같은 기능이 각기 서비스마다 다른 형태를 보였다는 것이다. 이를 통해 같은 방향을 추구하지만 정답이 없음을 다시한번 느꼈다.

profile
배우는 것이 즐겁다!

1개의 댓글

comment-user-thumbnail
2025년 7월 30일

좋은 글 감사합니다 👍

답글 달기