- 제작 기간: 2022.11.09 ~ 2022.12.05
- 참여 인원: 7명(FE: 4명, BE: 3명)
- JavaScript
- React Create React App
- Styled components
- React query
- axios
- 'Node.js
- npm
- Redux toolkit
Java 11
Spring Sercurity
Spring boot 2.7.5
Spring Data JPA
Spring Rest Docs
Gradle
MySQL 8
JWT 0.11.5
OAuth 2.0
Quartz 2.3.2
- 서로가 맡은 기능을 완료한 뒤, dev 브랜치에 merge를 하고 최종적으로 main 브랜치에 merge하는 전략을 사용.
- 잦은 시행착오를 겪으며 git에 대해 학습.
- api와 domain, quartz-scheduler로가 각각 독립적인 프로젝트로 판단.
- 단일 프로젝트 안에서 api, core, quartz의 모듈 분리.
- 중복될 수 있는 코드를 방지
- User 도메인 API 개발.
- Rest API 디자인 가이드 중 Resources, Http Methods, Status Code 고려.
2) Sping Security를 활용한 인증/인가 구현(JWT, OAuth 2.0) 📌core 모듈
- 회원가입 후 로그인 시 Access Token을 발급.
- refresh token을 활용한 token 관리.
⛔️ 인증에 실패할 경우, 예외 처리.
- OAuth 로그인 시 추가 정보(주소, 전화 번호) 기입 창으로 이동하고, 추가 정보 기입이 완료되면 Access Token을 발급.
- 리소스 서버에서 받은 리소스는 애플리케이션 서버의 데이터베이스에서 저장.
⛔️ 리소스 서버에서 데이터베이스로의 저장이 실패할 경우, 예외를 던짐.
- 추가 정보 기입을 하면 정보를 애플리케이션 데이터베이스에 저장 후 Access Token이 발급.
- 추가 정보를 기입하지 않을 경우 토큰이 발급.
- 추가 정보 기입 후 OAuth 로그인은 바로 토큰이 발급.
⛔️ 추가 정보 기입에 실패할 경우 예외를 던지고 Access Token은 발급되지 않음.
파사드 패턴
을 활용하여 파사드 클래스에서 단건 결제 요청과 정기 결제 요청, 결제 승인을 서비스 계층에 위임.- 파사드 객체에서 단건 결제인지, 정기 결제인지를 구분하는 역할.
- 결제 요청과 결제 승인에
전략 패턴
을 활용하여 변경이 생겼을 경우 클라이언트 코드의 변경을 최소화.
- RestTemplate을 이용해 외부 API와 통신했습니다.
- 동기 방식을 사용하므로 요청이 많아질 시 응답 지연을 고려.
- Connection Pool을 설정하고, 연결 시간 타임아웃과 응답 시간 타임아웃 설정해 유저들에게 결과를 빠르게 피드백하도록 구현.
#결제 요청 및 결제 승인 시퀀스 다이어그램
⛔️ 결제 요청 및 승인이 실패할 경우, 카카오페이 서버에서 제가 결제 요청 입력한 지정한 URL로 리다이렉트.
⛔️ 리다이렉트 후 에러 정보를 클라이언트에게 보냄.
- 정기 구독 시 Quartz 라이브러리를 이용하여 특정 날짜에 결제가 이루어지도록 결제 API와 연동.
- 멀티 모듈을 활용하여 스케쥴링 시스템을 독립적인 모듈로 구성.
- Jobkey API와 TriggerKey API를 활용하여 특정 job과 trigger를 조회, 취소, 변경기능 구현.
- 스케쥴러에서 설정한 스케쥴에 실행되지 않을 시 중복 실행을 방지.
⛔️ 만약, job 수행 시 예외가 발생할 경우,
- 첫 번째 에러 : 경우 바로 job 재시도.
- 두 번째 에러 : 3일 동안 24시간 간격으로 job을 재시도.
- 그 후에도 예외가 발생한다면 job을 취소하고 로그로 기록.
5) Exception 핸들링과 공통 Exception Response 구현 📌core 모듈
- 정적 팩토리 메서드를 통해 에러 응답 객체 생성 후 예외를 처리.
- 각 예외마다 객체 생성에 필요한 파라미터가 다르기 때문에, 정적 팩토리 메서드를 통한 공통 에러 응답 객체 기반으로 예외 처리.
{
"status": 400,
"customFieldErrors": [
{
"field": "email",
"rejectedValue": "1234567!",
"reason": "비밀번호는 숫자+영문자+특수문자 조합으로 8자리 이상이어야 합니다."
}
]
}
6) 단위 테스트(RestAssured) 및 통합 테스트 작성(Junit5) 📌api 모듈
- 프로젝트 개발 후 테스트 코드의 필요성을 인지하여 통합 테스트를 약 70개(Rest Docs를 위한 테스트 포함) 작성(일부 단위테스트 포함).
- Junit5를 사용해 단위 테스트를 진행.
- mokito를 사용해 외부 API에 독립적인 테스트 환경을 마련.
- @SpringBootTest와 사용하기에 RestAssured가 가독성이 좋다고 생각해 인수 테스트에 RestAssured를 사용.
7) Spring Rest Docs를 활용한 API 문서 작성 📌api 모듈
- 테스트 코드 작성 후 Spring Rest Docs를 이용한 API 문서를 작성.
1. @Schduled를 문제를 해결한 Quartz
- Spring의 @Scheduled을 이용하여 스케쥴링을 시도했지만, 몇 가지 문제가 있었습니다.
- 유저가 구독 주기 변경 시, 첫 정기 결제일을 기준으로 주기를 변경해야 했습니다.
- @Scheduled를 사용하여 런타임 환경에서 구독 주기를 변경하려면, 기존 스케쥴을 null로 변경 후 변경 시점을 기준으로 새로운 스케쥴을 다시 할당해야 했습니다.
- 이렇게 되면, 첫 정기 결제일을 기준으로 구독 주기 변경이 불가능했습니다.
- 만약 유저가 본인의 정기 구독 주기를 변경하거나 구독을 취소한다면, 애플리케이션에서 그 유저에 할당된 스케쥴러를 조회 후 처리해야합니다.
- @Scheduled 사용 시 특정 스케쥴러를 조회하는 방법이 없었습니다.
- Spring Batch를 학습하기엔 주어진 시간에 비해 학습 비용이 크다고 생각하여 Quartz를 선택했습니다.
Quartz
의 Trigger API 사용함으로써 런타임 환경에서 첫 정기 구독일을 기준으로 구독 주기를 변경시킬 수 있었습니다.Quartz
JobKey API를 사용함으로써 특정 스케쥴러 조회가 가능했습니다.
2. Jpa에서 동일한 엔티티 참조 에러
- Quartz를 사용하여 정기 결제 Job을 구현할 때, 첫 번째 정기 결제 때 사용된 order 객체의 정보들을 그대로 복사해서 다음 정기 결제 때 사용해야 했습니다.
- 처음에 첫 결제 때 사용한 order 엔티티를 가지고 와서 그대로 사용하려 했지만 에러가 발생했습니다.
(org.hibernate.HibernateException: Found shared references to a collection)
swallow copy
를 함으로써 원본 엔티티와 복사한 엔티티가 Heap에서 동일한 주솟값을 참조했습니다.- 하지만, 하이버네이트에서 이미 영속화된 엔티티와 동일한 주솟값을 가지는 엔티티를 또 다시 영속화할 수 없었습니다.
- order 엔티티에 deep copy를 위한 생성자를 추가하여
deep copy
했습니다.
- Java에서 copy에 관한 개념에 대해 학습했습니다.
- JPA에서 동일한 엔티티는 영속화 할 수 없다는 것을 알게 됐습니다.