- 적립, 적립취소, 사용, 사용취소 기능을 제공하는 추적 용이한 포인트 API
- 대량 데이터 고려 : 사용 가능 포인트 조회 시 Pagenation 처리
- 요구사항에 유연하게 대처하기 위한 추적 필드 구분
- 동시성 제어를 위한 낙관적 락(Optimistic Lock)
- 사용자 최대 보유 포인트 금액 DB 설정
- 1회 최대 적립 한도 yml 프로퍼티 관리
- Java 21
- SpringBoot 3.3.4
- Spring Data JPA
- JUnit5
- Swagger
- H2 DB
./gradlew bootRunURL : http://localhost:8080/swagger-ui/index.html
- 포인트 도메인 특성상 일종의 기업 부채 혹은 재화와 비슷한 의미로 사용되기 때문에, 이력을 정확하고 상세하게 관리
- 사용자의 포인트가 왜 이 금액이 되었는지 정확히 추적하기 위함
- Pagenation 통해 대량의 데이터를 한번에 로딩하지 않고 나눠서 조회함으로써 성능 최적화
- 사용 가능 포인트 추출 시 소스가 아닌 DB 레벨에서 추출
-
java 처리 시 대용량의 데이터를 메모리로 가져와서 계산해야하기 때문에 OutOfMemory 발생 방지를 위해 NativeQuery 사용
// 만료되지 않고, 취소, 사용되지 않은 포인트 + 부분 사용 취소된 포인트 합계 정렬하여 조회 @Query(value = "SELECT pt.id, " + "pt.expired_date, " + "(pt.amount - " + "COALESCE((SELECT SUM(u.amount) FROM point_transaction u WHERE u.type = :useType AND u.used_point_id = pt.id), 0) + " + "COALESCE((SELECT SUM(uc.amount) FROM point_transaction uc WHERE uc.type = :useCancelType AND uc.cancel_point_id = pt.id), 0)" + ") AS availableAmount " + "FROM point_transaction pt " + "WHERE pt.member_id = :memberId " + "AND pt.type = :saveType " + "AND pt.expired_date > :currentDate " + "AND NOT EXISTS (SELECT 1 FROM point_transaction pc WHERE pc.type = :saveCancelType AND pc.original_point_id = pt.id) " + "HAVING (pt.amount - COALESCE((SELECT SUM(u.amount) FROM point_transaction u WHERE u.type = :useType AND u.used_point_id = pt.id), 0) + " + "COALESCE((SELECT SUM(uc.amount) FROM point_transaction uc WHERE uc.type = :useCancelType AND uc.used_point_id = pt.id), 0)) > 0 " + "ORDER BY pt.expired_date ASC", countQuery = "SELECT COUNT(*) FROM point_transaction", nativeQuery = true) Page<Object[]> findAvailablePoints(@Param("memberId") Long memberId, @Param("currentDate") LocalDateTime currentDate, @Param("saveType") String saveType, @Param("useType") String useType, @Param("useCancelType") String useCancelType, @Param("saveCancelType") String saveCancelType, Pageable pageable);
-
- 각 필드는 특정 동작에 대한 추적만을 담당하게 하여, 의미적 충돌을 방지하고 코드를 보다 명확하게 유지
- 확장성 고려 : 추후 비즈니스 로직이 추가될 경우 필드가 명확하게 분리되어, 새로운 요구사항에 유연하게 대처 가능함으로써 유지보수성 향상
- 옵저버 패턴 사용 : 다른 도메인과 독립적으로 동작하도록 이벤트를 처리하여 결합도를 낮추고 응집도를 향상시킴으로써 유지 보수성 향상
- @Version 을 사용하여 ObjectOptimisticLockingFailureException 발생 : Retry 정책 수립하여 처리 가능