서론

풀스택으로 커뮤니티 페이지를 개발하면서 게시글 좋아요 API를 제작하며 발생한 고민에 대한 글입니다.

특히, HTTP Method를 결정하는 상황에 대한 고찰과 개인적인 결론으로 이루어진 글입니다.

 

현재 좋아요 ERD (하드 딜리트 상황)

  • `ID(PK)`
  • `userId`
  • `postId`

좋아요 생성

1. `POST`

`POST` 메소드는 새로운 데이터를 생성하는 메소드이다.

 

즉, “좋아요 생성”을 담당하는 해당 API에서 가장 자연스러운 선택으로 보인다.
2. `PUT`

`PUT` 메소드는 기존 데이터를 대체하는 메소드이다.

PUT 메소드는 데이터가

  • 없다면 새로운 데이터를 생성한다.
  • 있다면 해당 데이터를 교체한다.

 

이때 자연스럽게 발생하는 두가지 의문이 있다.

1. `PUT`으로 “좋아요 생성 API”를 구현하면 돼지 않을까?

 

2. (프론트엔드 개발자로의 입장에서)

`POST` 메소드는 멱등성을 만족하지 않기 때문에 같은 창을 여러개 띄워두고 여러번 같은 요청을 보내면 `{userId, postId}` 가 같은 여러개의 데이터가 생길 수 있지 않나?

 

극단적으로, 같은 창을 100개 켜두고 좋아요를 계속 누르면, 같은 데이터가 100번이나 생성되는 심각한 오류가 발생하는 것이다.

  • 이는, 좋아요 테이블의 PK가 `{userId, postId}` 의 복합키로 설정돼지 않았을 때의 가정이다.
    • `{userId, postId}` 가 복합키로 설정되어 있으면 데이터베이스에서 오류를 반환하여 문제가 없다.
    • 단, JPA와 같은 ORM 상에서 복합키를 사용하는 것은 번거로운 작업이다. (검색해보세요!)
“누가 악의적으로 창을 여러개 켜두고 좋아요를 눌러? 🤬 라고 생각할 수 있다. 
개발자는 모든 상황에 대처해야 한다.
적당한 개발은 없고, 있다면 발견하지 못한 것이다.

같은 인스타계정을 쓰는 상상속의 여자친구와 당신이 우연히 각자의 핸드폰과 노트북으로 같은 스토리에 동시에 좋아요를 누르는 경우가 있을 수도 있지 않은가?

여러 창을 켜두고 좌하단의 좋아요 버튼을 클릭하면 어떻게 될까...?

 

 

그럼에도 불구하고 “좋아요 생성”에는 POST를 사용해야 한다고 생각한다.

 

위에서 발생했던 의문들에 대한 각각의 답변은

1. Q: PUT으로 “좋아요 생성 API”를 구현하면 돼지 않을까?

A: 맞다. PUT으로 구현해도 된다.

 

단, RESTFul한 API 설계를 위해서는 상황에 맞는 적절한 메소드를 사용해야 하므로, 좋아요 “생성”에는 POST 요청으로 데이터를 생성해야 한다.

 

2. Q: `{userId, postId}` 가 같은 여러개의 데이터가 생길 수 있지 않나?

A: `POST` 메소드에서 데이터가 여러번 저장되는 상황은 `Service` 로직에서 중복 검사를 통해 처리해야 한다.

// @Service 에서 중복 검사
if (likeRepository.existsByUserIdAndPostId(userId, postId)) {
    // 이미 좋아요한 경우 → 아무 작업 안 함 
    return;
}

Like like = new Like(userId, postId);
likeRepository.save(like);

 

 

좋아요 삭제

좋아요 생성에서 `POST`를 쓰기로 했으니, `DELETE`를 사용하는 것이 RESTFul한 설계이다.

 

 


 

소프트 딜리트 상황

소프트 딜리트는 데이터 보존과 미래지향적 기획을 위해 삭제 요청된 데이터를 데이터베이스 상에서 삭제하는 대신, 삭제되었음을 표기한 뒤 해당 데이터를 보존하는 방법이다.

 

보통, `deleted_at (TIMESTAMP)` 과 같은 추가 필드로 삭제된 시간을 명시하여 미삭제 데이터와 구분한다.

 

그렇다면, 소프트 딜리트를 해야하는 상황에서도 `POST` (좋아요 생성), `DELETE` (좋아요 삭제)를 사용해야 할까?

 

2가지 방법이 있다.

1. `POST`(좋아요 생성) +`PATCH`(좋아요 삭제, ❌ DELETE )

기존 DELETE를 사용하던 방식에서 PATCH를 사용하여 데이터를 삭제하는게 아닌, 데이터를 변경하는 방향이 RESTFul한 설계로 보인다.

 

2. `PATCH`(좋아요 생성) + `PATCH` (좋아요 삭제) → 하나의 `PATCH` +  `body` (현재 상태로 구분)

해당 방법은 토글 형식으로, 클아이언트가 현재 좋아요 상태를 body에 제공해주면, 이를 토대로 서버에서 좋아요를 생성 / 삭제중 선택하여 처리하는 방법이다.

 

위 방법을 사용하면

  • 프론트엔드 개발자는 하나의 API만 다루어 편하게 개발 가능하다.
  • 서버 개발자는 클라이언트의 상태에 따른 로직 분리를 하여야 한다.

 

사실 두가지 방법 모두 가능하다.

 

정답은 없다. 설계자가 어떻게 설계하느지의 문제라고 생각한다.

 

 

단, RESTFul한 설계 방식으로는 개인적으로 1번 방법이 좋다고 생각한다. 

또한, 하나의 API에서 필드로 로직을 구분하기보다 관심사 분리가 확실하도록 API 자체를 분리하는 방법이 좋다고 생각한다.

 

2번 방식은 호기심이 많은 개발자가 (마치 나) 후임으로 들어오면 꼭 한번씩 물어볼 것 같은 설계이다.

(선배님 왜 PATCH 하나로 좋아요 생성, 삭제를 구현하셨나요?, 물론 같이 일할 확률은 극히 드물다.)

 

마무리

오늘은 간단하지만 기초가 되는 API의 메소드 설계 과정에 대한 고민을 담아보았습니다.

누구나 할 수 있는 고민이지만 누구도 하지 않는 고민이기도 한것 같습니다.

이런 작은 고민 하나하나가 모여서 단단한 개발자가 되는게 아닐까 생각합니다.

 

문제를 공유해주고 늦게까지 함께 고민해준 `jenny`, 현업자의 시선에서 따뜻한 피드백을 해준 `kevin` 고마워요!

+ Recent posts