순번 | 이름 | 버전 |
---|---|---|
1 | Spring Boot | 3.2.3 |
2 | Java | 21 |
3 | Jasypt | 3.0.5 |
4 | postgresql | 42.7.2 |
5 | gradle | 8.5 |
6 | redis | redis:6-alpine |
컬럼명 | 코멘트 | 타입 | 길이 | |
---|---|---|---|---|
1 | id | 아이디 | bigint | |
2 | card_data | 카드 정보 | varchar | 300 |
3 | installment_month | 할부개월수 | varchar | 2 |
4 | payment_id | 관리번호 | varchar | 20 |
5 | payment_kind | 결제 종류(결제, 취소) | enum | |
6 | price | 결제(취소)금액 | double | |
7 | tax | (취소)부가가치세 | bigint | |
8 | remain_price | 남은 금액 | double | |
9 | remain_tax | 남은 부가가치세 | bigint | |
10 | paid_by | 결제자 | varchar | 255 |
11 | paid_at | 결제 시각 | datetime | |
12 | parent_id | 부모 식별자 | bigint |
컬럼명 | 코멘트 | 타입 | 길이 | |
---|---|---|---|---|
1 | id | 식별자 | bigint | |
2 | card_data | 카드 정보 | varchar | 255 |
3 | management_id | 관리 번호 | varchar | 20 |
4 | created_by | 등록자 | varchar | 255 |
5 | created_at | 등록일자 | datetime |
1-1. docker 를 실행시킨다.
1-2. 테스트 코드를 실행시킨다.
테스트 컨테이너로 postgres 와 redis 가 자동으로 뜨게 되어 있으므로 도커만 실행시킨 다음에 테스트 코드를 실행하면 정상 작동.
로그인 사용자 역시 WithMockJwtAuthentication 으로 주입하고 있으므로 자동 로그인 처리.
2-1. docker 를 실행시킨다.
2-2. (redis 컨테이너가 6379포트로 실행된 것이 없다면) docker run -d --name redis -p 6379:6379 redis 로 레디스를 실행시킨다.
2-3. SpringBoot 를 시작한다.
2-4. postman 을 실행시켜서 resources 하위의 kakaoinsurance.postman_collection.json 을 collections 에 import 한다.
2-5. Environments 에 새로운 환경을 등록하고 ACCESS_TOKEN 을 등록한다.
2-6. login 을 선행한 이후 다른 API 들을 진행한다.
남은 금액을 확인 하기 위해서는 취소 금액을 더해서 최초 결제 금액에서 빼거나 취소를 할때마다 남은 금액을 업데이트 해 주는 방법이 있다.
남은 금액 조회는 취소 혹은 결제를 할때마다 확인해야 하는데, 여러 건의 취소 된 금액을 매번 조회해서 합산하는 것 보다는 취소할때마다 한번의 쓰기 연산이 발생하는 것이 더 효율적으로 생각 됐다.
따라서 쓰기와 읽기의 트레이트 오프에서 약간의 쓰기 성능을 포기하고 읽기 성능을 선택했다.
최초 결제가 일어날 때 원본 결제 테이블에 동일한 금액의 남은 금액 필드에 금액을 저장하고, 취소 요청이 올때마다 취소 금액만큼 남은 금액에서 차감한다.
카드 결제와 전체 취소는 동시에 실행이 되지 않아야 하고, 전체 취소는 한번만 가능하기 때문에 현재 실행중인 건이 있다면 대기하지 않고 바로 종료 되도록 로직을 장성해야 한다.
그래서 Lock 을 거는 방식 중에 Redis 의 Lettuce 를 사용해서 사용중인 카드 번호나 관리번호가 있다면 로직이 종료되도록 작성했다.
카드 부분취소는 동시에 실행되지 않아야 하지만 대기 이후 처리가 가능해야 하기 때문에 Redis 의 Redisson 을 활용해서 pub-sub 기반의 lock 구현을 통해 동시에 요청이 들어오더라도 한번에 한 건씩만 동작하도록 로직을 작성했다. ( lettuce 는 spin lock 방식으로 redis 자체에 무리를 주기 때문 )
카드 데이터를 만들기 위해 리플렉션 API 를 사용했다. 카드 데이터는 변경이 될 가능성이 있기 때문에 하드코딩 보다는 동적으로 변경이 가능한 것이 여러모로 유지보수에도 편할 것으로 생각되었다. @PayType 이라는 어노테이션을 만들고 데이터 타입과 길이를 정의하고 값이 전부 세팅 된 이후에 필드가 선언된 순서대로 StringBuilder 에 추가하여 문자열을 만들어 줬다. 리플렉션은 JVM 에 따라 순서가 늘 보장되지 않기 때문에 @PayType 어노테이션에 순서 필드(order)를 주고 선언된 필드를 가져오는 시점에 정렬을 해서 가져오는 방식으로 해결했다. 또, 데이터 타입에 맞게 빈 자리 공백은 String.format API 를 통해서 해결했다.