-
사용자가 '좋아요' 버튼을 두 번 누르면 어떻게 될까?Develop/Spring 2025. 7. 25. 13:51

TL;DR
안전한 '좋아요' 기능을 위해, 토글(toggle) 방식의 API 대신 중복 요청에도 결과가 동일한 멱등성 설계(예: POST로 생성, DELETE로 삭제)를 적용해야 합니다.
1. '좋아요' 기능, 왜 멱등성이 중요할까? 🤔
'좋아요' 버튼을 누르는 간단한 동작 이면에는 네트워크 오류나 사용자의 중복 클릭과 같은 잠재적 위험이 존재합니다. 만약 API가 이러한 중복 요청을 제대로 처리하지 못하면, 사용자의 의도와 달리 '좋아요'가 즉시 취소되거나 데이터가 꼬이는 문제가 발생할 수 있습니다.
멱등성이란?
동일한 요청을 여러 번 보내도 언제나 같은 결과를 만들어내는 성질입니다. 신뢰성 있는 API를 만들기 위해 멱등성 설계는 선택이 아닌 필수입니다.
- ✅ 멱등성이 보장되는 메서드: GET, PUT, DELETE
- GET은 데이터를 조회만 하므로 항상 멱등성을 가집니다.
- PUT은 특정 리소스를 지정된 상태로 교체하므로, 여러 번 호출해도 최종 상태는 같습니다.
- DELETE는 특정 리소스를 삭제하므로, 반복 호출해도 "이미 삭제됨" 상태가 유지됩니다.
- ❌ 멱등성이 보장되지 않는 메서드: POST
- POST는 주로 새로운 리소스를 '생성'하기 때문에, 호출할 때마다 새 데이터가 만들어질 수 있습니다.
토글' 방식 API의 숨겨진 위험
가장 흔히 저지르는 실수는 POST 메서드 하나로 '좋아요'와 '취소'를 모두 처리하는 토글(toggle) 방식을 사용하는 것입니다.
- 요청 ①: 사용자가 '좋아요' 버튼을 누름 (POST /api/v1/posts/123/like)
- 서버 처리: 서버가 요청을 받아 '좋아요' 상태로 변경함 (likeCount: 1)
- 네트워크 오류: 서버의 성공 응답이 사용자에게 도달하기 전에 타임아웃 발생
- 재시도 ②: 클라이언트는 요청이 실패했다고 판단하고, 같은 POST 요청을 재전송
- 예상치 못한 결과: 서버는 두 번째 요청을 받아 '좋아요' 상태를 다시 토글하여 '취소' 상태로 만듦 (likeCount: 0)
결과적으로 사용자는 '좋아요'를 눌렀지만, 시스템은 '좋아요 취소'로 처리하는 심각한 데이터 불일치 문제가 발생합니다.
멱등성을 보장하는 API 설계 패턴 🚀
안전한 API를 설계하려면 "그냥 반대로 바꿔줘(토글)"가 아니라, "이 상태로 만들어줘"라고 명확히 지시해야 합니다.
멱등성을 보장하기 위해선 "현재 상태가 어떻든 그냥 반대로 바꿔줘(토글)"가 아니라, "이 상태로 만들어줘"라고 명확하게 요청해야 합니다.
패턴 1: PUT과 DELETE를 이용한 리소스 제어
가장 REST 원칙에 충실한 정석적인 방법입니다. '특정 유저의 좋아요' 자체를 하나의 리소스(Resource)로 보고, 상태를 명확하게 제어합니다.
- 좋아요 생성/교체: PUT /api/v1/posts/{postId}/like
- 역할: 특정 게시물(postId)에 대한 나의 '좋아요' 리소스를 생성하거나 현재 상태로 유지합니다.
- 로직: 서버는 요청을 받으면 해당 '좋아요'가 없으면 생성하고, 이미 있다면 아무것도 하지 않거나 덮어씁니다. 몇 번을 호출해도 결과는 항상 '좋아요' 상태입니다.
- 좋아요 삭제: DELETE /api/v1/posts/{postId}/like
- 역할: 나의 '좋아요' 리소스를 삭제합니다.
- 로직: DELETE는 그 자체로 멱등성을 가지므로, 반복해서 호출해도 안전하게 '좋아요 취소' 상태를 보장합니다.
패턴 2: POST와 DELETE를 이용한 명시적 생성 / 삭제
PUT의 '교체'라는 의미 대신 POST를 '생성'의 의미로만 한정하여 사용하는 실용적인 방법입니다.
- 좋아요 생성: POST /api/v1/posts/{postId}/like
- 역할: 특정 게시물(postId)에 나의 '좋아요' 리소스를 생성합니다.
- 로직: 서버는 '좋아요'가 존재하지 않을 경우에만 새로 생성합니다. 이미 '좋아요'가 존재한다면 추가 작업을 하지 않고 성공으로 응답합니다. 따라서 여러 번 호출되어도 '좋아요'는 단 한 번만 생성됩니다.
- 좋아요 삭제: DELETE /api/v1/posts/{postId}/like
- 역할: 나의 '좋아요' 리소스를 삭제합니다.
- 로직: 패턴 1과 동일하게 멱등성을 완벽하게 보장합니다.
결론: 어떤 방식을 선택해야 할까?
구분 PUT / DELETE (패턴 1) POST / DELETE (패턴 2) 토글 POST (위험) 멱등성 ✅ 보장 ✅ 보장 ❌ 보장 안됨 REST 원칙 매우 높음 높음 낮음 구현 복잡도 보통 낮음 (직관적) 매우 낮음 추천 REST 원칙을 엄격하게 지킬 때 대부분의 상황에 추천 사용하지 않는 것을 권장 애플리케이션의 안정성과 데이터 정합성이 중요하다면, 반드시 멱등성을 보장하는 방식으로 설계해야 합니다.
- 가장 추천하는 실용적인 방법은 POST / DELETE 입니다. REST의 원칙을 충분히 지키면서 PUT보다 구현이 직관적이고, 멱등성을 완벽하게 보장할 수 있습니다.
- 매우 엄격한 REST API를 추구한다면 PUT / DELETE 가 가장 이상적인 설계입니다.
- ✅ 멱등성이 보장되는 메서드: GET, PUT, DELETE