웹 개발 포트폴리오 | 중고 거래 플랫폼
내일배움캠프 4기 웹 개발 과정 Spring 트랙 수료생 최종 프로젝트 ‘낙낙상회’을 소개합니다.
1. 프로젝트 소개
프로젝트 소개
내가 필요한 물건을 판매하는 이웃에게 knock, knock!
내게 필요한 물품을 이웃에게, 이웃이 필요한 물품을 내가 직접 이웃과 채팅으로 대화하며
안전한 직거래 방식으로 거래 할 수 있는 웹 사이트 입니다.
2. 개발 환경
개발 환경
- 개발 환경
- Spring Version : 3.0.1
- JDK Version : 17.0.5
- Gradle : 7.6
- 사용 스택
서비스 아키텍처
3. 서비스 기능
- 회원 가입 (이메일 본인인증) & 로그인
- 프로필 변경
- 상품 등록
- 상품 검색
- 관심 목록 추가
- 유저 신고
- 동네 설정 (ip 위치 정보)
- 채팅 기능
- 판매 완료 & 구매내역 & 판매내역
4. ERD & UML
ERD
UML
5. 분업 현황
ㅤ | 김민수 | 이영빈 | 박서우 | 유성재 | 정의준 |
ㅤ | 리더 | 부리더 | 팀원 | 팀원 | 팀원 |
BackEnd | CI / CD
git action / aws
Test Code - Controller / Rest Docs
User / Trade / Report - user 기능 구현
Trade / UserReport - QueryDsl | 리뷰/유저평점/관심/상품신고 기능 구현
전역 예외처리
상품 불러오기 페이징
시큐리티 설정
CORS설정
상품 검색
QueryDsl 적용(채팅/관심/상품/상품신고/리뷰/유저) | [User] 회원가입 & 로그인
[Email] 이메일 인증
[chat] 채팅
[address] 위치인증
[JasyptConfig] properties 암호화
| 상품 CRUD 구현
상품 서비스 단위 테스트 코드 작성
AWS - S3 이미지 업로드 기능 구현 | Board / Comment / Profile 기능 구현
Test Code - Service(Board, Comment, Interest, UserGrade, Review, Report, User, Product)
/ Entity(Board, Comment, Interest, Review, User, Product) |
FrontEnd | profile - update / report - user | 메인 /회원가입 /로그인/상품검색/상품 조회/내 정보 조회/ 관심내역 페이지 구현 | 구매내역 / 판매완료 / 채팅 / 동네설정(위치인증) / 상품삭제 / 상품 수정 | 상품 생성 페이지 작성 및 API 연결
S3 API 연결
판매내역 페이지 작성 및 API
관심 기능 API 연결 | 서비스 로고 제작 |
6. 프로젝트 진행 현황
7. 기술적 의사결정
JPA / QueryDsl
기존에 사용 경험이 있는 JPA만 사용할 것인지, 혹은 성능 향상을 위해 QueryDsl을 적용할 것인지에 대해 논의
JPA 사용
- 장점
- 본인이 따로 SQL 쿼리를 작성하지 않고 서비스 구현에만 시간을 사용할 수 있다.
- 초반에는 H2를 사용하지만 후에는 Mysql을 사용할 계획인데 JPA는 자동으로 해당 데이터베이스에 맞춰서 쿼리를 작성하기에 시간 소모 ↓
- 단점
- 복잡한 쿼리 처리의 어려움
- 쿼리 실행 횟수 증가로 성능 문제 발생
- 불필요한 필드를 가져올 수 있음
- 여러 개의 엔티티를 동시에 처리해야 할 경우에 트랜잭션 처리 복잡
QueryDsl 사용
- 장점
- JPA를 사용하면서도 사용 가능
- JPQL과 다르게 쿼리에 오류가 발생하면 컴파일 단계에서 캐치 가능
- 동적 쿼리 작성의 지원으로 유연하게 대처 가능
- 단점
- 아직 모두가 경험이 없는 부분의 기술
- 쿼리 작성을 요하기에 전체가 아닌 일부인원이 해당 부분을 맡아서 작성
- 별도의 환경 설정을 위한 라이브러리 추가
기본적으로 JPA를 사용해서 사용하도록 운영하더라도, 동적 쿼리가 필요하거나 복잡한 쿼리가 필요한 부분은 QueryDsl을 사용하는 부분으로 프로젝트 진행.
Swagger / RestDocs
API 자동 문서화에 대해 논의
swagger
- 장점
- Rest Docs의 경우 문서 작성을 위한 Test Code 강제
- 컨트롤러에 바로 작성 가능 - 테스트 관리 필요 x
- 간단한 사용 및 쉬운 적용
- 단점
- 문서의 API가 변할 경우 수동으로 업데이트
- 컨트롤러에 작성이 되기에 실제 프로덕트 코드가 늘어남
Rest Docs
- 장점
- 테스트 코드 작성을 통해 문서 작성이 되어서 자동으로 문서 업데이트
- 테스트 결과도 함께 반환하므로 많은 정보제공
- spring 프레임 워크에서는 보다 쉽게 적용 가능
- 단점
- 테스트 코드의 강제
- 현재 필수사항으로 테스트 코드는 들어가있음
- 처음 작성을 하는 경우 swagger보단 불친절한 인터페이스
- 작성을 해본 경험이 있기에 이 부분은 문제 x
프로젝트 필수 요구 사항에 Test Code가 강제 되기에 Test Code 작성 후 문서화를 해주는 Rest Docs를 적용하는 방향으로 결정
Kafka / Stomp
Kafka
- 대용량의 실시간 데이터를 처리하기 위한 분산 메시징 시스템
- 대용량의 메시지를 저장하고 처리할 수 있는 플랫폼
장점
- 대용량의 데이터 처리
- 필요한 만큼 서버를 추가하여 처리 능력 확장 가능
단점
- 복잡한 설정과 어려운 운영
- 메시지 전송 시간 늦을 수 있음
STOMP
- 메시지 중심의 다양한 프로토콜에서 사용될 수 있는 텍스트 기반의 메시징 프로토콜
장점
- 경량 프로토콜로 빠른 전송 속도와 낮은 대역폭 사용
- 웹 소켓과 연동하여 웹 기술에서도 사용 가능
단점
- 대용량 데이터 처리에 미적합
- 배경
프로젝트가 개인간의 중고거래를 주제로 하여 거래를 위한 채팅 기능 도입이 필요하였습니다
- 의사결정 우리 팀이 채팅 기능 구현에 스톰프를 선택한 이유는 다음과 같습니다.
- 사용 경험 有
- 우리 팀은 이전에 스톰프를 사용한 경험이 있어서, 이미 구축된 인프라를 재사용할 수 있었습니다.
- 쉬운 설정과 관리
- 스톰프는 비교적 쉬운 초기 설정과 관리가 가능합니다.
- 소형 프로젝트
- 스톰프는 경량 프로토콜이며 간단하고 빠르게 실시간 메시지 전달이 가능합니다.
- Kafka와 같은 데이터 스트리밍 플랫폼을 사용하여 대용량의 메시지를 처리할 수도 있지만 사용 경험이 아직 없기도 하고, 소형 프로젝트이므로 stomp로만 적용 하였습니다.
- 빠른 데이터 전달
- 스톰프는 실시간 데이터 전달을 위해 최적화되어 있어, 채팅 메시지와 같은 빠른 데이터 전달에 적합합니다.
- 결론
- 이미 사용 경험이 있어서 적극적으로 활용할 수 있고, 소형 프로젝트이기에 대용량 메시지 처리가 필요하지 않다고 판단되었기 때문에 경량 프로토콜인 스톰프 만을 선택하여 사용하였습니다.
8. 트러블 슈팅
문제 상황 1) CI / CD 연결 중 발생한 이슈
CI
- 빌드는 성공적으로 검사하지만 테스트 코드를 통과하지 못해 발생한 CI 실패 → Error: Cannot locate a Gradle wrapper properties file at '/home/runner/work/gitactionTest/gitactionTest/gradle/wrapper/gradle-wrapper.properties'. Specify 'gradle-version' or 'gradle-executable' for projects without Gradle wrapper configured.
- gradle 버전 입력 후 추가 에러 발생
해결 방안
- 문제가 된 해당 테스트 코드를 재 작성 및 푸시
- 프로젝트의 경로 및 gradle 버전 설정 문제였다. 버전 설정을 위한 yml 코드 추가
- uses: gradle/gradle-build-action@v2 with: gradle-version: 7.6
- 해당 프로젝트의 ci를 적용하는 기준의 프로젝트 경로의 오류. yml 기준으로 작성이 된 경로로 프로젝트 위치 재배치
CD
- 로컬 연결이 된 H2, Redis 연결이 된 상태로 진행 → AWS 내에는 실제 작업하던 local과의 환경 차이로 인해 당연히 해당 H2 / Redis 지원 x
- AWS RDBS , Redis 연결 및 환경 변수 지정
- script 변수 경로 설정
- 제공된 자료를 통해 해당 과정을 진행했는데, 중간에 오타 발생
- git action deploy는 통과하는데 실제 CD는 안되는 상황
- AWS 배포에 들어가서 어디서 오류가 생겼는지 확인
# 프로세스가 켜져 있으면 종료if [ -z $CURRENT_PID ]; then echo "$TIME_NOW > 현재 실행중인 애플리케이션이 없습니다" >> $DEPLOY_LOG else echo "$TIME_NOW > 실행중인 $CURRENT_PID 애플리케이션 종료 " >> $DEPLOY_LOG kill -15 $CURRENT_PID fi
문제상황 2) 프론트 사용자 페이지 구현 간 문제
- CORS : 요청 시 CORS 오류 발생 ~
Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at https://some-url-here. (Reason: additional information here).
- JWT 토큰을 요청 데이터에 담아 보내는 방법에 대해 학습한 적이 없어 구현을 위해 노력
해결 방안 :
1) CORS 정책에 맞는 서버 설정 및 요청 데이터 작성을 통해 CORS 통과 성공
2) 학습을 통해 JWT 토큰을 headers에 담아 요청 성공
문제 상황 3) 채팅 기능 구현 중 이슈
- 채팅방 조회 시 상품 판매하는 판매자에게는 뜨지 않고, 채팅 생성한 구매자에게만 생성한 채팅 리스트가 뜸
- 채팅방 생성 시 본인이 본인 상품에 채팅을 생성할 수 있고, 이미 해당 상품에 대한 채팅방이 있는데 또 생성되는 이슈
해결 방안 :
- Prodcuct 에서 아래와 같은 메서드를 추가해 user가 해당 상품을 가지고 있다면 판매자로 구분하도록 함
publicSet<ChatRoom> getChatRooms() { return this.room; }
- 본인 상품에 대해서는 상품 판매자 아이디와 비교해 같다면 생성 금지 시킴
- 채팅방이 계속 생성되는 경우는
exists
로 해당 상품에 대한 채팅방 존재 여부 확인
- 채팅 CORS
- 컨트롤러에 어노테이션 추가
- 웹소켓컨트롤러에서 서버와 클라이언트
// chatConroller에 추가 @CrossOrigin(value = "http://localhost:8080", methods = RequestMethod.GET) // ("*") 와일드카드 에서 아래 코드로 수정 ... .setAllowedOrigins("http://localhost:8080", "http://127.0.0.1:5500") ...
- 실시간 채팅(websocket STOMP)
- 채팅 실시간 반영이 되지 않는 문제
- 정상적으로 소켓 연결은 되었으나 채팅이 실시간 반영이 되지 않아 보내는 url과 받는 url을 roomId라는 접점으로 통일 시켜 구현 완료.
- 기존에는 해당 상품에 연결되어 있는 상대방에게 보내야지 해서 상품 번호와 받는 사람을 모두 url에 넣었지만 생각해보니 1:1 채팅으로 해당 방에는 판매자와 구매하고자 하는 사람밖에 없으므로 roomId로 통일
- 주로 1:1에는 `@SendToUser`를 사용한다고 알고 있어서 초반 변경 때 그렇게 바꾸었으나 똑같이 실시간 반영이 되지 않아 `@SendTo`로 바꾸어서 진행
// 기존 코드 @MessageMapping("/send") ... messagingTemplate.convertAndSend("/sub/" + message.getReceiver() + "/product" + message.getProductId(), message) // 수정 코드 @MessageMapping("/{roomId}") @SendTo("/pub/{roomId}")
문제 상황 4) Properties 설정 공유 간 이메일 계정, 비밀번호, AWS secret 키 등 개인정보 공유 이슈
- Jasypt 사용 중 적용이 되지 않았고, 암호화 한 password는 어디에 저장해야 하는지에 대한 문제
- jasypt 3.0.4 버전을 사용하는데 3.0.0 버전부터는 암호화 알고리즘이 바뀌어 참고했던 블로그들은 2.x.x 버전 사용 중이라 적용이 되지 않음
- 그리고 암호화한 password 또한 따로 저장을 해야 암호화된 정보를 지킬 수 있었는데, 여러 블로그들이 txt 파일로 저장을 했음
3.x.x 버전에서 이전 암호화 알고리즘을 활용할 때에는
jasypt.encryptor.iv-generator-classname
=org.jasypt.iv.NoIvGenerator
속성을 추가해야 가능그러던 중 vm 옵션에
-Djasypt.encryptor.password=
이런 식으로 지정해주면 되는 것을 확인한 후 적용해결 방안
1) Jasypt 암호화 사용 - JasyptConfig를 추가해 암호화 적용 후 vm option안에 Djasypt.encryptor.password=******를 추가해 설정 완료하여 개인정보 노출 방지
2) 서브 모듈을 구현하여 개인정보 노출 방지 구현 예정
문제 상황 5) gps 기능
- 위치를 불러오면서 보여주고, 저장하고, 이미 저장되어 있다면 수정까지 가능해야해서 PostMapping, GetMapping 등 어떤 것을 써야 할지 문제
- 많은 블로그 참조하여 여기저기서 참고한 코드를 프로젝트에 맞춰야 해서 필요 여부 결정 후 JavaScript 쪽에서도 걸러내는 문제
해결 방안
- RequestMapping 사용
- 하나의 역할을 하는 것이 아닌 한번에 여러 역할을 하기 때문에 `RequestMapping`을 사용하여 필요한 Method를 넣음
@RequestMapping(value = "/address", method = {RequestMethod.GET, RequestMethod.PUT})
- 불필요한 코드나 클래스는 지우고, 필요한 클래스는 생성
- JavaScript 또한 프로젝트에 맞게 변경
문제 상황 6) 서로 연관관계를 가진 객체간의 순환참조 오류
원인:
잘못된 쿼리 작성이 원인이었음.
review와 userGrade는 서로 OneToOne연관관계인데, review에서 userGrade를 호출하면 userGrade에서 또 review를 호출하고.. 하는식의 순환참조가 일어나는것으로 추측.
해결 방안: userGrade를 호출하지 않으면 해결됨. 실제로 필요한 값이 userGrade객체가 아닌 userGrade의 grade였기에 grade만 호출해서 해결.
Share article