본문 바로가기

Sparta x 이노베이션 캠프/팀 프로젝트

실전 프로젝트 - 내돈내여 (React + Spring)

반응형

광고 없는 알맹이로 가득 찬 정보를 찾고 싶으시다면 유저들이 내 돈으로 여행하고 인증하는 내돈내여를 써보세요!

광고 없는 여행 후기 내돈내여로 떠나기 ✈️

Back-End Github 👀

Front-End Github 👀

🧑🏻‍💻👩🏻‍💻 팀원 소개

 

서비스 개요

 

내 돈내고 내가 여행하는, 진정성있는 국내 단기 여행 정보 공유 플랫폼 서비스

 

서비스 아키텍쳐 

Front-end

node.js, 사용자 요청에 따른 각기 다른 웹사이트를 보여줄 수 있도록 동적 웹 호스팅이 가능한 EC2와, 리버스 프록시로서의 역할을 하며 서버가 SSL 요청을 처리하는데 드는 비용이 없는 nginx를 이용해 데이터 보안을 위해 https 환경으로 끊김없는 서비스를 제공 할 수 있고 많은 레퍼런스를 가진 PM2를 통한 무중단 배포를 하였고, 리액트 상태를 전역으로 관리하고 사용이 편리한 Redux-toolkit을 사용하였습니다. 또한 클라이언트 단에서 직접 이미지 저장을 위해 S3를 사용하고 있습니다. 서버와는 브라우저 호환성이 높고 에러 찾기에 용이한 Axios로 비동기 통신을 하였고 사용자 알림 서비스를 위해 Server-sent-events를 통해 서버로부터 데이터를 받고있습니다.

 

Back-end

데이터베이스는 AWS RDS의 My SQL을 사용하였고, 이미지 및 동영상은 S3에 저장됩니다. JWT를 사용해 사용자 인증을 하고, 프론트와 백엔드의 개발 속도 향상과 서버 중단으로 사용자가 서비스 이용을 할 수 없는 상황을 막고자 깃허브 액션으로 빌드를 진행하고 빌드 된 파일을 S3에 업로드 한 후, 코드디플로이에서 사전에 작성된 스크립트를 기반으로 EC2 자동 배포를 할 수 있도록 구현하였습니다. 

+ 스프링부트 설명 + Server-sent-event 설명 덧붙이기
소셜 로그인 구현을 위해 네이버와 카카오 톡 오픈 API를 사용하고 있습니다.

 

🗂 컴포넌트 구조

🚀 주요 기능

👍🏻 관심사 선택 & 추천 페이지

  • 회원 가입 후 사용자는 여행 관심사를 세 개까지 선택 할 수 있고, 그 다음으로 이동하는 추천 페이지에서 사용자가 선택한 여행 관심사 태그를 기반으로 추천 된 게시물을 볼 수 있습니다.

📝 블로그 형식의 게시물 등록 페이지

  • 형식에 구애 받지 않는 자유로운 형태의 여행 후기를 공유 할 수 있도록 TOAST UI Editor를 이용해 블로그 형식으로 게시물을 등록 할 수 있도록 페이지 구성을 하였습니다.

🗺️ 여행 경로 (Kakao map API)

  • 카카오 API를 사용하여 사용자가 방문했던 장소에 마커를 찍어 출발지-도착지와 폴리라인을 이용한 여행경로를 게시글 내에 여행코스로 저장

🍻 나의 일정

  • 여행 전 나만의 여행 일정을 작성 하고, 일정 완료와 취소 버튼으로 진행 중인 일정과 완료된 일정으로 나누어 관리 하며 필요 시 삭제 할 수 있습니다.

🎥 스토리 기능

  • 나만 보기 아까운 여행의 추억들을 영상 공유를 통해 내돈내여 회원들과 함께 나눌 수 있습니다. User 본인이 상시 삭제 가능하며, 매일 특정 시간에 자동으로 데이터베이스에서 삭제 되도록 구현했습니다.

🎉 알림 기능

  • Server Sent Events 기술을 이용해 사용자의 게시글에 댓글이 작성 되었을 때, ‘좋아요’가 눌렸을 때, 그리고 나의 일정이 임박 하였을 때 리마인더 알림을 서비스하고, 직접 웹 애플리케이션 내에서 알림을 열람하고 관리 할 수 있습니다.

👮신고 기능

  • 광고성이 배제된 진짜 여행 후기를 지향하는 내돈내여 서비스의 가치관과 부합하지 않는 게시물들을 관리하기 위하여 사용자들이 게시물을 해당 사유에 따라 직접 신고 할 수 있도록 신고 기능을 만들었습니다. 누적 신고 50개 이상이면 게시글이 자동 삭제 되도록 관리하고 있습니다.


Truble-Shooting : Front-end

 

1.  S3 Client-side Image 업로드

 

문제 상황

AWS S3를 이용해 client side 에서 image를 업로드하는 와중 버킷에는 객체가 업로드 되나 Image URL로 클라이언트 단에 반환 될 때 어플리케이션에 이미지가 뜨지 않는 현상이 있었음.

 

문제 원인 

클라이언트로 반환된 Image-URL과 버킷에 저장된 Image-URL이 달라 이미지가 보이지 않았다.


해결 과정

  1. 버킷에 업로드 전 이미지의 파일 이름이 중복되지 않도록 new Date 함수를 이용해 이름을 만드는 과 정에서 파일 이름에 특수문자인 ‘+ 와 : ‘ 가 들어간다. 이 이름으로 만들어진 URL이 버킷에서 클라이언트로 나오면서 %로 치환 되었다.
  2. 파일 이름에서 특수문자가 들어가지 않도록 정규 표현식을 이용해 파일 이름을 후가공 해주었다.

 

2. 성능 개선

 

문제 상황

데이터 양이 많아지면서 페이지 로딩 속도가 느려졌다.

 

문제 원인

한번에 전체 데이터를 받아오며, 많은 양의 사진과 동양상 등으로 로딩시간이 지연되었다.

 

문제 해결

  1. 이미지에 loading = "lazy" 속성을 추가해서 사용자가 보고있지 않은 부분에서 데이터가 로딩되는 낭비를 줄임.
  2. Lazy-load기법을 이용한 'intersection-observerAPI' 를 이용해 무한스크롤을 구현하여 필요한 데이터 만큼만 불러오도록 하였다.
  3. 이미지를 S3서버로 보낼 때 이미지 용량을 압축하는 browser-image-compression 라이브러리를 설치해 이미지 용량을 최소화 하였다.
  4. 폰트가 패치 되기 전에 텍스트를 화면에 표시하는 font-display를 swap으로 설정 해 주었다.

 

3. Kakao map 범위 재설정

 

문제 상황 

게시글 상세 조회 시 사용자가 저장한 지도 정보를 바로 불러오지 못함.

 

문제 원인 

카카오 맵에서 제공하는 API 맵 중심은 고정되어있다.

 

문제 해결 

유저가 맵을 사용 할 모든 경우의 수를 파악해 적용했으나 코드가 너무 길어졌다.

카카오 맵에서 제공하는 지도 범위 재설정 라이브러리를 적용해 코드의 가독성을 높임.

useEffect를 활용해 의존성 배열에 GET 요청 함수를 추가, 함수의 결과 값이 바뀔 때 마다 범위 재 설정을 실행했다.

하지만 간헐적으로 지도가 로딩 되는 속도보다 데이터 로딩 속도가 빨라 범위 재 설정이 되지 않는 현상이 발상했다.

setTimeout을 이용해 50ms 후 지도 범위 재설정이 일어날 수 있도록 해 주었다.

 

라이브러리 적용 전 맵 렌더링 부분
라이브러리 적용 후 맵 렌더링 부분
useEffect, setTimeout 활용해 지도 중심 변경하기
지도 여행경로 바로 보이게 구현

4. Access token 재발급

 

문제 상황

Access token이 만료 되었을 때 사용자가 어플리케이션 이용시 로그인이 필요한 이용에 제한이 있음.

 

문제 원인

Access token의 유효시간이 30분에 불과하기 때문

 

해결 과정

  1. Access token의 유효시간을 늘려야 하는 것인지 아닌지에 대한 의견이 있었으나 궁극적인 해결책이 아니라는 판단 하에 refresh token을 서버로 보내 access token을 재발급 받는 방향으로 결정.
  2. Cookie에 저장되어있는 Access token이 로그인 30분 후 만료 되므로 클라이언트 단에서는 29분이후에 삭제 되도록 세팅하였다. 따라서 유효하지 않은 토큰을 사용자가 들고 있을 시간을 없게 만들어 서비스 이용에 문제가 없도록 해주었다.
  3. 29분 후 cookie가 삭제 되면 useEffect 안에 있던 setInterval 함수가 쿠키가 있는지 없는지 감시하는 조건문을 넣어 쿠키가 없다면 토큰 재발급 함수를 호출하는 로직으로 변경 해 주었다.

쿠키 유효시간을 29분으로 세팅 하기
로그인 상태일 때, 쿠키가 없으면(로그인 29분 뒤) reToken 함수를 실행
토큰 요청이 잘 가는지 테스트하기 위해 retoken 함수 실행시 alert 창 띄워줌

 

튜토리얼 페이지

 

 

개선 할 것 & 보완하고 싶은 것

 

이미지 리사이징

AWS CLOUDFRONT + LAMBDA@EDGE를 활용한 IMAGE RESIZING

lighthouse 지표에 성능을 보면 browser-image-compression 만으로는 성능 개선이 만족할만 하다고 느껴지지 않아 

CloudFront  Lambda@EDGE 활용한 Image resizing  시도해보려 합니다.


모바일 환경 최적화
게시글 작성 부분에서 지도가 에디터를 가려서  작성이 불편했던 점을 반응형으로 최대한 수정하여 모바일 환경에  맞게 구현하고 싶습니다.

 

 

기술적 의사 결정

사용기술
기술 설명
CI/CD
GitAction/CodeDeploy를 사용, 개발 단계를 자동화 하여 FE와 BE의 빠른 협업으로 개발 속도 향상을 시도하여 서버 중단 시, 고객이 서비스 이용에 차질이 없도록 자동 배포 기능을 구현 했습니다.
NGINX
리버스 프록시를 이용한 클라이언트 요청을 분산하기 위해 로드 밸런싱, 매번 동일한 콘텐츠를 프록시 서버에 연결할 필요 없이 클라이언트에 응답하는 캐시, 편리한 무중단 배포, Let's encrypt를 이용한 무료,쉬운 발급의 이점이 있는 Https를 적용 했습니다.
SSE
단방향 통신인 알람 기능 사용에 있어 웹소켓보다 핸드 셰이크 발생량이 적어 합리적 사용이 가능합니다. 또한 UX적 측면에서 자신의 게시글에 다른 유저의 상호작용 발생 시, 나의 일정이 다가올 때 실시간 푸시 알림을 보내주어 활발한 사이트 이용을 유도했습니다.
Flyway
배포 후 유저 피드백을 바탕으로 DB를 수정해야 할 때 기존 mysql DB를 drop하지 않고, 스키마 구조를 추가, 변경 할 수 있기에 적용했습니다.
react-intersection-observer
컨텐츠가 필요할 때 마다 불러와서 표시 해주어 데이터가 많아졌을 때 컨텐츠를 효율적 으로 보여주기 위해서 전체 게시글 조회에 무한스크롤을 적용 했습니다.
Redux-toolkit
예측 가능한 데이터 플로우를 그릴 수 있다는 장점이 있고, 전역으로 상태 관리를 할 수 있으며, Redux보다 코드량을 줄일 수 있고 사용이 편리합니다.
SCSS
프로젝트가 커졌을 때 CSS보다 가독성과 유지 보수 측면에서 뛰어납니다.
Axios
response timeout (fetch에는 없는 기능) 처리 방법이 존재하며 Promise 기반으로 만들어졌기 때문에 데이터를 다루기 편리합니다. 브라우저 호환이 fetch보다 뛰어나기 때문에 웹 앱을 염두한 내돈내여 서비스에 적합하다고 생각했습니다.
EC2
클라이언트가 정보를 요청했을 경우 서로 다른 화면을 보는 동적 페이지 구현을 위하여 동적 웹 호스팅이 가능한 EC2로 Front-end 배포를 진행하였습니다.
browser-image-compression
웹 애플리케이션의 성능 최적화를 위해서 클라이언트단 서버로 저장되는 이미지의 용량을 줄이기 위하여 사용한 라이브러리 입니다.

 

 

 


 

 

Nginx 사용 로드맵

클라이언트와 백엔드 서버 사이에 리버스 프록시 서버를 두고 클라이언트는 리버스프록시 서버에 요청을하고, 백엔드 서버는 리버스 프록시로부터 사용자의 요청을 대신 받습니다. 클라이언트는 리버스 프록시 서버 뒷단의 백엔드 서버의 존재를 알지 못해 보안이 한층 강화됩니다. 이때, 리버스 프록시 서버에 SSL인증서를 발급해 HTTPS를 적용하게됩니다. WAS서버가 여러대로 늘어나도 SSL 인증서 발급을 추가로 하지 않아도 되어 확장성이 좋고 서버가 SSL 요청을 처리하는데 드는 비용도 들지 않습니다.

리버스 프록시 서버는 Nginx를 사용하고 간단한 SSL 인증서 발급, Nginx 환경 설정을 위해 Certbot을 사용합니다. 

HTTPS를 사용하는 이유 : 보안성이 HTTP보다 높아 사용자가 안심하고 서비스를 사용 할 수 있습니다.
인터넷에서 두 참여자 간의 통신을 보호하고, 정보가 변조되지 않고 목적지까지 도달하게 하며 웹사이트의 진위 여부를 확인 가능합니다.

 

SSE 

UX적 측면에서 자신의 게시글에 다른 유저와의 상호 작용 발생 시, 나의 일정이 다가올 때 알림을 보내주어 활발한 사이트 이용을 유도했습니다. websocket과 SSE는 양방향 통신과 단방향 통신이라는 차이점이 있는데  이 때문에 사용되는 곳이 조금 다릅니다. 

websocket은 리얼타임이 필요한 채팅, 주식 트레이딩 등에서 쓰이고 SSE는 비교적 실시간일 필요 없는 알람을 줄 때 많이 이용 됩니다.

 

intersection Observer API ,  무한 스크롤

 

Intersection Observer는  Web API중 하나로 지연로딩(lazy-loading), 광고 수익을 위한 광고 가시성 참고, 특정 위치에서 애니메이션 동작 등 여러 기능을 구현할 때 사용됩니다. 또한 무한스크롤에서도 많이 사용됩니다.

비동기적으로 실행되기 때문에 메인 스레드에 영향을 미치지 않아 onScroll의 문제점을 해결하고 웹에서 성능 저하를 시키는 주범인 리플로우를 발생시키지 않는다는 장점도 있습니다. 관찰할 컴포넌트를 선택하고 감지하는 역할을 해서 스크롤을 해도 계속 이벤트를 호출하지 않습니다. 

 

일반적으로 구글과 같은 정보를 전달하는 검색 엔진의 경우에 페이지네이션을 선택하는 경우가 많은데  이유는 키워드를 검색하고, 결과를 찾아 북마크에 추가하는 것이 용이하며, 또한 남은 컨텐츠의 양이 얼마나 되는지 추론   있기 때문에 사용자는 정보에 대한 통제권을 무한스크롤 보다  크게 느낄  있습니다. 내돈내여의 경우, 태그로 찾고자 하는 정보의 필터링이 가능하고 검색 기능 또한 존재 합니다. 또한 텍스트로 이루어진 정보 전달과 더불어 여행지에서의 사진, 여행 경로  시각적 자료를 많이 공유할  있는 특성 때문에 무한 스크롤을 통한 시각적인 어필을 하는 것도 유저를 끌어들일  있는 방법  하나   같아 적용하게 되었습니다. 또한 컨텐츠가 필요할 때 마다 불러 와서 표시 해주어 데이터가 많아졌을 때 컨텐츠를 효율적으로 보여 줄 수 있습니다.



Redux-toolkit

상태 관리 라이브러리로 가장 많이 쓰이고 있는 리덕스는 복잡한 리덕스 스토어를 구성, 패키지들을 추가로 설치해야 하는 점, 많은 보일러플레이트 코드를 요구하는 점의 단점 들이 있고, 이 단점들을 해결한 것이 리덕스 툴킷이라는 라이브러리로, 리덕스를 훨씬 쉽고 간편하게 전역으로 상태 관리를 하며 사용할 수 있다. 

 

카테고리 vs 태그 

카테고리는 게시글을 섹션별로 나누어 범위를 잡고 하위 카테고리를 만들어 게시글을 나누는 역할을 하지만 태그는 카테고리와 상관없이 글을 찾기 쉽게 해주는 표식입니다. 다중 선택을 해야하는 번거로움이있는 카테고리보다 직관적으로 선택해서 바로 결과물을 볼 수 있는 태그를 선택하는게 원하는 정보를 빠르고 쉽게 찾을 수 있을 거라 생각해 태그를 적용 해 보았습니다.

 

EC2

클라이언트가 정보를 요청 했을 경우에 서로 다른 화면을 보여주는 동적 페이지 구현을 위해 동적 웹 호스팅을 할 수 있는 EC2 로 배포를 진행하였습니다. 

 

CloudFront 와 Lambda@Edge를 활용한 이미지 리사이징

게시글 업로드 부분을 맡으면서 휴대전화로 찍은 사진을 직접 업로드 할 때와 온라인에서 수집한 이미지들을 업로드 할 때 용량 차이를 비교 해 보니, 같은 사이즈의 이미지여도 그 차이가 10배 20배 많게는 100배까지도 난 다는 것을 알게 되었습니다. 이미지 리사이징을 거치지 않은 휴대폰 앨범에 저장된 사진들을 이미지 컴프레션과는 별개로 리사이징을 해 줄 필요가 있다고 느꼈습니다. 또한 여러 디바이스에서 요구하는 해상도가 각기 달라 이런 케이스마다 리사이징 해주는 기능을 가진 Lambda@Edge 와 연결하는 캐시 서버 CloudFront 를 이용하면 서비스 최적화에 많은 도움이 될 것 이라 생각 합니다.

 

SCSS 선택 배경

다른 프로젝트들을 하면서 css, styled-components를 사용해 컴포넌트를 스타일링 해 왔습니다. css는 파일이 따로 분리되어있고, 선택자가 많아져 유지 보수를 할 때 가독성이 떨어진다는 단점이 있었고 styled-components는 class-name이 없어 코드가 간결해 지고 props로 받을 수 있는 이점이 있지만 개인적으로는 컴포넌트와 styled-components와의 구분이 힘들었고, 어떤 styled-components가 어떤 태그 속성을 갖고 있는지 바로 찾기가 힘들었습니다. 그래서 이번 프로젝트에서 어떤 방법으로 스타일링 할지 리서치 하던 중 SCSS가 CSS와 호환이 가능하면서 프로젝트가 클 수록 가독성과 재사용성을 높여주어 유지보수가 쉽고, 많은 레퍼런스들을 찾을 수 있어서 팀원들과 논의 끝에 사용 해 보기로 했습니다. 
특히 코드를 중첩시켜서 사용 가능 하다는점이 이용하면서 SCSS가 가독성이 뛰어나다고 느꼈습니다

 

PM2

 서버를 배포했을 때 서비스 제공 도중 갑자기 서버가 중지 되어 사용자가 불편감을 느끼지 않도록 무중단 서비스를 제공 할 수 있도록 해주는 Node.js의 프로세스 매니저입니다. 많은 레퍼런스들이 있고 사용이 간편하며 특히 같은 무중단 배포를 위한 기본 기능을 제공하는 Strong-PM과 비교해 GitHub star수가 월등히 높아서 선택하였습니다. 


Node.js 

자바스크립트 런타임 환경이고 Dom을 조작할 수 있는 Front-end 프레임워크인 React를 더 쓰기 편하게 해주는 도구를 제공하는 오픈 소스입니다.

font-display: swap

swap 옵션은 FOUT와 동일하게 작동하는 옵션입니다. 우선 폴백 폰트로 글자를 렌더링하고, 웹 폰트 로딩이 완료되면 웹 폰트를 적용한다. 웹 폰트 로딩 여부와 관계없이 항상 텍스트가 보입니다.

 

 

 

 

 

reference

 

https://hudi.blog/https-with-nginx-and-lets-encrypt/

 

Nginx와 Let's Encrypt로 HTTPS 웹 서비스 배포하기 (feat. Certbot)

목표 우리의 목표 우리의 목표는 위 그림과 같다. 클라이언트와 WAS 사이에 리버스 프록시 서버를 둔다. 클라이언트는 웹서버처럼 리버스 프록시 서버에 요청하고, WAS는 리버스 프록시로부터 사

hudi.blog

 

반응형