게시글 임시 저장 기능을 구현하면서...

chiyongs·2022년 9월 21일
22
post-thumbnail

게시글 임시 저장 기능은 블로그 기능 및 많은 글 쓰기를 지원하는 플랫폼에서는 필수적으로 필요한 기능입니다.
Velog처럼 블로그들의 주 기능은 바로 포스팅입니다.
글 작성 중 PC가 갑작스럽게 종료되거나 하는 이슈로 작성하던 글이 통째로 사라진 경험을 해보셨나요??
적지 않은 분들이 과제나 문서를 날려본 경험이 있으실 겁니다. 제가 그랬어요.. 마감 2분전에 저장을 안해서.. 😥

하지만, 요즘은 많은 플랫폼에서 임시 저장 기능을 제공합니다.
작성 중이던 글이 사라지는 경험을 하게 된다면 정말 끔찍하기 때문이죠. 🤬

최근 진행한 재밌는 프로젝트에서 임시 저장 기능을 구현했습니다.
편하게 임시 저장 기능을 사용하던 유저 입장에서 구현해야 하는 개발자 입장이 되니 고려해야 할 점들이 많았습니다.
임시 저장 기능을 구현하면서 고민한 내용과 해결해나간 내용을 간략하게 정리하며 적은 글이니 재밌게 읽어주시면 감사하겠습니다!!

임시 저장 기능을 구현한 이유?

게시글 임시 저장 기능을 구현하게 된 계기는 "게시글에 첨부한 이미지를 어떻게 관리할 것인가" 부터 출발했습니다.
게시글 작성 중 사용자가 이미지를 업로드하면 저장소(AWS S3)에 바로 이미지가 업로드되는 방식으로 설계했습니다.
하지만, 유저가 게시글 작성 도중 이미지를 업로드한 후 게시글을 저장하지 않고 페이지를 떠난다면 게시글이 저장되지 않기 때문에 업로드된 이미지들이 연관관계가 없는 고아가 되는 문제가 발생했습니다.
고아가 된 이미지들은 해당 이미지를 업로드한 게시글이 존재하지 않기 때문에 삭제 작업이 제대로 이루어지지 않게 되는 것이죠.
고아 이미지 문제를 해결하기 위해 유저가 이미지를 업로드할 때 작성중인 게시글을 임시저장하는 방법을 시도했습니다.

게시글을 저장할 때는 해당 게시글의 id(postId), 제목(title), 내용(content)를 client에서 받아와 저장합니다.

[게시글 저장 형식]

{
  "postId" : "7",
  "title" : "안녕하세요",
  "content" : "임시 저장해주세요!"
}

임시 저장 기능을 설계하면서 한 고민들 🤔

참고 : 해당 게시글의 id(postId)의 유무로 새롭게 작성하는 게시글과 임시 저장 게시글, 출간한 게시글을 구분했습니다.
또한, 임시 저장된 게시글과 출간한 게시글을 구분하기 위해 isPublished라는 boolean형 변수를 사용했습니다.

임시 저장 기능을 구현하기 위해 고려한 경우의 수는 다음과 같습니다.

  1. 게시글 id가 없고, 이미지를 업로드하는 경우
    Request : postId == null && isPublished == false
    Response : postId == [생성한 임시 저장 게시글 엔티티의 id] && isPublished == false
  2. 게시글 id가 없고, 임시저장 버튼을 눌러 임시저장하는 경우
    Request : postId == null && isPublished == false
    Response : postId == [생성한 임시 저장 게시글 엔티티의 id] && isPublished == false
  3. 게시글 id가 없고, 게시글을 출간하는 경우
    Request : postId == null && isPublished == false
    Response : postId == [생성한 임시 저장 게시글 엔티티의 id] && isPublished == true
  4. 저장된 임시 게시글을 수정할 때, 이미지를 업로드하는 경우
    Request : postId == [임시 저장 게시글 엔티티의 id] && isPublished == false
    Response : postId == [임시 저장 게시글 엔티티의 id] && isPublished == false
  5. 저장된 임시 게시글을 수정한 후, 임시저장 버튼을 눌러 임시저장하는 경우
    Request : postId == [임시 저장 게시글 엔티티의 id] && isPublished == false
    Response : postId == [임시 저장 게시글 엔티티의 id] && isPublished == false
  6. 저장된 임시 게시글을 출간하는 경우
    Request : postId == [임시 저장 게시글 엔티티의 id] && isPublished == false
    Response : postId == [출간된 게시글 엔티티의 id] && isPublished == true
  7. 출간한 게시글을 수정할 때, 이미지를 업로드하는 경우
    Request : postId == [출간된 게시글 엔티티의 id] && isPublished == true
    Response : postId == [출간된 게시글의 사본 게시글 엔티티의 id] && isPublished == true
  8. 출간한 게시글을 수정할 때, 임시저장 버튼을 눌러 임시저장하는 경우
    Request : postId == [출간된 게시글 엔티티의 id] && isPublished == true
    Response : postId == [출간된 게시글의 사본 게시글 엔티티의 id] && isPublished == true
  9. 출간한 게시글을 수정한 후, 출간하는 경우
    Request : postId == [출간된 게시글 엔티티의 id] && isPublished == true
    Response : postId == [출간된 게시글 엔티티의 id] && isPublished == true

출간된 게시글의 경우, 수정 또는 이미지 업로드 행위가 발생하면 출간된 게시글의 사본을 만들어 해당 사본 게시글에 변경 사항을 적용했습니다.

원본 게시글과 사본 게시글을 구분하기 위해 게시글 엔티티에 originalPostId 변수를 추가하여 원본 게시글 엔티티의 id를 가지도록 했습니다.
originalPostId와 해당 게시글 엔티티의 id가 동일하다면 원본, 동일하지 않다면 사본으로 구분했습니다.

위와 같이 9가지 경우에 대한 코드를 구현하면서 중복되거나 유사한 코드가 너무 많다고 느껴졌습니다.

예를 들어

  • 임시 게시글을 출간하거나, 출간한 게시글을 다시 출간하는 경우
  • 임시 게시글을 임시 저장하거나, 출간한 게시글을 다시 임시 저장하는 경우
  • 임시 게시글을 수정할 때 이미지를 업로드하는 경우와 출간한 게시글을 수정할 때 이미지를 업로드하는 경우

유사한 경우들을 나누지 않고 하나로 처리할 수 있을 것 같은 느낌이 들었습니다.

리팩토링 🔧

임시 저장된 게시글과 출간된 게시글은 모두 postId를 가지고 있기 때문에 하나의 경우에서 isPublished 변수를 사용한 분기 처리로 코드를 리팩토링했습니다.

  • 새로 작성하는 게시글에 이미지를 업로드하면서 임시 저장하는 API
    • client로부터 받아온 이미지를 AWS S3에 업로드하고 리턴받은 URL을 response로 전달
    • 게시글을 임시 저장하는 이유??
      • 해당 게시글에 포함되어야 하는 이미지들의 URL을 연관관계로 가지고 있어야하기 때문에 임시 게시글이 필요!
  • 새로 작성하는 게시글을 임시 저장하는 API
  • 새로 작성하는 게시글을 발행하는 API
  • 임시 게시글을 수정하고 이미지를 업로드하거나, 발행된 게시글의 사본을 만들고 해당 사본을 수정하고 이미지를 업로드하는 API
    • 임시 게시글에 이미지를 업로드하게 되면, 해당 게시글에 이미지 URL 엔티티를 추가
    • 발행된 게시글을 수정 중이라면 사본을 만들어 사본에서 수정과 이미지 업로드를 진행
  • 임시 게시글을 임시 저장하거나, 발행된 게시글의 사본을 임시 포스트로 저장하는 API
  • 임시 게시글을 발행하거나, 발행된 게시글을 재발행하는 API
    • 발행하려고 하는 임시 게시글이 사본인 경우 원본 게시글에 데이터를 다 옮기고 삭제

9가지의 경우의 수를 6가지로 줄이면서 반복되는 코드를 제거했습니다.


임시 저장 기능을 만들면서..

당연하게 사용해왔던 임시 저장 기능을 직접 고민해보고 구현해보면서 저에게 문제를 깊이 고민해본 경험으로 남을 것 같습니다.
제가 만든 임시 저장 기능이 프로젝트에서도 잘 동작이 되는 것을 보면서 느꼈던 감정은 정말 최고였습니다.
임시 저장 기능을 구현하시는 분들은 제가 고민한 부분을 참고하시면 좋을 것 같습니다!
더 좋은 알고리즘 또는 최적화 포인트를 알고 계신다면 댓글로 남겨주시면 큰 도움이 될 것 같습니다 👍

2개의 댓글

comment-user-thumbnail
2022년 9월 21일

좋은 정보 감사합니다! 저도 임시 저장 기능 구현해보고 싶네요 ㅎㅎ

1개의 답글