Skip to content

boostcampwm2023/web16-B1G1

Repository files navigation

별 하나에 글 하나 🌟

별 하나에 글 하나 로고

"내 삶의 반짝이는 기억들을 우주에 담아보세요"

3D 기반 웹 추억 저장 서비스


남겨두고 싶은 순간을 찍은 사진과, 그 순간을 떠올리며 적은 글을 별에 담습니다.

기억을 담은 별들이 모여 나만의 은하가 만들어집니다.

추억으로 가득 채워진 나만 우주를 소중한 사람들에게 공유해보세요 ❤️


✨ <별 하나에 글 하나> 사용해보기

🔗 wiki 바로가기


목차


⭐️ 프로젝트 소개

<별 하나에 글 하나>를 만들게 된 계기

우리는 모두 형형색색의 기억들을 가지고 있습니다.

그 기억들을 눈으로 볼 수 있다면 얼마나 좋을까요?

저희 팀은 기억을 시각화할 수 있는 서비스를 만들고 싶었습니다.

또 밋밋하고 정적인 일기 서비스에서 벗어나, 사용자가 서비스 이용에 더 큰 흥미를 느낄 수 있도록 하고 싶었습니다.

그래서 우주 공간을 탐험하는 느낌이 드는 독특한 사용자 경험을 주는 서비스, <별 하나에 글 하나>를 만들게 되었습니다.


주요 기능 설명

wiki에서 더 많은 기능을 살펴볼 수 있습니다.

🔗 wiki 프로젝트 소개 바로가기

( gif 로딩이 느릴 수 있습니다🥹 조금만 기다려주세요 )

[ 글 조회 ]

글 조회 이미지
  • 1.5배속된 영상입니다.
  • 별에 마우스를 호버하면 제목을 볼 수 있습니다.
  • 별을 한 번 누르면 별이 화면의 중앙에 오도록 시점이 변경되고, 한 번 더 누르면 별에 다가가면서 글 조회 모달이 띄워집니다.
  • 이미지는 양쪽 화살표 버튼과 아래쪽의 페이지네이션으로 이동할 수 있습니다.
  • 좋아요 버튼을 누를 수 있습니다.
  • 내 별이면 수정/삭제할 수 있습니다.

[ 글 작성 ]

글 작성 이미지
  • 글은 마크다운 형식으로 작성할 수 있으며, Preview 버튼을 누르면 마크다운이 적용된 글을 미리 볼 수 있습니다.
  • 사진은 5장까지 첨부할 수 있습니다.
글 커스텀 이미지
  • 글 작성하고 다음 버튼을 누르면 별을 커스텀할 수 있습니다.
  • 별의 양 옆에 있는 화살표 버튼을 통해 별의 모양을 변경할 수 있습니다.
  • 색상, 크기, 밝기를 조절할 수 있습니다.
  • 색상 추천 버튼을 누르면, CLOVA Sentiment api를 통해 글의 감정을 분석해 색상을 추천해줍니다.
  • 글이 생성될 때와 삭제될 때 별에서 애니메이션이 발생합니다.

[ 은하 수정 ]

은하 수정 이미지
  • 내 은하 나선팔 꼬인 정도, 나선팔 두께, 막대 길이, 은하 높이를 조절할 수 있습니다.
  • 오른쪽 위의 되돌리기 버튼을 누르면 수정 이전의 내 은하 스타일로 돌아갑니다.
  • 왼쪽 아래의 초기화 버튼을 누르면 기본 은하 스타일로 돌아갑니다.

[ 은하 공유 ]

은하 공유 이미지
  • 체크박스를 통해 검색 허용 여부를 설정할 수 있습니다.
  • 로그인하지 않은 사용자도 공유 링크를 통해 은하에 접근할 수 있습니다.

[ 은하 검색 ]

은하 검색 이미지
  • 검색 허용된 사용자들의 우주를 닉네임 검색을 통해 구경할 수 있습니다.
  • 검색해서 들어간 우주에서 왼쪽 위 뒤로가기 버튼을 누르면 다시 내 우주로 돌아옵니다.

프로젝트 실행 방법

Front-end

yarn workspace client dev

Back-end

yarn workspace server start:dev

⚒️ 기술 스택

🔗 wiki 기술 스택 소개 바로가기

기술스택 이미지 아키텍처 이미지

기술 스택 선정 이유에 관한 팀원들의 글


💪🏻 기술적 경험

FE

프론트엔드의 주요 기술적 도전은 Three.js + React-Three-Fiber(R3F)를 사용한 우주 공간 구현이었습니다. 팀원 모두에게 생소한 기술이었기에 사용한 것 자체도 도전적인 경험이었지만, 그 중에서 특히 사용자 경험 개선 위주의 경험을 작성해보았습니다.

먼저 아래는 Three.js와 R3F에 관련하여 팀원들이 작성한 기술블로그입니다.


R3F Camera

3D 공간 상에서 카메라는 사용자의 시점입니다. 그렇기 때문에 카메라 움직임은 사용자 경험에 직결됩니다. 저희는 자연스러운 카메라 움직임을 만들어내 사용자 경험을 향상시키기 위해 여러 과정을 거쳤습니다.

저희 서비스에서 별을 클릭하면 해당 별을 바라보도록 해야 합니다. 처음에는 카메라의 위치는 그대로 둔 채 시야만 회전하도록 하는 회전 운동의 방식을 사용했습니다. 처음 회전 운동 방식을 적용해본 결과, 별을 바꿀때마다 별과 카메라 사이의 거리를 직접 조정해 줘야 한다는 문제가 있었습니다.

그래서 별과 카메라 사이 거리를 유지한 채 별을 향해 직선 운동 하도록 변경했습니다. 이 방식은 회전 운동에 비해 사용하기 편했으나, 움직임이 너무 뻣뻣했기에 더 부드러운 모션을 추가하면 좋겠다는 생각을 하게 되었습니다.

많은 고민 끝에 회전 운동처럼 별을 향해 회전하고 직선 운동처럼 별에 다가가도록 하여 '포물선 운동'을 만들어 냈습니다. 포물선 운동은 회전 운동의 장점인 자연스러운 움직임과 직선 운동의 장점인 직관적인 움직임을 모두 가졌습니다. 이러한 이유로 저희는 포물선 운동을 적용하게 되었습니다.

  • 직선 운동하는 카메라

    직선 운동하는 카메라 이미지
  • 포물선 운동하는 카메라

    포물선 운동하는 카메라 이미지

하지만 아직 멀리 있는 별이 너무 작게 보이는 문제가 남아있었습니다. 어찌보면 당연한 이야기일 수 있지만, 서비스 특성상 사용자 입장에서 불편한 요소였고 시각적으로 좋지 않았습니다. 그래서 거리에 비해서 물체가 커 보이게 처리해 멀리 있는 별이 너무 작아보이지 않도록 했습니다.

그랬더니 거리가 먼 별이 겉보기보다 멀리 위치하게 되는 문제가 발생했습니다. 사용자가 그 별로 이동하는데 예상하는 것보다 많은 시간이 소요되었습니다. 이 문제를 해결하기 위해 멀리 이동할 때는 좀 더 빠르게, 가까이 이동할 때는 좀 더 느리게 이동하도록 처리했습니다.


성능 최적화

저희는 은하를 만들기 위해 수많은 별 오브젝트들을 화면에 띄워야 했습니다. 하지만 별 개수를 늘릴수록 화면이 더 버벅이기 시작했습니다. 별 개수를 줄이면 시각적으로 좋지 않았기에, 저희는 별 개수를 유지하면서도 화면이 버벅이지 않도록 최적화를 시도하게 되었습니다.

  1. Instancing

    저희가 선택한 첫 번째 최적화 방식은 Instancing이었습니다.

    CPU가 GPU에게 무엇을 어떻게 그릴지 지시하는 Draw Call은 단순해 보이지만 상당히 무거운 작업입니다. 일반적인 컴퓨터 환경에서 Draw Call이 대략 1000회 넘어가면 프레임 드랍이 생긴다고 합니다. 은하를 구성하는 별 오브젝트만 4000개인 저희 프로젝트에서 이러한 Draw Call을 줄이는 것이 중요햐다고 생각했습니다.

    이를 위해 사용한 방식이 Instancing으로, 동일한 오브젝트를 여러 번 그리는 경우 이를 한번에 처리하도록 하는 방식입니다. 저희는 이를 InstancedMesh를 사용해 구현했습니다. 이 방식을 통해 은하를 구성하는 별을 종류별로 묶어줌으로써 4000개의 오브젝트를 13개의 인스턴스로 줄일 수 있었습니다. 이렇게 Draw Call에 의한 CPU 병목 현상을 해결했습니다.


하지만 금요일 프로젝트 현황 공유 시간 때 '처음으로 맥북 팬 소리를 들었어요', '컴터가 안좋아서 그런지 느려요ㅠㅜㅠ' 같은 피드백을 들으면서 추가적인 최적화 작업의 필요성을 느꼈습니다.


  1. Performance Monitoring

    피드백을 받은 이후 선택한 것은 Performance Monitoring입니다. 다양한 최적화 방식이 있었으나 프로젝트에서 사용하는 대부분의 오브젝트가 매우 단순한 형태라 그리 효과적이지 않았습니다. 이에 선택한 방법이 Performance Monitoring으로, 실시간으로 웹의 퍼포먼스를 모니터링해 이를 반영하는 방식입니다.

    react-three/drei 라이브러리의 Performance Monitor를 통해 웹의 퍼포먼스를 모니터링합니다. 그리고 퍼포먼스가 좋지 않은 경우 Canvas의 Device Pixel Ratio을 최대 0.5까지 낮춥니다. 은하의 해상도를 낮추어 프레임 드랍을 해결하는 방식입니다. 이렇게 CPU만 고려하던 1번 방식에서 나아가 GPU의 부담까지 덜어주는 방식을 추가함으로써 더 최적화된 서비스를 만들 수 있었습니다.

    아래 사진 중 왼쪽은 최고 해상도인 경우이고, 오른쪽은 최저 해상도인 경우입니다.

    은하 최고 해상도 은하 최저 회상도

    아래 사진은 메모리 사용량을 비교한 것으로, Performance Monitoring 최적화 전 13.46GB였던 메모리 사용량이 최적화 후 12.50GB까지 감소했습니다.

    아래 사진은 퍼포먼스를 비교한 것으로, GPU 전력 사용량이 0.91 에서 0.62로 감소했고 GPU 사용률이 66에서 51로 감소했습니다.


FSD 아키텍처

프로젝트를 진행함에 따라 파일들이 점점 많아졌고, 파일 분리와 폴더 구조에 대한 명확한 원칙이 필요해졌습니다. 그래서 팀원들이 함께 여러 폴더 구조와 아키텍쳐들에 대해 조사해보았고, 결과적으로 FSD(Feature-Sliced Design) 아키텍처를 적용하게 되었습니다.

저희 프로젝트는 상대적으로 규모가 작은 편인데, FSD 방식은 폴더를 세세하게 나누는 만큼 규모가 큰 프로젝트에 적합하다는 생각도 했습니다. 하지만 프로젝트를 분할하여 정복하는 해당 방식의 장점이 매력적으로 다가오기도 했고, 이 프로젝트는 학습의 목적이 크기 때문에 팀원들 모두 새로운 폴더구조를 적용해보고 싶어했습니다.

출처: https://feature-sliced.design/

FSD 아키텍처는 app, pages, widgets, features, entities, shared라는 6개의 Layer로 이루어져있습니다. 그리고 각각의 LayerSlice들로 이루어져있고, 그 SliceSegment로 이루어져있습니다. 하위요소들을 조합하여 상위 요소를 구성하는 방식으로, 이 매커니즘이 저희에게 굉장히 매력적으로 다가왔습니다. 이렇게 각자의 역할이 분명한 폴더구조를 적용해봄으로써 모듈을 만들 때 각 모듈의 역할을 명확히 정의하게 되었습니다. 또한 하위 요소들이 모두 개별적으로 기능할 수 있기 때문에 훨씬 유지보수성이 높은 코드를 작성할 수 있게 되었습니다.

아래는 저희 프로젝트의 폴더구조입니다.

📦src
 ┣ 📂app
 ┃ ┣ 📜App.tsx
 ┃ ┣ 📜Router.tsx
 ┃ ┗ 📜global.css
 ┣ 📂assets
 ┃ ┣ 📂fonts
 ┃ ┣ 📂icons
 ┃ ┣ 📂logos
 ┃ ┗ 📂musics
 ┣ 📂entities
 ┃ ┣ 📂like
 ┃ ┣ 📂posts
 ┃ ┗ 📜index.ts
 ┣ 📂features
 ┃ ┣ 📂audio
 ┃ ┣ 📂backgroundStars
 ┃ ┣ 📂coachMarker
 ┃ ┣ 📂controls
 ┃ ┣ 📂star
 ┃ ┗ 📜index.ts
 ┣ 📂pages
 ┃ ┣ 📂Home
 ┃ ┣ 📂Landing
 ┃ ┗ 📜index.ts
 ┣ 📂shared
 ┃ ┣ 📂apis
 ┃ ┣ 📂hooks
 ┃ ┣ 📂lib
 ┃ ┃ ┣ 📂constants
 ┃ ┃ ┣ 📂types
 ┃ ┃ ┗ 📜index.ts
 ┃ ┣ 📂routes
 ┃ ┣ 📂store
 ┃ ┣ 📂styles
 ┃ ┣ 📂ui
 ┃ ┃ ┣ 📂alert
 ┃ ┃ ┣ 📂alertDialog
 ┃ ┃ ┣ 📂audioButton
 ┃ ┃ ┣ 📂buttons
 ┃ ┃ ┣ 📂inputBar
 ┃ ┃ ┣ 📂modal
 ┃ ┃ ┣ 📂search
 ┃ ┃ ┣ 📂slider
 ┃ ┃ ┣ 📂textArea
 ┃ ┃ ┣ 📂toast
 ┃ ┃ ┗ 📜index.ts
 ┃ ┗ 📂utils
 ┣ 📂widgets
 ┃ ┣ 📂error
 ┃ ┣ 📂galaxy
 ┃ ┣ 📂galaxyCustomModal
 ┃ ┣ 📂landingScreen
 ┃ ┣ 📂loginModal
 ┃ ┣ 📂logoAndStart
 ┃ ┣ 📂nickNameSetModal
 ┃ ┣ 📂postModal
 ┃ ┣ 📂screen
 ┃ ┣ 📂shareModal
 ┃ ┣ 📂signupModal
 ┃ ┣ 📂starCustomModal
 ┃ ┣ 📂underBar
 ┃ ┣ 📂upperBar
 ┃ ┣ 📂warpScreen
 ┃ ┣ 📂writingModal
 ┗ 📜vite-env.d.ts

BE

테스트와 쿼리 로그 분석을 통한 이유 있는 코드 작성

TDD, e2e 및 유닛 테스트

하나의 API를 구현하기 전에 여러 케이스에 대하여 먼저 테스트코드를 작성하는 TDD(Test Driven Development)를 해보았습니다.
그 과정에서 어색함도 많이 느꼈고, 완벽하게 했다고도 하지 못하지만 TDD의 방법과 장점 등에 대해 알 수 있었습니다.

기능 구현 이후에도, 코드 커버리지를 높이기 위해 e2e 테스트 코드 개선과, mocking을 활용한 유닛 테스트 등을 학습하고 적용해 보았습니다.

학습 및 개발 기록


인증/인가

인증/인가에 대해 고민도 많이 하였습니다.
Session vs JWT, Authorization Bearer vs Cookie, RefreshToken
특히 보안과 성능 및 편의성 사이의 트레이드오프에 대해 고민하고 학습을 하였습니다.

학습 및 개발 기록


트랜잭션 제어, 쿼리 최적화

TypeORM 쿼리 로그를 통해 하나의 비즈니스 로직에서 복수개의 테이블을 수정하는 경우, 트랜잭션을 직접 제어할 필요가 있었습니다. 저희는 TypeORM의 queryRunner와 transaction 메소드, NestJS의 Interceptor 등을 활용하여 여러 차례 트랜잭션 제어 로직을 개선하였고, 각 구현방식의 장단점에 대해서도 학습할 수 있었습니다.

또한 쿼리 로그와 MySQL 쿼리 플랜 기능을 활용해 기존 TypeORM 메소드의 쿼리를 분석하고, 자주 사용되는 일부 메소드에 대해 이를 개선하여 queryBuilder로 개선된 쿼리를 요청하는 쿼리 최적화 과정도 수행해 보았습니다.

학습 및 개발 기록


NestJS Enhancers

NetsJS 자체에 대한 학습을 위해 NestJS의 Lifecycle과 각 Enhancer들에 대해서도 학습을 해보았습니다.
Interceptor, Exception Filter 등 학습을 하고 백엔드 코드에 적용을 해보았습니다.

학습 및 개발 기록


배포 및 자동화

클라우드 배포 경험이 많지 않아 이번 프로젝트를 통해 많은 성장을 할 수 있었습니다. AWS 및 NCP에서 제공하는 서버, VPC, NAT Gateway 등 주요 서비스에 대해 학습하여 배포 환경을 구성하고, Nginx, Docker 및 Docker Compose, GitHub Actions 등을 학습하여 main 브랜치에 push되면 자동으로 배포되도록 설정했습니다.

학습 및 개발 기록


admin 페이지 구현

리액트를 경험해보고 싶어서 Vite + React + TS를 활용해 간단한 admin 페이지를 만들어보았습니다.
admin용 계정 정보를 설정하고, 게시글 관리 및 컴퓨터 자원 사용량, 에러 로그의 차트를 볼 수 있습니다.

image

image


🏃‍♂️ 팀원 소개

🔗 wiki 팀원 소개 바로가기

J010 김가은 J016 김동민 J053 박재하 J073 송준섭 J098 이백범

🐙 J010 김가은 (FE)

🐧 J016 김동민 (FE)

👾 J053 박재하 (BE)

⚽️ J073 송준섭 (BE)

🐰 J098 이백범 (FE)


🍡 팀원 회고