
Members
Backend 5명(인턴 2 포함) / Frontend 4명
GitHub Repository
Service URL
Presentation Video

Swagger
프로젝트 진행 방식
- 3-4일 주기로 스프린트 진행
- 스프린트 시작일에는 스토리 포인트 산정 및 회의, 마지막 일에는 스프린트 회고로 진행도 및 개선사항 체크
- 매일 13시, 18시30분 스크럼 진행, 스크럼 이후에 논의 사항 전달 및 회의
- JIRA, Notion, Slack, Discord 활용
본격적인 기능 구현 전, 팀원이 가져야할 마음가짐과 팀 내의 규칙들을 정의합니다.
프로젝트 아키텍처

ERD

기술 스택
🎒 백엔드
- SpringBoot 2.7.1
- Java 17
- Gradle 7.4.1
- Junit5
- MySQL
- Flyway
- Swagger
- AWS
- JPA
📃 문서/협업
- Jira
- Notion
- Slack
- Git
Develop Contribution
프로젝트에서 어떤 개발을 진행했으며 어려움을 겪었다면 어떻게 해결해 나갔는지에 대한 내용입니다.
자세한 트러블 슈팅 과정은 링크로 연결된 페이지에 있습니다.

# 확장성을 고려한 JWT(RefreshToken 방식 + Redis), OAuth 적용
추후에 Scale out이 일어날 상황을 고려해 확장이 용이한 JWT를 선택
# 동적쿼리, 커서 페이징 방식의 조회 API 구현
상황에 따라 유연하게 사용할 수 있도록 Querydsl을 사용해 동적 쿼리 API 구현
커서 페이징 방식으로 오프셋 페이징 방식에 비해 성능적으로 안정적인 API 구현
쿼리 파라미터
- 매치 상태(모집중, 모집완료, 경기완료)
- 카테고리(축구, 야구, 배드민턴 등)
- 날짜 타입(생성일, 매치 예정일)
- 작성자
- 해당 파라미터를 사용할 경우 거리에 상관 없이 작성자의 모든 공고글을 불러옴(작성자 글 모아보기 기능)
- 조회 Size(한번 조회할 시 불러올 데이터 개수)
- 거리(5km ~ 40km)
# 이미지로 위장한 불순한 데이터 업로드 방지를 위한 Validator 구현
확장자만 바꾸면 이미지로 인식한다고?
문제
이미지가 아니어도 이미지를 가장하도록 확장자를 바꾸면 이미지로 인식해 업로드되는 문제 발생
분석 및 개선 방향
검색 결과 이미지에는 Signature 데이터가 있고 이것은 확장자가 바뀌어도 변하지 않음.
이것을 활용해서 Validator를 구현하면 문제를 해결할 수 있을것으로 판단
해결
본 프로젝트의 애플리케이션에서 허용하는 이미지 파일 확장자와 확장자에 맞는 Signature를 Enum에 정의하여
확장자를 우선으로 확인하고 지원하는 확장자라면 파일을 16진수 값으로 읽어들여 해당 확장자의 Signature와 일치하는지 확인하는 식으로 Validator 구현하여 해결
# 인증 여부 2차 검증을 위한 @AuthenticationPrincipal 어노테이션 커스텀
@AuthenticationPrincipal 커스텀
문제
@AuthenticationPrincipal을 사용하는 API에 대해서 인증이 되었는지 Controller단에서 2차 검증 코드가 필요
기본적으로 해당 어노테이션은 인증이 되지 않아 Anonymous로 인증 객체가 들어간다면 바인딩이 되지 않아 NPE가 발생하여 NULL 체크를 위한 코드가 각각의 API에 모두 들어가야 했기 때문에 중복이 발생하고 핵심 로직의 가독성을 저해
분석 및 개선 방향
@AuthenticationPrincipal에 인증 객체가 어떻게 바인딩 되는지 분석한 후AuthenticationPrincipalArgumentResolver을 커스텀해 인증 객체가 올바르지 않을 경우 예외를 발생 시킨다면 개선할 수 있을듯 해보였음.
해결
CustomAuthenticationPrincipalArgumentResolver를 만들어 인증 객체가 올바르지 않을 경우NotAuthenticatedException 이라는 커스텀 예외를 발생 시키도록해 @AuthUser 어노테이션을 사용하기만 한다면 인증 여부를 판단하고, 인증이 되지 않았다면 예외를 발생시켜 중복, 가독성 문제를 해결.
문제
@AuthenticationPrincipal을 커스텀한 @AuthUser 어노테이션을 추가하고 사용한 후 스웨거 명세에@AuthUser 객체가 명세되는 문제가 발생
분석 및 개선방향
Swagger의 기본 설정에는 @AuthenticationPrincipal 이라는 어노테이션을 기본적으로 Ignore하는 설정을 갖고 있었음.
하지만 새로만든 @AuthUser어노테이션은 Ignore 설정이 되지 않았기 때문으로 파악
새로만든 @AuthUser 어노테이션도 @AuthenticationPrincipal 처럼 Ignore 되도록 설정해주면 해결될 것으로 판단
해결
addAnnotationsToIgnore() 함수를 사용해 커스텀한 어노테이션도 Ignore 되도록 해결
# 환경별로 다른 설정 값들을 유연하게 사용하기 위해 설정들을 yml로 외부화
환경별 Cookie 옵션 설정
문제
JWT Token을 Cookie에 저장하는 방식을 적용했지만, 협업하며 개발 시에 환경별로 디버깅 및 테스트를 위해서 옵션을 자주 변경 해줘야 했음. 기존의 방식으로는 매번 환경이 바뀔 때마다 Cookie를 생성하는 옵션을 일일이 바꿔주어야해서 번거로움을 느꼈음
분석 및 개선 방향
Cookie 옵션을 각 환경별 yml 파일로 분리한다면 유연한 변경이 가능해 보였음
해결
Cookie 옵션을 .yml 파일로 외부화하고 생성자바인딩 방식으로 Property를 사용하여 유연하게 사용할 수 있도록 개선.
같은 맥락으로 환경별로 바뀔 수 있는 다른 설정들도 yml로 외부화 했음
# S3, OAuth 도입 후 발생한 통합 테스트 의존성 문제 해결
OAuth, S3를 도입 후 통합 테스트 의존성 문제 발생
문제
프로젝트 진행 중 OAuth와 이미지 업로드를 위한 S3를 도입 후 테스트 코드에서 S3Uploader와 OAuth를 사용하지 않는데 S3 설정을 하지 않았다고 빈 생성이 불가능했고 이를 해결하기 위해 불필요한 의존성 주입이 필요해 문제가 발생
분석 및 개선 방향
하지만 S3Uploader나 OAuth를 사용하는 테스트는 한정적이었고 다른 테스트 코드에는 의존성 주입이 불필요 하다고 판단.
이러한 문제를 해결하기 위해서 환경별로 유연하게 Bean을 등록하거나 등록하지 않을 수 있다면 이 문제를 해결할 수 있을 것이라고 판단.
해결
해결 방법을 고민 중 @MockBean 어노테이션을 사용하는 방법을 사용 해봤지만 모든 통합 테스트 코드에서 어노테이션을 넣어줘야 하는 단점이 있었음
@Profile 어노테이션과 Optional을 이용해 프로필에 따라 선택적으로 의존성을 주입 받는 방법을 선택해 해결
# Feign Client를 활용해 외부 API(Reverse Geocoding)를 사용해 사용자 지역명 제공
Feign Client 맛보기
매치 데이터 조회 시 위도 경도 데이터를 바탕으로 지역명 정보도 보내달라는 추가 요구사항 발생
RestTemplate과 FeignClient를 비교해 FeignClient 선정
Feign Client 장점
- 관심사 분리 : 외부 API 호출용 별도의 인터페이스 사용
- 테스트 용이성 : 호출 URL 변경 시 테스트 코드에 변경이 없어도 됨
# 통합 배포 자동화를 위한 Github Action, S3, CodeDeploy를 이용한 CI/CD 구축
CodeDeploy에서는 왜 환경변수가 안 불러와져?
문제
CodeDeploy를 통해 배포할 때, 배포 완료 후 스크립트로 JAR파일 실행 시 시스템 환경 변수에 지정해둔 복호화 키, profile 정보 등을 불러와 사용하려 했지만 환경변수가 불러와지지 않는 문제점이 발생
분석 및 개선 방향
많은 실험과 고민을 한 결과 결론은 CodeDeploy를 통해 Script를 실행할때에는 기본적으로 .bashrc를 사용하지 않음.
CodeDeploy로 배포할 때에도 .bashrc 를 사용할 수 있게된다면 문제를 해결할 수 있을 듯 해보였음.
해결
CodeDeploy에서 실행 시킬 script 파일에서
source ~/.bashrc
명령어를 통해 .bashrc를 사용할 수 있도록 추가해주어 해결# 로그 확인의 불편함 개선을 위한 AWS Cloud Watch를 활용한 로그 모니터링 구축
콘솔로 로그 확인은 너무 어려워요
문제
기존에는 로그를 확인하기 위해 SSH로 EC2에 접속해 일일이 cat, tail 명령어 또는 vim 편집기로 로그 파일을 찾아 봐야하는 번거로움이 있었고, 로그 파일이 쌓임에 따라 날짜별, 시간대별 로그를 확인하는 것이 번거로워졌음
그에 따라 프론트엔드와 협업 중 디버그, 테스트도 느려짐
해결
AWS CloudWatch를 도입 후, 콘솔환경이 아닌 웹 환경에서 원하는 날짜별, 시간대별로 확인할 수 있었고 다음과 같은 디버그 프로세스를 빠르게 진행할 수 있었음.
- 프론트엔드 측에서 문제를 식별하고 문제 식별 상황, 에러응답(HttpStatus, 에러 코드, 대략적인 에러 메시지, 발생 시간 등)을 백엔드 측에게 전달
- 해당 시간에 전달받은 에러 사항으로 발생한 로그를 CloudWatch에서 확인
- 로그를 토대로 코드 분석 및 디버깅
# 반복적인 시드 데이터 삽입 자동화를 위한 Flyway 활용
문제
짧은 프로젝트 기간에 따라 DB 스키마가 자주 바뀌는 상황에서 테스트용 시드 데이터를 수동으로 삽입 시키는 것이 번거로웠고 변경 이력을 추적하기가 쉽지 않았음
해결
Flyway를 활용해 변경 이력을 추적해 문제 발생 시 대처하기 편리하고 시드 데이터 또한 애플리케이션 실행 시 자동으로 삽입 시켜 주므로 번거로움을 해결
Improved
프로젝트 기간이 끝난 후 개선 작업을 수행했습니다.
# 채팅 기능 개선
짧은 프로젝트 기간으로 인해 웹 소켓을 이용한 채팅 구현 대신 폴링 방식을 선택 후 추후에 개선하고자 했음
프로젝트 기간동안 담당한 영역은 아니지만 개인적으로 많이 아쉬웠던 부분이어서 개선하고자 했음
- 폴링 방식을 웹 소켓 구조로 변경
- 다중 서버 환경에서는 로드 밸런싱으로 인해 대상 서버가 달라질 시 채팅 커넥션이 유지되지 않는 문제 발생
- Redis Pub/Sub 구조를 이용한 해결
- Nginx 프록시 서버를 이용한 로드밸런싱 시 커넥션 연결할 때 필요한 Upgrade 헤더가 백엔드 서버로 전달되지 않아 정상적인 연결이 불가능
- Upgrade 헤더는 hop by hop 헤더로 프록시 서버를 거치면 사라지고 소켓 서버로 전달되지 않는 헤더임을 인지
- Nginx 설정에 웹 소켓 연결 엔드포인트에 대해 헤더 세팅으로 해결
# 매치 조회 성능 개선
조회 시 매번 위, 경도를 주소 명으로 변환하는 구조로 인한 성능 저하 개선
- 매 레코드 마다 외부 API 통신으로 인한 성능 저하

- DB 테이블 local_name 컬럼 추가, 매치 작성 시 미리 변환된 유저 위치 정보를 삽입 → 조회시 변환 불필요


- 구조 변경 전

- 구조 변경 후

Match 테이블 인덱스 적용
추가적으로 where 조건절에 가장 많이 쓰이고 다른 컬럼에 비해 카디널리티가 높다고 판단된 생성일 컬럼에 인덱스 적용
- 적용 전

- 적용 후

최종적으로 약 100만건의 매치 데이터에서 비정상적으로 오래 걸리던 조회 성능을 약 50ms 까지 개선