백엔드 개발 포트폴리오 | 중고 거래 플랫폼

내일배움캠프 4기 웹 개발 과정 Spring 트랙 수료생 최종 프로젝트 ‘낙낙상회’을 소개합니다.
Aug 17, 2023
백엔드 개발 포트폴리오 | 중고 거래 플랫폼

1. 프로젝트 소개

프로젝트 소개

내가 필요한 물건을 판매하는 이웃에게 knock, knock!
내게 필요한 물품을 이웃에게, 이웃이 필요한 물품을 내가 직접 이웃과 채팅으로 대화하며
안전한 직거래 방식으로 거래 할 수 있는 웹 사이트 입니다.
notion image

2. 개발 환경

개발 환경

  • 개발 환경
    • Spring Version : 3.0.1
    • JDK Version : 17.0.5
    • Gradle : 7.6

  • 사용 스택
    • notion image

서비스 아키텍처

notion image

3. 서비스 기능

 
  • 회원 가입 (이메일 본인인증) & 로그인
  • 프로필 변경
  • 상품 등록
  • 상품 검색
  • 관심 목록 추가
  • 유저 신고
  • 동네 설정 (ip 위치 정보)
  • 채팅 기능
  • 판매 완료 & 구매내역 & 판매내역
 

4. ERD & UML

 
ERD
notion image
UML
notion image

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
  • 메시지 중심의 다양한 프로토콜에서 사용될 수 있는 텍스트 기반의 메시징 프로토콜
장점
  • 경량 프로토콜로 빠른 전송 속도와 낮은 대역폭 사용
  • 웹 소켓과 연동하여 웹 기술에서도 사용 가능
단점
  • 대용량 데이터 처리에 미적합
 
  1. 배경
    1. 프로젝트가 개인간의 중고거래를 주제로 하여 거래를 위한 채팅 기능 도입이 필요하였습니다
  1. 의사결정 우리 팀이 채팅 기능 구현에 스톰프를 선택한 이유는 다음과 같습니다.
      • 사용 경험 有
        • 우리 팀은 이전에 스톰프를 사용한 경험이 있어서, 이미 구축된 인프라를 재사용할 수 있었습니다.
      • 쉬운 설정과 관리
        • 스톰프는 비교적 쉬운 초기 설정과 관리가 가능합니다.
      • 소형 프로젝트
        • 스톰프는 경량 프로토콜이며 간단하고 빠르게 실시간 메시지 전달이 가능합니다.
        • Kafka와 같은 데이터 스트리밍 플랫폼을 사용하여 대용량의 메시지를 처리할 수도 있지만 사용 경험이 아직 없기도 하고, 소형 프로젝트이므로 stomp로만 적용 하였습니다.
      • 빠른 데이터 전달
        • 스톰프는 실시간 데이터 전달을 위해 최적화되어 있어, 채팅 메시지와 같은 빠른 데이터 전달에 적합합니다.
  1. 결론
    1. 이미 사용 경험이 있어서 적극적으로 활용할 수 있고, 소형 프로젝트이기에 대용량 메시지 처리가 필요하지 않다고 판단되었기 때문에 경량 프로토콜인 스톰프 만을 선택하여 사용하였습니다.
 

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) 채팅 기능 구현 중 이슈
  • 채팅방 조회 시 상품 판매하는 판매자에게는 뜨지 않고, 채팅 생성한 구매자에게만 생성한 채팅 리스트가 뜸
  • 채팅방 생성 시 본인이 본인 상품에 채팅을 생성할 수 있고, 이미 해당 상품에 대한 채팅방이 있는데 또 생성되는 이슈
해결 방안 :
  1. Prodcuct 에서 아래와 같은 메서드를 추가해 user가 해당 상품을 가지고 있다면 판매자로 구분하도록 함
publicSet<ChatRoom> getChatRooms() { return this.room; }
  1. 본인 상품에 대해서는 상품 판매자 아이디와 비교해 같다면 생성 금지 시킴
  1. 채팅방이 계속 생성되는 경우는 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 버전 사용 중이라 적용이 되지 않음
      • 3.x.x 버전에서 이전 암호화 알고리즘을 활용할 때에는
        jasypt.encryptor.iv-generator-classname=org.jasypt.iv.NoIvGenerator 속성을 추가해야 가능
    • 그리고 암호화한 password 또한 따로 저장을 해야 암호화된 정보를 지킬 수 있었는데, 여러 블로그들이 txt 파일로 저장을 했음
      • 그러던 중 vm 옵션에 -Djasypt.encryptor.password= 이런 식으로 지정해주면 되는 것을 확인한 후 적용
해결 방안
1) Jasypt 암호화 사용 - JasyptConfig를 추가해 암호화 적용 후 vm option안에 Djasypt.encryptor.password=******를 추가해 설정 완료하여 개인정보 노출 방지
2) 서브 모듈을 구현하여 개인정보 노출 방지 구현 예정
 
문제 상황 5) gps 기능
  • 위치를 불러오면서 보여주고, 저장하고, 이미 저장되어 있다면 수정까지 가능해야해서 PostMapping, GetMapping 등 어떤 것을 써야 할지 문제
  • 많은 블로그 참조하여 여기저기서 참고한 코드를 프로젝트에 맞춰야 해서 필요 여부 결정 후 JavaScript 쪽에서도 걸러내는 문제
 
해결 방안
  1. RequestMapping 사용
    1. 하나의 역할을 하는 것이 아닌 한번에 여러 역할을 하기 때문에 `RequestMapping`을 사용하여 필요한 Method를 넣음
@RequestMapping(value = "/address", method = {RequestMethod.GET, RequestMethod.PUT})
  1. 불필요한 코드나 클래스는 지우고, 필요한 클래스는 생성
    1. JavaScript 또한 프로젝트에 맞게 변경
 
문제 상황 6) 서로 연관관계를 가진 객체간의 순환참조 오류
원인:
notion image
잘못된 쿼리 작성이 원인이었음.
review와 userGrade는 서로 OneToOne연관관계인데, review에서 userGrade를 호출하면 userGrade에서 또 review를 호출하고.. 하는식의 순환참조가 일어나는것으로 추측.
해결 방안: userGrade를 호출하지 않으면 해결됨. 실제로 필요한 값이 userGrade객체가 아닌 userGrade의 grade였기에 grade만 호출해서 해결.
Share article
Subscribe to our newsletter
RSSPowered by inblog