06/13 ~ 07/01 기간 동안 진행한 issue-tracker 프로젝트 회고록
코드스쿼드의 마지막 프로젝트로 3주 간 BE 2명과 FE 2명이 함께 issue-tracker 프로젝트를 진행했다. issue-tracker는 GitHub Repository의 issue 기능을 만드는 프로젝트로 이슈 필터 기능을 중점적으로 구현하고자 했다.
프로젝트 저장소
미션에서 한 일
- 요구사항 분석, 도메인 설계, 테이블 설계, API 설계
- MockService를 통한 MockAPIServer 구현
- Swagger를 통한 API 문서 작성
- VPC 서버 아키텍처
- GitHub Actions와 CodeDeploy를 통한 CI/CD
- 로그인 기능
- JWT + GitHub OAuth를 통한 로그인 기능
- Redis를 통한 Refresh token 관리
- annotation을 이용한 로그인 인증
- S3를 이용한 파일 업로드 기능
- Spring Data JPA, QueryDSL
- 이슈 필터링을 위한 동적쿼리
- Paging
- 삽질기록
- 학습정리 기록
😍 좋았던 것
프로젝트 멤버 모두 올빼미족이라서 데일리 스크럼을 첫날 제외하고 오후 5시 30분에 진행했다. 매일 BE 진행상황, FE 진행상황을 공유하면서 API 문서에 대해 토론도 진행했다. 매일 진행상황을 공유하면서 개발 속도를 맞출 수 있어서 좋았다. 또 프론트와 협업이 많이 필요한 로그인 기능을 구현할 때는 같이 모여서 충분히 토론한 이후에 구현을 진행하는 등 소통이 잘 되어서 좋았다. 이 과정에서 너무 백엔드 중심의 API설계를 하거나 잘못된 로그인 flow를 프론트와 토론하는 과정에서 바로잡을 수 있었고 클라이언트의 시각에서 다시 한 번 생각해 볼 수 있어서 좋았다. 또 클라이언트의 입장에서 생각하기 위해서는 클라이언트에 대한 지식이 있어야 겠다는 생각을 하게 되었다. 나중에 간단하게 프론트 공부도 해야겠다.
프로젝트 첫 날 요구사항 분석, 기본 설계를 진행했다. 아직 테이블 설계나 ERD 설계에 미숙한 점이 많은데 같이 프로젝트를 진행하는 후가 도움을 많이 줘서 같이 기본 설계를 수월하게 진행할 수 있었다. ERD 설계가 있으니 프로젝트의 규모나 구조가 더 잘 보이고 내가 지금 무엇을 하는지 확실하게 인지하면서 프로젝트를 진행할 수 있어서 좋았다.
- Mockup API Server (Swagger + MockService)
지난 미션에서는 postman을 통한 Mockup API Server를 구축했었다. 프로젝트를 진행하면서 API를 변경하는 일이 생기면 코드 레벨에서 고치고 postman에서 API 문서를 한 번 더 고쳐야 한다는 불편함이 있었다. 이때문에 바로바로 수정을 하지 못해서 지난 미션에서는 Mockup API Server와 서비스하고 있는 실 서버의 API가 다른 경우가 있었다. 이에 코드레벨에 가깝게 Mockup Server를 두어야 한다고 판단하여 MockService를 구현하여 자바에서 Mock Data를 직접 생성한 후 빠르게 배포를 하여 Mockup API Server를 만들었다. 이 때 Swagger도 연동을 하여 코드 레벨로 API 문서를 관리하니까 API의 변경이 있어도 바로 반영을 할 수 있었다.
같은 BE 멤버인 후와 많은 과정을 페어 프로그래밍으로 진행했다. Intellij의 code with me를 통해서 페어 프로그래밍을 하거나 협업을 했다. 코드를 작성하면서 의견이 다른 부분은 사소한 것 하나까지 서로의 입장을 이야기해보고 무엇이 더 좋을지에 대해 이야기를 많이 나누었는데 이 과정에서 나와 다른 시각을 배울 수 있어서 좋았다.
처음 써보는 기술 또는 공부가 필요한 키워드들을 적어 놓고 각자 공부한 이후에 노션에 정리한 후 서로 설명을 했다. 이 과정에서 혼자 공부하는 것보다 훨씬 더 많이 배울 수 있어서 좋았다.
이번 프로젝트를 진행하면서 파일 S3 업로드 후 링크 반환, CORS, Redis, CodeDeploy, annotation을 통한 인증 처리 등 새로운 기술과 기능을 적용해보았다. 새로운 기술을 학습하고 적용하는 것을 계속 반복하다 보니 점점 익숙해지고 어떻게 해야 할지 감이 잡히는 것 같다.
📚 배운 것
처음으로 프론트엔드와 작업을 하게 되면서 CORS 문제를 해결해야 했다. 스프링이 간편하게 CORS 문제를 해결해주지만 CORS가 무엇이고 왜 서버에서 해결을 해야 하는지 궁금해서 이와 관련되어 공부를 진행했고 프로젝트에서 CORS 문제를 해결하도록 설정해주었다.
- QueryDSL에서 동적쿼리 & MultipleBagFetchException 해결
이전에도 QueryDSL을 사용했었지만 이 번 미션처럼 많은 엔티티를 fetch join하고 동적쿼리를 사용해야 할 상황이 없었다. 이번 미션을 진행하면서 N+1 문제를 해결하고자 1 : N 관계에 있는 엔티티 여러 개를 fetch join 해야 했었는데 MultipleBagFetchException이 발생하였었다. QueryDSL에서는 한 개의 1 : N 관계만을 fetch join할 수 있으므로 자료구조를 List가 아닌 Set으로 변경하여 여러 1 : N 관계를 fetch join하여 N + 1 문제를 해결했다. 또 굉장히 많은 조건을 동적쿼리로 만들어 볼 수 있어서 QueryDSL 사용법을 조금 더 익힌 것 같다.
Redis를 학습한 이후에 프로젝트에 적용하여 기존 메모리에 저장하고 있던 refresh token을 Redis에 저장하도록 변경했다. 기존 메모리에 저장하던 방식은 서버가 여러 대일 경우 한 곳의 서버에만 계속 요청을 보내야 한다는 단점을 Redis로 분리하여 서버가 여러 대일 때 어떤 서버에 요청하여도 제대로 된 응답을 내릴 수 있게 하였다. 또한 Redis는 일정 기간 이후에는 삭제할 수 있는 기능을 사용해서 간편하게 refresh token을 관리할 수 있게 되었다.
- annotation을 이용한 로그인 인증 처리
interceptor만을 통해 로그인 인증 처리를 하고 있었다. 이렇게 하다 보니 회원가입을 진행하는 POST /api/members 는 로그인 인증 처리를 하면 안 되고 멤버 리스트 조회를 위한 GET /api/members 는 로그인 인증 처리를 해야 하는 상황이 생겼다. interceptor에서 request를 통해 /api/members 일 경우 HttpMethod가 GET이면 인터셉터 실행, POST면 인터셉터 실행하지 않는 등의 로직을 넣고 싶지 않아서 @LoginRequired 라는 커스텀 어노테이션을 정의하고 로그인이 필요한 요청인 메서드에만 @LoginRequired 어노테이션을 통해 로그인 인증 처리를 하도록 인터셉터를 수정했다.
💦 부족했던 것
- CORS를 세팅한 이후에 문제없이 잘 동작하다가 3주 차 수요일에 preflight 요청에서 CORS 문제가 발생했는데 해결하지 못했다. OPTIONS 요청이 제대로 처리가 되지 않는 것 같은데 마지막까지 제대로 해결을 하지 못해 아쉽다.
- 3주 차에 체력을 모두 소진한 상태라서 집중이 잘 되지 않았다. 체력관리를 한다고 했는데 실패했다.