사과마켓 프로젝트 회고

2023. 10. 9. 14:29회고

이번에 코드스쿼드 부트캠프에서 진행한 사과마켓 프로젝트를 마무리하면서 기억이 없어지기 전에 회고록을 작성하고자 합니다. 회고록에는 프로젝트의 기본적인 정보와 프로젝트를 하면서 좋았던 점, 아쉬운점, 개선할점, 어려웠던 점들에 대한 내용들이 작성되어 있습니다.

 

프로젝트 기간 : 2023-08-21 ~ 2023-10-06 (7주)

팀원구성 : 백엔드 2명(A팀), 2명(B팀), 프론트 1명(FE팀)

특이사항

  • 다른 백엔드 2명이 같은 주제와 API 명세서를 이용하여 구현하고 프론트 팀원이 개발한 UI를 같이 사용하여 각각 A팀, B팀이 따로 개발합니다.
  • 기존 프론트 엔드가 2명이었으나 1명이 도중에 하차하게 되면서 3주차 부터는 1명이 개발하게 되었습니다.
  • 7주 기간 중 1주는 추석이 포함되었습니다.

 

주제 : 중고거래로 잘 알려진 당근 마켓을 패러디한 사과 마켓을 구현합니다. 사과 마켓은 회원이 자신의 사는 동네를 등록하고 동네에서 판매할 상품을 등록하거나 반대로 파는 상품들을 구매할 수 있도록 환경을 제공합니다.

 

기술스택

  • 앱 서버 기술 스택
    • Spring Boot 2.7
    • Spring Data JPA, QueryDSL
    • webflux
    • mysql, redis
    • aws-java-sdk-s3
    • jjwt
  • 웹 서버 기술 스택
    • React: 사용자 인터페이스 구축
    • Vite: 웹 개발 빌드 도구
    • react-router-dom: 라우팅 관리
    • styled-components: 컴포넌트 스타일링
    • svgr: SVG를 React 컴포넌트로 변환
    • TypeScript: 정적 타입 검사
    • msw: 프론트엔드 API 모킹
    • Jest: 테스팅 프레임워크
    • react-query: 데이터 가져오기 및 캐싱
    • zustand: 전역 상태 관리 라이브러리

 

1. 좋았거나 배웠던 점

1.1 이미지 업로드 서비스 구현

 사과마켓을 구현하면서 서비스를 요청할때 단순한 문자열뿐만 아니라 이미지(회원의 프로필 사진, 상품 게시글 이미지)를 같이 첨부하여 등록해야 하는 경우가 많았습니다. 대표적으로 회원가입, 프로필 사진 변경, 상품 게시글 등록, 상품 게시글 수정 기능 등이 있었습니다. 해당 서비스들을 구현하면서 multipart/form-data라는 컨텐츠 타입으로 데이터를 받는 방법을 배웠고 이미지들을 S3 버킷에 저장하는 서비스들을 구현해볼 수 있어서 좋았습니다.

 

 이미지를 저장하는 방식은 이미지를 multipart/form-data 방식으로 받고 스프링 서버에서 S3 버킷에 저장하는 방식으로 구현하였습니다. 다른 방식으로는 Presigned URL 방식을 이용하여 S3에 먼저 업로드하고 url을 생성하는 방식이 있습니다. 이 방식은 이미 개발이 완료된 시점에서 이러한 방식이 있다고 들었고 구현 방식을 변경하는데 시간이 없다고 판단하여 Presigned URL 방식으로는 변경하지 않았습니다. 다른 프로젝트를 하게 되어 이미지를 업로드하는 서비스를 구현하게 된다면 해당 방식으로 구현 해야겠다고 생각하였습니다.

 

 

 회원가입과 상품 게시글 등록시 이미지를 첨부하여 등록하는 서비스를 구현해야 했습니다. 기능에 따른 테스트 케이스를 작성하기 위해서 MockMultiFile라는 클래스를 이용하여 테스트용 샘플 이미지를 저장하고 테스트를 진행하였습니다. 이러한 테스트 케이스 구현 과정에서 Mock 이미지 파일까지 다뤄 볼 수 있어서 좋았습니다.

 

1.2 롱 폴링 방식을 이용한 채팅 구현

 기획서와 요구사항에 채팅 기능에 대한 부분이 있었으나 정확히 어떠한 방식으로 구현하라는 것은 명시되어 있지 않았습니다. 채팅 기능을 구현해본적이 없어서 구글링을 해봤는데 처음에는 웹 소켓에 대한 키워드가 많이 보여서 웹 소켓 방식으로 구현하려고 했으나 마스터의 조언으로 요구사항에 구현방식에 대한 별도의 명시가 없다면 애플리케이션의 환경과 특성을 고려해서 무조건적인 웹 소켓 방식으로 구현하지 않아도 된다고 조언하였습니다.

 

 그래서 사과마켓의 환경을 고려했을때 채팅 기능이 1:1 채팅이라는 점과 채팅이 주요한 서비스가 아니기 때문에 카톡처럼 지속적인 실시간성을 제공하지 않아도 된다고 생각했습니다. 그리고 백엔드가 2명이라는 점과 채팅 외에 다른 기능들을 구현했을때 시간이 얼마 남지 않을 것이라고 생각하였습니다. 그래서 1:1 채팅 방식에 적합하고 구현 시간도 웹소켓 방식에 비해 상대적으로 적을것이라고 생각하여 롱 폴링 방식으로 구현하기로 하였습니다.

 

 

 결론적으로 롱 폴링 방식을 통하여 일정시간 실시간성으로 작동하는 채팅 기능을 구현해볼 수 있었고 프로젝트 기한에 맞추어 완성할 수 있었습니다. 또한 롱 폴링 방식으로 구현하기 위해서 DefferedResult이라는 클래스를 사용하였고 리액티브 프로그래밍의 맛을 볼 수 있어서 좋았습니다.

 

1.3 OAuth 서비스 개발 과정중 에러 해결

 프론트 역할인 팀원은 로컬 개발 환경에서 개발하며 API 요청을 개발 배포 서버로 요청하는 형태로 개발을 진행하였습니다. 하지만 개발 배포 서버에서는 잘 동작하는 OAuth를 이용한 회원가입이 프론트 팀원의 로컬 개발 환경에서는 동작하지 않는 문제가 있었습니다.

 

 문제 원인은 redirect_url 때문이었습니다. 로컬 개발 환경에서 인가 코드를 받을때는 redirect_url을 “http://localhost:5173/my-account/oauth”으로 전달해서 인가 코드를 받았고 받은 인가 코드를 그대로 회원가입 요청할때 전송하였습니다. 인가 코드와 회원가입 요청을 받은 스프링 서버는 액세스 토큰을 발급받을때 OAuth 서버에 전달하는 redirect_url은 클라이언트로부터 전달받은 redirect_url이 아닌 배포 서버에서 관리하고 있는 고정된 프로퍼티인 “http://applemarket.site/my-account/oauth”을 전달해서 발급받습니다. 그래서 인가코드를 받을때의 redirect_url과 액세스 토큰을 발급받을때의 redirect_url이 서로 달라서 에러가 발생한 것이었습니다. 이 문제를 해결하기 위해서 쿼리 스트링으로 redirect_url을 받아서 그대로 OAuth 서버에 액세스 토큰을 발급받을때 전송하도록 하였습니다. 만약 별도의 redirect_url을 입력받지 않는다면 배포 서버에서 관리하고 있는 redirect_url을 사용하여 액세스 토큰을 발급받고자 구현하였습니다.

 

https://github.com/masters2023-project-03-second-hand/second-hand-max-be-a/issues/134

 

[bug] 배포 환경과 로컬 환경에서의 OAuth 로그인 문제 · Issue #134 · masters2023-project-03-second-hand/second-

상황 배포 환경에서 UI를 이용하여 카카오 OAuth 회원가입은 잘됬으나 프론트엔드 개발자가 로컬 환경 UI에서 배포환경의 스프링 서버로 카카오 OAuth 회원가입을 요청할때 회원가입이 되지 않는

github.com

 

문제 해결 과정에서 마스터의 도움으로 redirect_url 문제를 해결하였는데 해결 과정중 공식문서와 스택 오버플로우를 이용하여 검색하는 방법을 배우면서 한 것이 인상깊었습니다. 그리고 라이브러리 메소드를 단순히 호출하는 것이 아닌 호출하고나서 받은 리스폰스 엔티티 결과를 파싱 및 로깅해서 단순히 호출만해서 받았을때 모호한 에러 내용에서 정확하게 어떤 것이 문제인지 알 수 있게 분석한 과정이 좋았습니다.

 

1.4 상품 게시글의 뷰 카운팅 문제 해결

 사용자가 상품 게시글의 상세 페이지로 이동하면 조회수를 증가시켜야 했습니다. 그런데 단순히 사용자가 상세 페이지로 접근하게 되면 조회수 카운트를 1개씩 증가시키게 된다면 비정상적으로 조회수를 증가시킬 수 있다고 생각하였습니다. 그래서 redis 저장소에 회원이 해당 상품을 조회했다는 기록을 저장하여 조회수의 중복된 카운팅을 해결하였습니다. redis에 저장된 조회 기록은 1분으로 설정하였고 1분후에는 자동으로 만료되서 삭제되게 구현하였습니다. redis에 저장되는 키(key)는 “{로그인 아이디}-itemId: {상품 아이디}”와 같은 형식으로 구성되고 값은 “true”와 같이 저장하였습니다.

 

1.5 mysql 컨테이너에 볼륨을 설정하여 데이터 유지하도록 개선

 mysql 컨테이너에 별도의 볼륨을 설정하지 않아서 개발 배포 서버에 배포될 때마다 컨테이너가 다운되었다가 다시 실행되는 과정에서 테이블이 제거 되었다가 다시 생성하게 되고 데이터도 다 삭제 되는 과정을 겪었습니다. 처음에 이렇게 설정한 이유는 엔티티가 변경되는 경우가 많아서 편의성을 위해서 테이블을 다시 초기화하는 방법이 좋았습니다. 그런데 시간이 지날수록 엔티티가 변경되는 경우가 없어지고 배포 서버에서 등록한 샘플 데이터를 다시 삽입하는 것은 매우 불편하였습니다. 그래서 배포될때 mysql 컨테이너에 볼륨을 설정하여 테이블이 배포될때마다 삭제되지 않고 데이터를 유지하도록 개선하였습니다.

 

 처음에는 볼륨을 설정하였음에도 데이터가 유지되지 않았었는데 알고보니 application.yml 파일에서 dev 프로파일에 JPA 옵션중 하나인 “ddl-auto” 옵션을 “create”로 설정하여서 테이블이 삭제되고 다시 생성되서 볼륨의 데이터가 삭제된 것이었습니다. 옵션값을 “none”으로 변경하여 문제를 해결하였습니다.

 

1.6 json 데이터 형식의 문자열을 enum 타입으로 변환하는 방법을 학습

Request Body의 json 문자열을 입력으로 받을때 String 타입의 문자열을 enum으로 변환하여 입력받는 방법을 배웠습니다. jackson 라이브러리를 이용하여 json 데이터를 객체로 역직렬화시 @JsonCreator 애노테이션을 이용하여 특정한 생성자나 팩토리 메소드를 사용하여 enum으로 변환할 수 있었습니다.

 

1.7 카테고리 데이터에 캐싱을 적용하여 불필요한 쿼리를 날리는 일을 방지

 카테고리 목록 데이터는 고정적이고 변동될 가능성이 매우 적기 때문에 스프링 서버에서는 “@Cacheable” 애노테이션을 이용하여 카테고리 목록 서비스 요청시 불필요한 쿼리가 발생하는 것을 방지할 수 있었습니다. 그리고 클라이언트에게는 응답시 set-max-age를 설정하여 이 응답 데이터가 캐싱 데이터라고 알려줄 수 있도록 구현하였습니다.

 

2. 아쉬웠던점

2.1 생각보다 오래 걸린 Oauth를 이용한 회원가입 및 로그인 서비스

 지난 프로젝트인 Issue Tracker 프로젝트에서 Spring Security 없이 Oauth 로그인을 구현을 해봤기 때문에 프로젝트 시작인 1주차때 구현을 완료할 줄 알았습니다. 그런데 회원가입에도 Oauth 로그인을 요구하게 되었고 회원가입시 프로필 사진으로 사용할 이미지를 첨부한 서비스를 구현하게 되면서 테스트 케이스도 작성하게 되서 생각보다 시간이 많이 소모되었습니다. 예상 시간으로 1주만에 될줄 알았지만 총 2주정도 소모되었습니다.

 

3. 개선할점

3.1 개발 서버를 배포할때 버전화 작업

 개발 배포 서버 브랜치는 “dev” 브랜치로 정하고 Github Action을 이용하여 CI/CD를 수행하였습니다. 이 과정에서 별도의 배포 버전화를 하지 않았습니다. 다음 프로젝트 때는 배포할때 버전화를 할 수 있도록 학습하고 적용하기로 생각하였습니다.

 다른조의 마스터 피드백 시간때 이 부분이 피드백되었지만 아직까지는 불편하점을 느끼지 못하고 개발단계여서 넘어갔었습니다. 그런데 만약 현재 버전에서 문제가 있어서 이전 버전으로 리셋해야 한다면 어렵다고 생각했습니다. 그래서 배포 버전화에 대한 별도의 학습이 필요하다고 생각하였습니다.

 

3.2 mysql 컨테이너 다시 살리기

 개발 배포 서버에서 db를 mysql 컨테이너를 사용하였는데 아침에 일어나보면 가끔식 mysql 컨테이너가 죽어서 개발 배포 서버가 제대로 동작하지 않는 것을 알게 되었습니다. 아무래도 t2.micro 인스턴스를 사용해서 일시적으로 메모리가 부족한 것일 수 있다고 생각하였습니다. 그래서 mysql 컨테이너가 죽어있으면 수동 명령어로 다시 mysql 컨테이너를 살렸습니다. 컨테이너가 다운되면 자동으로 다시 살리는 방법의 필요성을 느꼈습니다.

 

4. 어려웠던 점

4.1 롱 폴링 방식의 채팅 구현

 사과 마켓에서 제일 어려웠던 기능은 채팅 기능이었습니다. 별도의 새로고침 없이 실시간으로 상대방의 채팅 메시지를 전송 받기 위해서는 새로운 기술이 필요하였습니다. 마스터의 조언으로 채팅 기능을 구현하기 위해서는 여러 방식이 있습니다. 대표적으로 폴링, 롱폴링, 웹 소켓 방식이 있습니다. 결국에는 채팅 구현 방식을 롱 폴링 방식으로 하였는데 이유는 사과마켓 채팅 특성상 1:1 채팅이라는 점과 사과마켓의 주요한 서비스 이용은 상품 등록과 조회라고 생각하였습니다. 그래서 채팅같은 경우는 카톡 처럼 지속적인 실시간성을 부여하지 않아도 되고 일정 시간동안만 실시간성을 부여하면 사과마켓에서의 주요한 채팅 기능은 작동한다고 생각했습니다. 또한 두 사용자간의 채팅 메시지들의 핑퐁의 빈도가 매우 적을 것이라는 점도 하나의 이유였습니다.

 

 롱 폴링 방식으로 구현한다고 하더라도 간단하게 구현할 수는 없었습니다. 아무래도 처음 만들어보는 기능이었고 반응적으로 작동해야 하는 부분도 있었기 때문입니다. 예를 들어 클라이언트가 새로운 메시지가 있는지 서버에 롱 폴링 요청을 날리고 대기하는 동안 도중에 새로운 메시지가 추가되면 응답받는 부분이 그랬습니다. 그래서 롱 폴링 개념을 학습하기 위해서 구글링도 하고 리액티브 프로그래밍 강의도 들었던 것 같습니다.

 

 하지만 웹 소켓보다는 학습의 난이도가 낮았고 구현시간은 상대적으로 웹 소켓보다는 낮았기 때문에 프로젝트 기한에 맞추어 요구사항을 만족하는 채팅 기능을 구현할 수 있었습니다.

 

4.2 상품 게시글 수정시 썸네일 교체

 상품 게시글 수정시 썸네일도 변경될 수 있기 때문에 경우의 수가 많음을 알게 되었습니다. 예를 들어 대표적으로 3가지 경우가 있습니다.

  • 상품 게시글 수정시 기존 선택한 썸네일을 유지하는 경우
  • 상품 게시글 수정시 새로 추가한 썸네일을 선택하는 경우
  • 상품 게시글 수정시 기존 추가된 이미지들중 현재 선택한 썸네일을 제외한 다른 이미지를 선택하는 경우

 추가된 이미지(기존, 새로 추가된 것들 포함)들 중에서 어떤 것이 썸네일 사진인지 별도로 표시하지 않아서 더욱 어려웠습니다. 결국에는 썸네일이라는 boolean 타입의 컬럼을 추가하여 해당 데이터가 썸네일이라고 표시하여 문제를 해결했습니다. 그리고 상품 게시글 수정시 썸네일 파일을 별도로 입력 받아서 처리하여 구현하였습니다.

 

4.3 동일한 API와 UI에서 서로 다른 서버 개발

 이번 프로젝트는 특이하게 백엔드 2명(A팀), 백엔드 2명(B팀), 프론트 엔드 1~2명(FE팀)으로 나뉘어서 개발했다는 점입니다. 그래서 5~6명이서 API 명세서는 서로 상의하에 정한 다음에 API에 맞추어 각 팀마다 개발하는 것이었습니다. 그래서 A팀과 B팀은 같은 FE팀의 UI를 사용하여 개발합니다.

 

 이 방식의 단점은 API에 문제가 있어서 변경하고자 하는 경우 다른 백엔드 팀의 상의를 구하여야 한다는 점입니다. 그래서 쉽게 API를 변경할 수 없다는 점과 만약 변경하고자 하는 경우 평소 개발때보다 시간이 걸린다는 점이 있습니다. 반면 장점은 소수의 인원으로 개발을 하기 때문에 내가 이전에는 못해본 서비스를 개발해볼 기회가 있다는 점이 있었습니다.

 

4.4 한 API에 채팅 메시지 목록 페이징과 롱 폴링 요청을 구현

 한 채팅방 안의 채팅 메시지들의 목록을 조회시 해당 응답에 대해서 페이징을 적용하는 것과 해당 메시지 목록 조회시 다음 롱 폴링 요청과 겹처서 어떻게 구현해야 할지 몰라서 힘들었습니다. 결국에는 채팅 메시지 목록의 페이징과 롱 폴링 요청을 한꺼번에 요청하는 것이 아니라 별도로 분리해야 한다고 생각하였습니다. 그런데 그것을 이미 인지한 시점에서는 프로젝트 기한이 얼마 안 남았기 때문에 채팅 메시지 목록 조회시 페이징을 넣지 않고 롱 폴링 요청의 응답에 맞추어 구현하게 되었습니다. 다음에는 별도로 분리하여 구현하기로 생각하였습니다.

 

References

github : https://github.com/masters2023-project-03-second-hand/second-hand-max-be-a

 

'회고' 카테고리의 다른 글

FineAnts 프로젝트 회고  (0) 2023.11.06
TodoList 프로젝트 회고  (0) 2023.07.22