Published on

사이드 프로젝트(자린고비) 회고록

Authors
  • avatar
    Name
    Na Hyunwoo
    Twitter

사이드 프로젝트


프로젝트 개요

What

  • 고물가 시대에 들어서면서 사람들이 소비를 아끼려는 큰 니즈를 보였습니다. 이는 카카오톡 오픈 채팅방 ‘거지방’과 같은 것들이 유행함을 통해 파악할 수 있었습니다.
  • 사람들은 자신의 소비 내역을 공유하면서, 자신의 소비에 대한 사람들의 반응을 보며 재미를 느꼈습니다.
  • 이런 사회적 흐름과 사람들의 니즈에 따라, 공통된 지출 목표를 가진 사람들을 위한 커뮤니티 서비스를 만들어야겠다고 생각했습니다.

Tech Stack

Next.js, React Query, Tailwind Css, Zustand, React Hook Form, Radix UI, MSW, ESLint, Prettier, Husky, Storybook

Github Url


어려웠던 점과 해결 방안

개발 외적으로 어려웠던 점

  • 구성원 개개인이 프로젝트를 통해 달성하려는 목표가 달랐기 때문에, 이를 맞추는데 약간의 충돌이 있었습니다.
  • 예를 들어, 저는 유저들을 많이 끌어모을 수 있는 제품을 만들고 싶었습니다. 그러나 다른 어떤 분은 실무에서 사용해보지 못하는 기술 스택들을 마음껏 사용해보고 싶어 하셨습니다.
  • 이 과정에서 충돌이 발생하였습니다. 저는 최신 기술 스택들을 많이 사용하는 것은 좋으나, 모든 것들을 최신 기술 스택을 사용하려 한다면, 해당 기술 스택을 학습하는데 시간을 너무 많이 소비하게 되어 프로젝트를 마무리 하지 못할 가능성이 커진다고 생각했습니다. 따라서, 최신 기술 스택은 적극적으로 수용하지만, 해당 스택 도입의 근거가 명확해야 한다는 기준을 정하였습니다.
  • 이를 통해 도입해야 하는 기술 스택과 그렇지 않은 기술 스택을 명확히 구분할 수 있었습니다.
  • 그러나, 앞서 갈등 탓이였는지, 자린고비 서비스의 가장 핵심적인 기능을 맡은 프론트 리더가 중간에 이탈하였습니다. 프로젝트에 이제 막 집중을 쏟으려던 찰나였기 때문에 우왕좌왕할 시간이 없었습니다. 남은 프론트 인원 세 명의 구성원 중 한 분은 백엔드 개발자셨고, 남은 한 분보다 제가 프로젝트 경험이 많다고 판단했습니다. 따라서, 제가 프론트 리드 역할을 맡게 되었습니다. 너무 힘들었습니다 :(
  • 이외에도 구성원의 대부분이 실무의 경험이 많지 않은 구성원으로 이루어져 있다보니, 평소에 당연하다고 생각하던 것들에 대해서 다시 의견을 나누는 상황이 빈번하게 발생하였습니다.
  • 구현하기에는 너무 큰 애플리케이션과 너무 부족한 개발 인력으로 인해 매일 퇴근 후 새벽까지 작업을 했습니다.

개발적으로 어려웠던 점

이번 프로젝트는 개발적으로 난이도가 있기보다는, 제한된 시간 안에 굉장히 많은 양의 개발을 해야한다는 것이 주요 쟁점이었습니다. 현재 백패커 소속으로써 회사의 업무를 가장 우선순위에 두고, 그 이후의 자투리 시간에 사이드 프로젝트를 만드는데 할애해야 했기 때문입니다.

그러나, 사이드 프로젝트라는 이름에 걸맞지 않게, 앱의 규모가 컸고, 그에 따라서 최대한 효율적으로 개발하는 것이 중요했습니다.

이를 위해서 항상 가장 중요한 문제를 먼저 해결하려 노력했습니다.

다음은 가장 중요하면서도 까다로웠던 두 가지 문제에 대해서 이야기 해보겠습니다.

  • 스크롤 문제
  • Optimistic Updates

스크롤 문제

  • 채팅방 피드는 일반적인 화면과 다르게, 아래에서 위로 스크롤이 이루어져야 합니다. 이는 일반적인 형태에서 벗어나기 때문에 여러 다른 문제를 파생합니다.
  • 예를 들어, 피드의 수가 많아 피드를 표현하는 영역을 초과했다고 가정해보겠습니다.
  • 그러면 당연하게도 화면은 스크롤 최 상단에 위치하는 피드를 보여줍니다.
  • 그러나, 우리가 보여줘야 하는 화면은 화면의 최 하단에 위치하는 피드를 보여줘야 합니다.
  • 이를 해결하기 위해선, 스크롤을 최 하단으로 내려야 합니다.
  • 이를 해결하기 위해 하나의 커스텀 훅을 만들었습니다.
import { useEffect, useRef } from 'react'

const useScrollToBottom = ({ earlyReturn }: { earlyReturn?: boolean }) => {
  const bottomRef = useRef < HTMLDivElement > null

  useEffect(() => {
    if (!bottomRef.current || earlyReturn) {
      return
    }

    bottomRef.current.scrollIntoView()
  }, [[bottomRef.current]])

  return { bottomRef }
}

export { useScrollToBottom }
  • 이 훅은 화면 최 하단에 위치하는 bottomRef로 스크롤하는 커스텀 훅입니다.
  • earlyReturn이라는 props는 특정 상황에서 스크롤 동작을 막게하기 위함입니다.
  • 사용하는 쪽에서 bottomRef를 화면의 최 하단에 위치시켜 주면 채팅방 스크롤 문제를 해결할 수 있습니다.

Optimistic Updates

  • 낙관적 업데이트는 프론트엔드/UX 개발에서 볼 수 있는 패턴으로, 서버의 확인을 기다리지 않고 시각적 인터페이스에서 다양한 동작을 즉시 업데이트하는 것입니다.
  • React Query를 사용한다면 Optimistic Updates를 손쉽게 구현할 수 있습니다.
  • 그러나, 저희 프로젝트는 피드 각각의 반응 상태들을 서버 상태로 관리하고 있는 것이 아니라 React Query에서 제공하고 있는 패턴을 적용할 수 없었습니다.
  • 피드별 반응을 모두 따로 관리하려면 20개의 피드 호출시에 20번의 서버 호출이 필요했기 때문입니다.
  • 따라서, 클라이언트에서 직접 Optimistic Updates를 구현하기로 하였습니다.
  • 코드는 다음과 같습니다.
const handleClickEmoji = (clickedEmojiType: EmojiType) => {
  setPrevEmojis(emojis)

  const clickedEmoji = emojis.find((emoji) => emoji.type === clickedEmojiType)
  const isClickedEmojiSelectedBefore = clickedEmoji?.selected

  if (isClickedEmojiSelectedBefore) {
    deleteEmoji.mutate({
      recordId,
      type: clickedEmojiType,
    })

    setEmojis((prev) =>
      prev.map((emoji) => {
        if (emoji.type === clickedEmojiType) {
          return {
            ...emoji,
            selected: false,
            count: emoji.count - 1,
          }
        }
        return emoji
      })
    )
    return
  }

  updateEmoji.mutate({
    recordId,
    type: clickedEmojiType,
  })

  setEmojis((prev) =>
    prev.map((emoji) => {
      if (emoji.type === clickedEmojiType) {
        return {
          ...emoji,
          selected: true,
          count: emoji.count + 1,
        }
      }
      if (emoji.selected) {
        return {
          ...emoji,
          selected: false,
          count: emoji.count - 1,
        }
      }
      return {
        ...emoji,
        selected: false,
      }
    })
  )
}

useEffect(() => {
  if (!updateEmoji.isError) {
    return
  }

  setEmojis(prevEmojis)
}, [updateEmoji.isError])

useEffect(() => {
  if (!deleteEmoji.isError) {
    return
  }

  setEmojis(prevEmojis)
}, [deleteEmoji.isError])
  • 위 코드는 다음과 같은 의미를 가지고 있습니다.
  • 사용자가 emoji를 click하면 클릭되기 이전 emoji state를 prevState로 저장합니다.
  • 만약 클릭된 이모지가, 이전에 이미 클릭한 적 있다면, 이모지 반응을 제거합니다.
  • 만약 클릭된 이모지가, 이전에 클릭된 적 없다면, 이모지 반응을 추가합니다.
  • 그리고 useEffect 훅을 이용하여, 이모지 반응을 남기는 도중에 에러가 발생한다면, 이전 행동을 취소합니다.
  • 결과는 다음과 같습니다.
스크린샷 2023-03-23 오후 4.52.10.png

결과

  • 성공적으로 배포에 성공하였고, 큰 문제 없이 시연에 성공하였습니다.
  • 완성된 제품을 보니 생각보다 좋은 퀄리티의 제품이 나와서 뿌듯했습니다.
  • 8팀중에서 4등을 하였습니다. 시연 전날까지 QA를 진행할 정도로 빠듯한 일정속에서 준수한 성적을 거두었습니다. 3등팀과 점수 차이가 많이 안났기 때문에 시연시에 조금 더 적극적으로 했다면 더 좋은 성적을 거둘 수 있었을 것 같습니다.
  • 실험적인 기술들을 실제 프로덕션 레벨에서 사용해볼 수 있었습니다. 그리고 좋았던 것들을 뽑아서, 회사 팀원들을 설득시킨 후 실무에 적용시킬 수 있었습니다. (ex. Shadcn/ui)

개선점과 대안

  • 프론트 리더가 이탈하기 이전에 징조를 파악하고 서로 합의를 도출할 수 있지 않았을까 하는 아쉬움이 남았습니다.
  • 시간과 인력이 부족하다는 핑계로 너무 무의식적으로 코드를 짜낸 부분이 있던 것 같습니다. 리펙토링 해야겠습니다.
  • 실제로 시연시에 50명이 넘는 유저들이 몰렸고, 그에 따라 피드 쪽에 대량의 이미지가 로딩되는 상황이 생겼습니다.
  • 실제 시연 장소가 지하였었고, 데이터가 안터지는 수준으로 느렸기 때문에 이미지가 뜨는데 한참 걸리는 상황에 마주했습니다.
  • 실제 프로덕션 레벨 테스트시에 3g 속도로 전환해서 체크를 해봐야겠습니다.
  • 또한 이를 해결하기 위해서 이미지 최적화를 위한 작업을 진행해야겠습니다.
  • 실 API 연동 과정이 수월하지 않았습니다. MSW를 통해 전체 프로젝트 개발을 진행했기 때문에, 실제 API 연동시 생각하지 못한 부분에서 이슈가 많았습니다.
  • MSW를 사용한다고 하더라도 각 Feature별로 QA를 진행해야 마지막에 이슈가 몰리지 않을 것 같습니다.
  • 결국에는 FE, BE가 모여 꼬인 실타래를 하나씩 풀어냈습니다.
  • optimistic updates를 하는 과정에서 비즈니스 로직을 섞다보니 코드가 읽기 어려워졌다고 판단했습니다.
  • 이를 useImmerReducer를 활용하면 멋지게 해결할 수 있을 것 같습니다.
  • 해당 훅을 통해 업데이트하면 블로그에도 업데이트 하겠습니다.

결론

  • 프론트 리더의 중간 이탈 이후에 프론트 리더의 역할을 맡으면서 멘탈이 나간 적이 많았습니다.(실제 제가 진행했던 프로젝트 중 가장 힘들었습니다.) 그럴 때마다 팀원들에게 내가 어떤 부분에서 힘든지 논의하며 잘 넘어갈 수 있었습니다.
  • 신규 기술들을 많이 도입해보면서 실무 적용까지 해볼 수 있었던 좋은 기회였습니다.
  • 외부 개발자들과 소통하면서 회사 안에 갇혀있던 저의 시야가 바깥으로 트였습니다. 매번은 아니여도 기술에 대한 학()을 마친 이후에 습()하기 위해 사이드 프로젝트를 진행하면 저의 성장에 큰 도움이 될 것 같습니다.
  • 프로젝트를 끝내고 나면 항상 드는 생각이지만, Fundamental에 대해서 부족하다고 느낍니다. 아마 종종 디버깅의 어려움을 느꼈기 때문인 것 같습니다.
  • 그래서 앞으로의 4개월은 실제 프로젝트 하는 시간은 줄이고, Next.js, React의 Docs를 달달 외울 예정입니다. 추가적으로 FE Loadmap에 있는 개념들을 완전히 숙지하기 위한 개인 스터디 프로젝트를 진행할 예정입니다.