kobeomseok95 / gjgs

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

gjgs-logo

팀명 : 가지각색

                   고범석                                       최준성                                       김기완                   
- 팀장
- Back-end
- GitHub
- 기획자
- Back-end
- DevOps
- GitHub
- PM
- Front-end
- GitHub
soma-logo

Languages

HTML5CSS3 JavaScript TypeScript Java SQL


Technologies

GitGitLab Jira AWS Linux Jenkins Docker React TypeScript FCM Spring Boot Spring Batch JPA queryDsl mysql Redis ELK nGrinder


📝 Table of Contents


📝Outline

취미 생활 및 자기계발 활동에 금전적으로 투자하는 사람들이 지속적으로 증가하고 있으며, 20 ~ 30대 대상 685명 설문조사 결과 사람들은 취미를 혼자보다 함께 즐기고 싶어할뿐만 아니라 전체의 75% 이상이 처음만나는 사람과도 함께 취미를 즐기고 싶다고 답변했습니다. 또한, 유료로 취미생활 및 자기계발 분야 참여시 전체의 63%가 오프라인 방식을 선호하였습니다. 저희는 취미 관련 오프라인 유료 클래스의 수요가 충분하다는 것을 파악하였고, 기존 업체들의 문제점들을 보완하여 오프라인 클래스 중개 플랫폼을 서비스하고자 합니다.


🧐 Pain Point

현재 오프라인 취미 클래스 중개 업체들의 문제점

  • 기존 오프라인 취미 클래스 중개 업체의 문제점
    • 개인 중심의 서비스
      • 설문조사 결과 사람들은 취미를 함께 즐기는 것을 선호한다고 했지만, 정작 현재 업체들은 개인 신청위주로 서비스가 진행중
      • 예를 들어, 클래스 수강신청 인원 상태가 0/8 인 상태에서, 취미를 타인과 함께 즐기고자 하는 고객이 클래스에 수강신청을 했을경우 1/8이 되지만, 최종적으로 해당 고객 이외에는 아무도 신청하지 않아 의도와는 다르게 혼자만 신청할 가능성 존재
      • 현재 수강신청 인원 상태가 0/8 인 경우, 함께 클래스를 즐기기 위해 서비스를 이용하는 고객은 심리적으로 신청하기를 꺼리게 됨
      • 클래스 모집 최소 단위가 존재하는 경우, 개인 신청 시 최악의 경우 클래스 자체가 개설 X
    • 취미가 비슷한 사람들을 모아주는 기능 X
    • 사용자간의 소통 기능 X
    • 단체 예약에 대한 할인 정책 X

  • 동호회 모임 어플
    • 전문적인 클래스 연결 X
    • 사람을 모아주는 역할만 하기 때문에 취미를 제대로 배우고 싶은 사람들에게 부적합
    • 30 ~ 50 명의 그룹으로 형성되어 있어 관리가 어려움

💡 Idea / Solution

  • 그룹 시스템
    • 클래스 수강 신청 전에 취미가 비슷한 사람들을 찾고, 서로 소통할 수 있는 그룹을 만들 수 있는 기능
    • 그룹원간의 채팅 기능
    • 그룹원 초대 기능
    • 그룹원 프로필 확인 기능 -> 실제 오프라인 클래스 수강 전에 그룹원이 어떤 사람인지 확인 가능

  • 게시글 시스템
    • 그룹 생성 시, 모집글을 통해 특정 클래스를 지정해두고 함께 수강신청할 그룹원 모집 기능

  • 사용자 매칭 시스템
    • 원하는 지역, 취미, 나이, 시간, 인원 등을 입력 시 이를 기반으로 사용자들을 빠르게 하나의 그룹으로 묶어주는 매칭 시스템

  • 인원수에 따른 가격 변동 시스템
    • 단체 신청 시 인원당 가격 할인 폭 증가 -> 단체 신청 이점을 제공함으로써 그룹 서비스 사용을 유도
    • ex) 1인 신청시 인당 50,000원
    • ex) 2인 신청시 인당 48,000원

해결책 요약

그룹 시스템을 통해 클래스에 수강신청하기 전에 취미가 비슷한 사람들을 모을 수 있는 기능을 제공합니다.
이를 통해, 클래스를 함께 즐기고자 하는 고객이 최종적으로 클래스에 혼자 수강신청하게 되는 불상사를 막을 수 있습니다.
또한, 클래스를 수강하기 전에 채팅을 통해 그룹원들 간에 소통이 가능합니다.
그룹원들 모아서 클래스에 수강신청하는 것이 번거롭다면, 매칭시스템을 이용해 빠르게 그룹을 구성할 수 있습니다.
만약 혼자 취미 클래스를 수강해도 상관이 없다면 바로 클래스에 수강신청을 진행할 수 있습니다.
그룹 시스템을 통해 취미가 비슷한 사람들을 클래스 수강신청 전에 사전에 모을 수 있다는 점, 유저간의 소통할 수 있다는 점이 타 중개 서비스와의 가장 큰 차이점이므로 그룹 시스템의 이용을 유도하기 위해 단체로 클래스 수강신청을 진행할 경우, 인원에 따른 가격 할인폭 높여 그룹 시스템을 사용하도록 유도합니다.


📈 Structure

structure
  • VPC로 논리적으로 격리된 공간을 만들고 외부 접근 제한
    • VPC가 외부와 통신이 가능하도록 Internet Gateway 를 구성하고 라우팅 테이블에서 Public Subnet(10.0.1.0/24, 10.0.2.0/24)과 연결
    • NAT Gateway를 구성하여 나머지 Private Subnet 리소스가 인터넷으로 트래픽이 통할 수 있도록 연결
    • Bastion EC2를 통해 Private Subnet EC2로 접근
  • Jenkins와 CodeDeploy를 사용한 Blue Green 무중단 배포
  • Load Balancer과 Auto Scaling으로 트래픽 분산
  • Redis Cluster 및 Redis stat 모니터링 구축
  • Log Monitor 용 ELK 구축
  • 검색용 ELK Cluster 구축
  • RDS(MySQL) 이중화 구성

🎁 Outputs

메인페이지

main
  • 광고배너
  • 검색 버튼
  • 함께 할 친구 찾기 버튼
  • 8가지 카테고리 분류
  • 추천 클래스(회원 가입시 선택한 카테고리 기반)
  • 인기 클래스(조회수 기반)
  • 신규 클래스
  • 하단 네비게이션바 - 홈, 카테고리, 그룹, 찜, 마이

하단 네비게이션바

navigation
  • 카테고리 : 다양한 카테고리분류와 지역별을 통해 필터링하여 클래스 검색 기능
  • 그룹 : 자신이 속한 그룹 목록
    • 내가 찜한 클래스
    • 내가 찜한 게시글
    • 내가 속한 그룹원들이 찜한 클래스
  • 마이 : 사용자 정보
    • 카카오 연동 로그인
    • 보유 리워드
    • 보유 쿠폰
    • 나의 게시글
    • 나의 클래스(결제, 결제 대기)
    • 나의 문의
    • 나의 리뷰

그룹 생성

group-create
  • 클래스에 수강신청하기 전에 같이 수강할 사람을 모으기 위해 그룹을 생성

그룹 상세 페이지

group
  • 그룹에 대한 요약 정보
  • 그룹원들이 찜한 클래스 공유
  • 그룹의 공통 태그 기반 클래스 추천
  • 그룹 리더의 경우, 사용자 초대 기능
  • 그룹원 목록 -> 클릭시 그룹원 프로필 확인 가능
  • 그룹원간의 채팅 기능 -> 채팅 대상 클릭시 프로필 확인 가능

게시글 생성

group-bulletin-create
  • 그룹을 생성했다면, 그룹원들을 모으기 위해 만드는 게시글
  • 마이페이지 나의 게시글에서 확인이 가능하며, 게시글 우측 하단의 버튼으로 활성화, 비활성화 가능

그룹원 모집 게시글 페이지 및 게시글 상세 페이지

bulletin
  • 그룹원 모집 게시글 페이지
    • 특정 클래스를 함께 수강신청할 사람들을 모집하는 곳
    • 게시글을 통해 모집이 번거롭다면, 상단의 있는 함께할 친구 매칭버튼을 클릭하여 빠르게 매칭이 가능
  • 게시글 상세 페이지
    • 게시글 모집 요약
    • 선택한 클래스 정보(함께 수강하고 싶은 클래스)
    • 해당 모집 게시글에 참여하고 있는 그룹원 정보
    • 좌측 하단 하트 버튼으로 게시글 찜
    • 참가신청 기능

매칭 시스템 - 함께 할 친구 매칭하기

matching
  • 게시글을 통해 클래스를 함께 수강신청할 사람들을 모집하기 번거로울 경우, 빠르게 그룹을 형성해주는 기능
  • 매칭을 원하는 지역, 클래스 카테고리, 요일, 시간, 인원을 입력하면 이를 기반으로 빠르게 함께할 사람을 매칭하여 그룹 생성
  • 매칭중에는 '함께할 친구 매칭하기' 버튼 문구가 '함께할 친구 매칭중'으로 변경되고 이때 클릭 시 매칭 중단 가능

클래스 상세 페이지

class
  • 클래스 상세 정보
  • 개인, 그룹 단위로 신청이 가능하며, 인원에 따른 가격 할인폭 변동
  • 하단에 고객에게 비슷한 카테고리 기반 다른 클래스 추천
  • 함께할 사람 찾기 버튼 클릭 시, 해당 클래스를 함께 들을 사람을 모집하는 게시글을 필터링하여 제시
  • 우측 상단에 공유 버튼 클릭시, 카카오톡으로 공유 또는 링크 복사 기능

클래스 검색 필터

filter
  • 다양한 필터링 검색 기능
    • 지역별, 인기순, 금액순 등등

알림

app_alarm
  • 매칭, 이벤트 등을 알려주는 알림 기능

마이페이지

회원가입 및 로그인

sign-up

리워드

reward

쿠폰

coupon

프로필 편집, 나의 게시글, 나의 클래스, 나의문의, 나의 리뷰

mypage

결제

payment

디렉터 전용 웹

로그인 페이지

web_director_login

메인 페이지

web-director-main

디렉터 소개 수정

web-director-edit

공지사항 조회

web-director-notice

클래스 등록

1. 기본 정보 입력

web-director-create-class-1
  • 클래스 개설의 첫번째 단계로 기본적인 정보를 입력하는 페이지

2. 상세 소개

web-director-create-class-2
  • 클래스 개설의 두번째 단계로 클래스의 상세 소개를 입력하는 페이지

3. 커리큘럼

web-director-create-class-3
  • 클래스 개설의 세번째 단계로 커리큘럼을 입력하는 페이지

4. 스케줄

web-director-create-class-4
  • 클래스 개설의 네번째 단계로 클래스의 스케줄 정보를 입력하는 페이지

5. 가격 및 쿠폰

web-director-create-class-5
  • 클래스 개설의 다섯번째 단계로 클래스의 가격 정보 및 할인 쿠폰 정보를 입력하는 페이지

6. 부가 정보

web-director-create-class-6
  • 클래스 개설의 마지막 단계로 약관 동의를 입력받는 페이지

내 클래스 목록

web-director-my-class-list
  • 내가 개설한 클래스의 상태를 볼 수 있는 페이지
    • 상태 : 진행중, 작성중, 검수중, 검수 거절, 종료
    • 검수가 완료된 클래스의 경우, 스케줄 정보만 변경 가능

클래스 관리

web-class-manage
  • 내가 개설한 클래스의 스케줄별 상태 및 정보를 조회할 수 있는 페이지

문의 관리

web-director_question
  • 내가 개설한 클래스에 고객이 남긴 문의글을 확인하고 답글을 달 수 있는 페이지

리뷰 관리

web-director_review
  • 내가 개설한 클래스에 고객이 남긴 리뷰를 확인하는 페이지

채팅

web-director-chat
  • 내가 개설한 클래스에 수강신청한 고객과 채팅하는 페이지

할인 쿠폰 관리

web-director-chat
  • 내가 개설한 클래스에서 제공하고 있는 할인 쿠폰을 관리하는 페이지

백오피스 어드민 전용 웹

로그인 페이지

web-admin-login

알림 보내기

web-admin-alarm
  • 전체 유저에게 또는 특정 유저를 검색하여 해당 유저에게 알림을 보낼 수 있는 페이지

클래스 검수하기

web-admin-check
  • 디렉터가 등록한 클래스에 대해 검수를 진행하는 페이지
    • 적절한 경우, 승인
    • 적절하지 않은 경우, 거부

공지사항

web-admin-notice
  • 공지사항을 생성, 수정, 삭제 할 수 있는 페이지

⏰ Jira

roadmap
kanban
sprint

📈 ERD

erd

🔨Test

Unit Test

test

nGrinder 부하 테스트

ngrinder

Log Monitor

log
  • ELK를 이용하여 구축한 로그 모니터링 페이지

Redis Monitor

redis-monitor
  • Redis-stat를 사용한 Redis 모니터링 페이지

🚀Refactoring & Improve Performance

Bulk Query

refac-bulk

코드상 여러 곳에서 이런 문제가 발생했지만 회원가입시 선호하는 카테고리를 입력하는 부분을 예시로 작성하겠습니다.
회원가입 시 선호하는 카테고리를 선택하게 됩니다.

문제점

코드상 cascade를 이용해 따로 save하지 않아도 member를 save하면 같이 member_category가 save되도록 설계했습니다.
또한, 고아 객체 orphanRemoval를 사용하여 삭제 또한 따로 delete 쿼리를 보내지 않아도 동작하도록 설계했습니다.
개발자 입장에서는 위와 같은 설계로 코드를 작성하기가 매우 수월했고, 당연히 한번에 한방 쿼리가 나갈 것으로 예상했습니다.
하지만 쿼리로그를 찍어본 결과 save와 delete 모두 한방 쿼리가 아니라 여러번의 쿼리가 나가는 것을 확인했습니다.

해결책

refac-bulk-solution

결론부터 말씀드리면, cascade를 제거했고 다음과 같이 수정했습니다.

  • Insert의 경우 : JdbcTemplate.batchUpdate() 사용
  • delete의 경우 : queryDsl의 in 쿼리 사용

Insert 해결책

해결책은 2가지가 존재했습니다.

  1. Table Id strategy를 SEQUENCE로 변경하고 Batch 작업
  2. JdbcTemplate.batchUpdate() 사용

MySQL의 Table Id 전략은 대부분이 IDENTITY 전략을 사용하기도 하고, 저희는 이미 Id 전략을 IDENTITY 전략으로 사용하고 있었기에 Id전략을 변경하기에는 무리가 있었습니다. 또한, Jdbc를 사용하는 것이 성능상 더 뛰어나다는 결과를 확인했습니다.

refac-bulk-performance

출처


Delete 해결책

이미 프로젝트에서 queryDsl를 사용하고 있어 이를 이용하는 것이 가장 간단했기 때문에 queryDsl의 delete in 쿼리를 사용하여 해결했습니다.


API 문서화

refac-docs

Jira로 일정관리를 하고 있었기에 프로젝트 초기에는 Jira Confluence를 사용하여 API 문서화를 진행했습니다.
프로젝트 초기 단계가 지나 작성해야 하는 API들이 많아지면서 일일이 Confluence에 작성하고 확인하기가 번거로워졌기에 코드상으로 해결 가능한 Swagger를 적용하여 문서화를 진행했습니다.
이후 프로젝트의 중후반 단계가 되었을 때, 정말 많은 API들을 만들게 되었는데 이 과정에서 Swagger의 단점이 명확하게 보이기 시작했습니다.

  1. 문서화 작업을 위한 Swagger 애노테이션으로 인해 코드의 가독성이 떨어진다.
  2. Swagger 명세는 단순히 주석일 뿐이라, 실제 로직이 변경되고 실수로 문서를 갱신하지 않는다면 문서와 실제 API 스펙이 상이하게 나타나는 결과를 초래한다.
  3. 모든 오류에 대한 여러 가지 응답을 문서화할 수 없다.

위와 같은 문제를 Spring REST docs는 모두 해결할 수 있었기에 Spring REST docs로 전환하게 되었습니다.


토근 저장소 MySQL -> Redis

Front-end는 React를 사용하고, Back-end는 Spring을 사용하고 있었기에 로그인 관련해서는 JWT토큰을 이용해 구현했습니다.
이 과정에서 Refresh Token과 Access Token을 유효시간이 지나게 되면 expire 되도록 처리를 해야했는데 이 과정을 MySQL에서 진행하기에는 너무 번거로웠습니다.
Redis의 TTL기능을 이용하면 간단하게 구현할 수 있었기에 토큰 저장소로 Redis를 사용하게 되었습니다.
Redis를 처음 사용해보는 것이기에 초기에는 하나의 EC2에 Redis를 띄워 사용하였으나, 이광호 멘토님의 조언을 듣고 설계를 변경하게 되었습니다.

refac-redis

설계 초기처럼 하나의 Redis만 사용할 경우, Redis가 죽어버리면 로그인에 문제가 생기기 때문에 Master Redis 3대, Slave Redis 6대를 띄워 클러스터를 구축하였습니다.
따라서 하나의 Master Redis가 죽어도 Failover을 통해 Slave가 Master로 승격되어 동작하게 됩니다.


RabbitMQ -> Redis Expire Event + Spring batch

refac-message

결제 과정에서 그룹 수강신청의 경우, 그룹이 신청한 스케줄에 대해서는 그룹 인원만큼의 여석은 다른 고객이 신청하지 못하도록 막아서 확보해야 했습니다.
예를 들면, 0/8 인 상태에서 4명이 있는 그룹이 수강신청을 한다면 4/8 인 상태로 변경해야 했습니다. 이 과정에서 그룹원들이 결제할 때까지 시간을 무한정으로 줄 수 없기 때문에 30분으로 제한하도록 비즈니스 로직을 설계했습니다.
따라서 30분이 지난 후에 결제가 완료되지 않았다면 해당 그룹의 수강 신청을 취소시켜야했습니다.
처음에는 이 로직을 구현하기 위해서 RabbitMQ를 클러스터 구축하여 다음과 같이 구현했습니다.

수강 신청시 RabbitMQ로 메시지를 보내고, RabbitMQ Delayed Message Plugin를 이용해 30분이 지난 후에 처리한다.

사실 RabbitMQ도 처음 사용해 보는 기술이었기에 이광호 멘토님께 조언을 구했고, RabbitMQ도 결국 거대한 큐이기에 30분 동안 저장해두고 처리하도록 설계하게 될 경우, 수많은 요청이 몰리면 병목현상이 발생할 것이라고 조언해 주셨습니다.
이 비즈니스 로직 또한 결국 유효시간에 관한 문제였기에 Redis의 Expire Event를 사용하여 해결했습니다.
수강 신청 시, TTL을 30분으로 설정하여 Redis에 저장하고, 유효시간이 지나면 삭제되면서 pub/sub 방식으로 message를 쏘도록 만들어 준 뒤, Spring에서는 메시지 리스너를 구현해 메시지를 받아 로직을 수행하도록 구현했습니다.
하지만 이 메시지가 100% 리스너에 도착한다고 보장할수는 없습니다. 중간에 어떤 오류가 발생할수 있기 때문입니다.
따라서 이에 대한 안전 장치로 Spring batch + Quartz 를 사용하여 30분마다 배치 작업을 수행하도록 설계했습니다.


DB Replication

refac-db

초기에는 하나의 RDS를 가지고 모든 작업을 진행했습니다. 하지만 사용자가 많아져 트래픽이 늘어날 경우, 하나의 DB에서 쿼리를 모두 처리하기 힘들어지면서 병목현상이 발생할 수 있습니다.
DB를 이중화할 경우, Master에서는 쓰기/수정/삭제 연산을 처리하고 Slave에서는 읽기 연산만을 처리하여 병목 현상을 줄일 수 있습니다.


검색 기능 DB -> Elasticsearch

refac-elasticsearch

클래스 검색은 로그인 하지 않아도 검색 가능하기 때문에, 만약 서비스의 사용자가 많아져 검색 요청이 많아지면 데이터베이스에 부하가 생겨 장애 발생할 수 있습니다. 따라서, 검색에 대한 요청은 Elasticsearch로 옮겨 데이터베이스의 부하를 줄이고 검색 성능 개선하였습니다.


Flyway

refac-flyway

dev 환경에서는 단순히 ddl을 create-drop 또는 update 옵션을 사용하고 있었기에 DB에 대해 고민할 필요가 없었습니다.
하지만 운영환경에서는 ddl을 validate 또는 none 옵션을 사용해야하기 때문에 DB script를 뽑아서 따로 관리했습니다.
배포 후 기능을 추가하면서 변경되는 일이 많았고, 매번 일일이 스크립트를 관리하는 것이 번거로울 뿐 아니라 실수하기 딱 좋은 부분이라 Flyway를 이용해 데이터베이스 형상관리를 시작했습니다.


Cloud Watch -> Kibana

monitor

Cloud Watch에서 Kibana로 변경한데는 특별한 성능적인 이유는 없습니다.
초기에는 Cloud Watch를 사용하여 모니터링을 구축했습니다. AWS에서 몇번의 설정 클릭만으로 구축할 수 있기에 매우 편리했습니다.
이에 반해, ELK의 Kibana로 로그 모니터링을 구축할 경우, EC2에 filebeat를 심어주고, ELK를 구축하는 추가적인 작업이 필요했습니다.
검색 기능에서 Elasticsearch를 사용하기도 했고, 경험적 측면에서 추후에 많은 도움이 될 것 같아 ELK로 모니터링 시스템을 구축했습니다.


Jenkins

jenkins

CI/CD 구축을 처음 진행해보기에 가장 간단한 Travic CI로 구축을 연습하고 실제 프로젝트에 적용시키려고 했습니다.
하지만 SW Maestro에서 제공하는 Gitlab 계정으로 Gitlab, Travis CI 연동이 불가능했습니다.(프로젝트 진행 후반부에야 연동이 가능하도록 업데이트 되었습니다.)
따라서 다른 선택지가 없어 Jenkins와 AWS CodeDeploy를 이용해 Blue Green 무중단 배포를 구축했습니다.
프로젝트의 규모를 생각했을 때, 다양한 세팅과 서버를 구축해야하는 Jenkins가 최선의 선택은 아니라고 생각합니다.
하지만, 경험적 측면에서는 서버를 구축하고 Jenkins의 다양한 플러그인을 사용해볼 수 있었다는 점에서 좋은 선택이었던 것 같습니다.


코드 리팩토링

프로젝트 진행 기간은 7월 ~ 11월까지이고, 팀원은 대학생 3명으로 구성된 데다가 설계한 figma 화면 개수가 약 100장이 넘어가는 규모였기에 일정을 잡았을 때 기능 구현에도 시간이 빠듯하다고 느꼈습니다.
또한, 프로젝트를 기획하는 시점에서 백엔드를 담당할 최준성 팀원은 프로젝트 경험이 없는 비전공자였고, 고범석 팀원도 실무 경험이 없는 대학생이었습니다.
비즈니스 로직에 대한 설계에 대해서는 서로 어느 정도 이야기를 나누고 자신이 짠 코드에 대해서 설명하는 시간 정도는 가질 수 있었지만, 어떤 코드가 올바른 코드이고 깔끔한 방식인지에 대해서는 이야기를 나눌 수 없었기에 당연히 코드 리뷰를 진행할 수 없었습니다.
프로젝트 초기부터 주말 구분없이 매일 만나면서 오전부터 저녁까지 코드친 결과 프로젝트 후반부에서 시간적 여유가 생기게 되었고, Clean Code 책을 읽으면서 효율적인 코드에 대해 이야기를 나눠 리팩토링을 진행하게 되었습니다.


의미있는 이름과 함수

코드를 다시 되돌아보았을 때, 당시에는 이해할 수 있을 정도의 이름으로 지었다고 생각했으나 명확하게 와닿지 않는 네이밍들이 있었습니다.
따라서 주석이 필요 없을 정도로 명확하게 변수명과 함수명을 수정하였고, 함수의 경우 예외를 던진다면 마지막에 OrThrow를 붙여주었습니다.
함수에 대해서는 Clean Code에서 5줄 이내를 권장하고 있었습니다. 코드를 되돌아본 결과 생각보다 함수가 긴 것들이 존재했고 충분히 줄일 수 있는 수준의 내용들이었기에 할 수 있는 한에서 5줄 내외를 지키도록 수정했습니다.


JPA

JPA에 대해서는 서로 어느 정도 이해하고 있어, 적절한 fetch join을 사용하여 코딩했었기에 N+1 문제는 발생하지 않았습니다.
하지만 연관관계에 대해서 문제가 있었습니다.
가장 좋은 연관관계 설계는 단방향을 기초로 하되 필요하면 양방향 설계를 하는 것입니다.
JPA 프로그래밍의 저자, 김영한 선생님의 의견을 빌리자면 다음과 같습니다.

양방향으로 하면 복잡도가 높아지는 단점이 있지만 성능상 이점을 얻을 수 있습니다.
정말 성능이 너무 중요해서 쿼리 하나를 줄이는게 꼭 필요한 상황이라면 복잡해지더라도 최적화를 해야합니다.
반면에 쿼리가 하나 더 나가더라도 시스템 자원이 충분해서 성능에 영향을 미치는 것이 미미하다면 코드 복잡도를 낮게 유지하는 것이 더 중요합니다.

refac-mapped

기존 코드에는 왼쪽과 같이 무분별한 양방향 관계가 존재했고, 리팩토링 과정에서 불필요한 양방향 관계를 모두 끊어내고 정리했습니다.


QueryDsl 성능 개선

exist 메서드 금지

refac-querydsl-exist

기본적으로 JPA에서 제공하는 exists는 조건에 해당하는 row 1개만 찾으면 바로 쿼리를 종료하기 때문에 전체를 찾아보지 않아 성능상 문제가 없습니다. 하지만 QueryDSL에서 제공하는 exists 메서드는 count > 0 으로 쿼리가 나가기 때문에 전체를 다 찾아보고 결과를 반환하여 성능상 문제가 있었습니다.
따라서 QueryDSL에서 exists를 사용할 경우 한건만 찾으면 바로 쿼리를 종료하도록 fetchFirst()를 이용해 직접 구현했습니다.


Cross Join 회피

refac-querydsl-cross

queryDsl은 용빼는 재주가 있는 것이 아니고 그저 편리하게 query를 날려주는 도구일 뿐인데 너무 안일하게 코드를 작성한 것이 문제였습니다.
where 문에서 체이닝으로 타고 들어가기 때문에 cross join이 발생하게 되는데 이 부분은 join을 통해 cross join이 발생하지 않도록 수정했습니다.


조회컬럼 최소화하기

refac-querydsl-minimize

엔티티에 수정이 필요한 경우라면, Entity를 꺼내야겠지만 이외의 경우라면 굳이 Entity를 꺼낼 필요가 없습니다.
FK에 들어갈 Id가 필요한 경우라면 위와 같이 Id만을 가져와서 해당 엔티티를 새로 만들어 연관관계를 맞춰줄 수 있습니다.
실제로 DB에서는 FK인 Id값만을 요구하기 때문입니다.


refac-querydsl-minimize-dto

필요한 데이터가 명확하게 한정적이라면, 위와 같이 Member의 reward 총액 데이터값만 필요하다면 Member 엔티티를 꺼내서 찾는 것이 아니라, dto를 이용하여 필요한 데이터만 가져오도록 수정했습니다.


테스트 코드

네이밍

테스트 함수의 이름을 카멜 케이스를 사용했습니다.
하지만 스네이크 케이스가 조금 더 가독성이 좋다고 판단하여 테스트 함수 명을 스네이크 케이스로 수정했습니다.


상속

refac-test-extends

테스트에 필요한 중복적인 코드는 상속을 통해 여러 번 작성하지 않아도 되도록 했습니다.


Test Container

refac-testContainer

테스트에서는 H2 DB를 사용하지만, 실제 운영 DB는 MySQL를 사용하고 있기에 서로 문법이 100% 호환되지 않습니다.
H2를 사용할 경우, Bulk Insert 부분에서 쿼리가 정확히 나가는지 확인할 수 없었습니다.
Test 전체에서 MySQL Test Container을 띄워서 사용하기에는 수행 시간이 너무 오래걸리기에 호환되지 않는 문법에 한해서만 Test Container를 적용하여 테스트를 진행했습니다.


채팅

refac-chat

초기에 채팅기능구현에 Socket.IO, Web RTC 등 다양한 시도를 했습니다. 하지만 앱에서의 최적화되어있지 않아 구현에 어려움이 있었습니다.
그래서 실제 비즈니스에서 많이 활용 중인 Firebase의 데이터베이스를 사용하여 보다 앱 환경에서 최적화된 실시간 데이터 통신 서비스를 구현했습니다.


CORS

refac-cors

백엔드 서버 혹은 외부 API에서 데이터 요청 시 CORS 정책으로 인해 통신이 잘 이루어지지 않는 문제가 있었습니다.
임시적인 방편으로 보편적으로 사용되는 “Access-Control-Allow-Origin” 헤더를 통해 해결을 시도했으나 이 또한 문제가 있어 Proxy서버와 DNS를 통해 해결을 하였습니다.


💁‍♂️ Detail Role

  • 고범석
    • 팀장
    • Back-end
    • 그룹, 게시글, 클래스, 쿠폰, 리뷰, 문의, 결제 구현
    • DB 클래스 정보를 Elasticsearch로 옮기는 배치 작업 및 Elasticsearch 구현
    • DB 이중화 구성
    • Swagger, REST docs 문서화

  • 최준성
    • 기획자
    • Back-end & DevOps
    • 로그인, 마이페이지, 찜, 매칭, 알림, 공지사항, 리워드, 결제 취소 배치 작업 구현
    • 전체적인 AWS 환경 구축
    • Redis 구축
    • ELK 모니터링 구축
    • Swagger, REST docs 문서화

  • 김기완
    • PM
    • Front-end
    • 모든 Front-end 구현

시연 영상

시연 영상
최종 발표 진행 중에 사용한 시연 영상입니다.


✍️ Author

About


Languages

Language:Java 58.5%Language:TypeScript 39.5%Language:CSS 1.5%Language:Objective-C 0.1%Language:HTML 0.1%Language:Vim Snippet 0.1%Language:JavaScript 0.0%Language:Ruby 0.0%Language:Shell 0.0%Language:Dockerfile 0.0%Language:Starlark 0.0%Language:Swift 0.0%Language:C 0.0%