[SSAFY] 공통 프로젝트 우수상, 세이푸트
7월 개강하고 아이디어톤부터 시작해서 8월 16일 최종 발표, 8월 21일 전국 발표까지 길고 길었던 공통 프로젝트가 끝이 났다. 당연히 최우수상을 받을 수 있을 거라 생각하고 있었는데 우수상이라서 조금의 아쉬움은 남았지만 아이디어 회의부터 발표까지 최선을 다한 프로젝트이기에 미련은 없다. 우리 팀 모두 최선을 다했지만 우리와 경쟁했던 팀의 프로젝트가 심사위원들에게는 조금 더 설득력이 있었겠구나 하고 받아들이기로 했다. 프로젝트 기간동안 트러블 슈팅, 공부 내용 등을 원래 블로그에 기록하려 했으나 너무 바빠져서 노션에 기록했다. 바빠서 기록하지 못했던 내용들을 포함해서 공통 프로젝트 회고록을 오늘 작성해보려 한다. 그럼 지금 시작하겠다.
세이푸트란?
우리 서비스를 한 줄로 소개하자면 다음과 같다.
손 안에서 확인하는 안전한 푸드트럭, 세이푸트
실제로 내가 발표할 때, 첫번째 소개 문장으로 위의 문장을 사용했다. 우리 서비스는 손님 입장, 사장님 입장의 2가지 입장으로 개발하였다. 2가지 입장에서 대표적인 기능만 나열하면 아래와 같다.
- 손님
- 푸드트럭의 목록을 조회
- 푸드트럭에서 주문
- 실시간으로 주문 상태를 알림
- 리뷰 작성
- 수요조사 작성
- 사장님이 진행하는 푸드트럭 홍보 방송에 참여, 소통
- 사장님
- 가입할 때 올바른 인허가번호(식품의약품안전처 푸드트럭지정현황조회 API)를 가졌을 경우에만 푸드트럭을 등록 가능
- 정해진 푸드트럭 영업 허가구역 내에서만(공공데이터포털 푸드트럭 허가구역 조회 API) 영업 시작 가능
- 푸드트럭 및 메뉴 관련 CRUD
- 수요조사 확인
- 주문 수락/거절
- AI를 활용한 로고 생성
- openVidu를 활용한 푸드트럭 홍보 방송 및 채팅
- AI 챗봇(가게 관련 질의응답 답변 가능)
- AI 리뷰 답글 초안 생성(손님이 남긴 리뷰와 가게의 정보를 분석해 답글 초안을 작성해주는 기능)
이 외에도 다양한 부가 기능들이 있다. 그럼 우리 서비스는 어떤 배경에서 시작했을까?
기획 배경
팀원들과 첫째주부터 아이디어 회의를 시작했다. 우리의 목표는 모두가 관심있고 재미있는 주제를 선정해서 수상을 하는 것이었다. 그렇기 때문에 아이디어가 쉽게 채택되지 않았고, 2주를 넘기고 3주차에 들어갔을 때 아이디어를 픽스할 수 있었다. 그 사이에 무수히 많은 아이디어가 나왔었지만 이미 레퍼런스가 존재하거나 식상하고 재미없어 보이는 주제들을 넘기다보니 오래 걸렸다.
그렇게 선정한 주제가 바로 푸드트럭이다. 자연스럽게 이야기를 나누다가 소상공인, 푸드트럭과 관련된 이야기가 나왔고 우리가 이용하는 푸드트럭이 언제 어디로 오는지, 그리고 과연 안전한 지에 대해 궁금했다. 이 궁금증에서 세이푸트를 기획하기 시작했다.
시스템 아키텍처
아키텍처는 위 그림처럼 구상했다.
- 서버는 제공받은 EC2 ubuntu 환경을 이용하였고, 내부에서 docker를 이용해 container를 띄워서 아키텍처를 구축했다.
- 사용자가 우리 서비스에 접속하면 Nginx에서 리버스 프록시를 통해 React, Springboot로 전달한다.
- 개발자가 코드를 Gitlab에 commit하면 Jenkins에서 자동으로 테스트부터 배포까지 하도록 pipeline을 구축하였다.
ERD
ERD는 위와 같이 구성했다. 회원의 경우 사업자 번호의 존재 유무로 손님/사장님을 구분하였고 사장님인 경우에만 푸드트럭을 가질 수 있도록 하였다. Spring Security를 적용시켜 사장님/손님을 구분하여 API를 요청할 수 있도록 구현하였다.
서비스 동작 화면
아이디 찾기 / 비밀번호 찾기
위의 아이디 찾기 또는 비밀번호 찾기를 이용하면 Java의 MailSender를 이용하여 실제로 임시 비밀번호를 등록된 이메일로 받을 수 있다.
회원가입 / 점포등록(사장님)
- 손님의 경우 필요 정보를 입력하고 이메일/닉네임/전화번호 중복검사를 거친 후 회원가입 가능
- 사장님의 경우 사업자 번호 확인 후 회원가입을 할 수 있으며 그 후 바로 푸드트럭 등록으로 넘어가서 식약처인허가번호를 확인받은(푸드트럭지정현황조회 API) 다음 푸드트럭을 등록
소셜 로그인(카카오 / 구글)
OAuth를 사용하여 카카오 로그인과 구글 로그인을 구현하였다.
손님 메인화면(방송 조회 / 푸드트럭 조회(지도) / 푸드트럭 조회(목록) / 주문)
손님의 메인화면에서는 푸드트럭 방송 조회, 푸드트럭 조회를 할 수 있고 조회한 푸드트럭에서 주문을 할 수 있다.
주문 실시간 알림 기능 - SSE(Server Sent Event)
사장님이 주문을 수락하면 SSE를 적용시켜 실시간으로 손님에게 알림이 오고 주문 상태창이 변경되도록 구현하였다.
리뷰 작성 - AWS S3
리뷰에는 여러 개의 이미지를 첨부하여 작성할 수 있다. 이 때 이미지 저장에는 AWS에서 제공하는 S3를 사용하였고, DB에는 PATH와 URL만 저장하는 방식으로 이미지를 관리하였다.
라이브 방송(방송 개설 / 방송 알림 / ai 챗봇) - openVidu
openVidu를 이용해서 라이브 방송을 할 수 있도록 WebRTC를 구현하였으며, 방송을 개설하면 해당 푸드트럭을 찜한 손님에게 알림이 간다. 채팅창에서는 손님 / 사장님을 구분지어서 대화를 할 수 있고 '/ai'를 입력하여 질문을 하면 ai 챗봇 기능을 이용할 수 있다.
소비패턴 분석(손님) / 매출 분석(사장님)
손님에게는 주간 소비패턴 분석을, 사장님에게는 일간과 주간 매출 분석을 제공한다.
수요조사(손님 / 사장님)
수요조사는 손님 입장에서 원하는 위치에 원하는 카테고리의 푸드트럭을 제출할 수 있고 사장님은 장소를 조회해 해당 위치로 어떤 푸드트럭들을 손님들이 원하는지 확인할 수 있다.
AI 로고 생성
이 외에도 기타 다양한 기능들이 있지만 다른 기능들은 github 링크를 남겨둘테니 확인해주시면 될 듯하다.
담당했던 기능
- JPA를 활용한 객체 관계 매핑 및 데이터베이스 설계
- ERD Cloud를 사용하여 Database 테이블 설계
- 객체 도메인 설계 후, JPA를 사용하여 객체 관계 매핑(ORM)
- Spring Security와 JWT를 활용한 인증 및 인가 구현
- QueryDSL을 활용한 동적 쿼리 구현 및 N+1 문제 해결
- EC2, Docker, Jenkins를 활용한 CI/CD pipeline 구축
이번 프로젝트를 진행하며 많은 것들을 배울 수 있었다.
Infra
Infra를 구축하면서 EC2, Docker, Jenkins에 대해 공부하고 시스템 아키텍처의 설계 방식과 시스템의 흐름에 대해 이해할 수 있었다.
특히 Jenkins pipeline을 설계하면서 gitlab에 코드를 push 하였을 때 test부터 배포까지 자동화를 구축하는 경험을 할 수 있었고, 다음 프로젝트에서는 무중단 배포까지 구축해보려 한다.
Spring Security
Spring Security와 JWT를 활용한 인증 및 인가는 저번에도 했었지만 이번에는 @PreAuthorized 어노테이션을 활용하여 메서드별로 명확하게 인가를 구분하였고, SecurityContextHolder를 활용하여 로그인된 정보를 기반으로 사용자 정보, 점포 정보 등을 조회할 수 있도록 보안에 좀 더 신경을 썼다.
JPA, QueryDSL
이전까지 기초적인 기능만 적용하고 사용하였던 JPA와 QueryDSL을 좀 더 깊게 이해하고 성능을 고려하며 최적화하는 경험을 할 수 있었다. 특히 API 성능 테스트를 진행하던 중 기본적인 JpaRepository에서 제공하는 메서드만을 사용하여 조회를 하였을 때 N+1 문제가 발생하는 것을 확인할 수 있었다. 이를 해결하고자 QueryDSL로 동적 쿼리를 생성하고 fetch join을 활용하여 필요한 테이블을 미리 조회하였으며 Projection을 활용하여 필요한 필드들만 조회 후 DTO로 반환하도록 구현하였다. 그 결과 아래 이미지처럼 성능을 개선시켰다.
- 개선 전 응답시간: 11.39s
- 개선 후 응답시간: 120ms
아쉬웠던 점
Jira와 Gitlab의 연동
Jira를 처음 사용해보면서 Epic, Story, Task를 이해하고 사용하였으나, Gitlab과 연동하지 않고 사용한 것에 대한 아쉬움이 있다.
다음 프로젝트를 진행한다면 Gitlab과 연동하여 이슈 태그를 통해 유연하게 Jira를 관리해보고 싶다.
무중단 배포, 컨테이너 관리
Infra를 처음 경험해봐서 배포 자동화를 구축하는 데 급급했던 것 같다. 실제로 운영되는 서비스라면, 배포가 되는 순간에도 서비스를 이용할 수 있도록 무중단 배포를 구축해야 한다고 생각한다.
또한 EC2 내에서 Springboot, React, MySQL 등의 이미지를 컨테이너로 띄웠는데, 각각 띄우고 하는 과정이 매우 번거로웠다. 이를 compose를 활용하여 묶어서 관리했으면 더 좋았을텐데 하는 아쉬움이 남는다. 그리고 컨테이너를 관리하는 쿠버네티스라는 기술이 있다는 것을 나중에 알게 되었는데 다음 프로젝트에서는 이를 적용시켜보고 싶다.
QueryDSL과 JpaRepository
N+1 문제를 해결하면서 QueryDSL을 적극 사용하였다. 그런데 추가적으로 공부를 하다보니 JpaRepository에서 제공해주는 기능이 생각보다 다양하고 최적화도 잘 이뤄진다는 것을 알게 되었다. 무분별한 QueryDSL 사용을 지양하고 JpaRepository에서 제공해주는 메서드로 해결할 수 있는 문제들은 해결하고, 실제로 복잡한 쿼리들만 QueryDSL로 작성했다면 더 깔끔한 설계가 되었을 것 같다.
테스트 주도 개발(TDD)
TDD를 실천하고 싶었는데 주어진 개발 기간이 6주인데 그 중 2주는 설계에 사용하고 1주는 QA 및 최종 발표 준비에 사용하니 실제로 개발하는 기간은 3주였다. 주어진 기간이 짧다보니 테스트 코드를 작성하지 못하고 Postman을 사용하여 API 테스트만 진행하면서 개발을 하였다. SSAFY의 프로젝트는 기간이 짧아 현실적으로 어려울 것이라 생각하지만 언젠가는 꼭 TDD로 개발을 해보고 싶다.
회고
길다면 길고, 짧다면 짧았던 6주간의 공통 프로젝트가 끝났다. 컨설턴트님과 코치님께 피드백을 받으며 기획을 진행하면서, 기획이 정말 어렵다는 것을 느꼈다. 그리고 팀장으로써 개발을 진행하면서 협업에서 가장 중요한 건 개발 실력보다 소통이라는 것을 다시 한 번 깨달을 수 있었다. Infra를 경험해봤고, 현업에서 협업툴로 많이 사용한다던 Jira를 사용해보며 협업의 문서화를 할 수 있었으며, 구현에만 초점을 두는 것이 아닌 성능도 신경을 쓰면서 Ngrinder를 통한 부하 테스트, Postman을 이용한 API 성능 테스트를 수행하면서 성능을 향상시키는 경험도 할 수 있었다. 확실히 팀 프로젝트를 하면서 성장하는 것을 느꼈고, 이번 프로젝트에서 부족했던 점을 보완하여 다음 특화 프로젝트는 Infra를 혼자서 담당하면서 이번에 아쉬웠던 무중단 배포, 쿠버네티스와 같은 기술들을 적용시켜보고자 한다.