인프런 커뮤니티 질문&답변
CouponService에서 이미 다운로드 한 쿠폰 안 내려주기
해결된 질문
작성
·
45
·
수정됨
1
안녕하세요! 수업 중에 재민 님이 말씀해주신 이미 다운로드 한 쿠폰은 내려주지 않는 것과 관련해서 질문이 있습니다.
제 나름대로 생각해 본 코드는 이렇습니다.
fun getCouponsForProducts(productIds: Collection<Long>): List<Coupon> {
    val productTargets = couponTargetRepository.findByTargetTypeAndTargetIdInAndStatus(
        CouponTargetType.PRODUCT,
        productIds,
        EntityStatus.ACTIVE,
    )
    val categoryTargets = couponTargetRepository.findByTargetTypeAndTargetIdInAndStatus(
        CouponTargetType.PRODUCT_CATEGORY,
        productCategoryRepository.findByProductIdInAndStatus(productIds, EntityStatus.ACTIVE).map { it.categoryId },
        EntityStatus.ACTIVE,
    )
    val applicableCouponIds = (productTargets + categoryTargets).map { it.id }.toSet()
    val downloadedCouponIds = ownedCouponRepository.findByUserIdAndState(userId, OwnedCouponState.USED) # userId 어디서 받아오지?
        .map { it.couponId }
        .toSet()
    val finalCouponIds = applicableCouponIds - downloadedCouponIds
    if (finalCouponIds.isEmpty()) {
        return emptyList()
    }
    return couponRepository.findByIdInAndStatus(finalCouponIds, EntityStatus.ACTIVE)
        .map {
            Coupon(
                id = it.id,
                name = it.name,
                type = it.type,
                discount = it.discount,
                expiredAt = it.expiredAt,
            )
        }
}여기서 고민됐던 부분은 findByUserIdAndState 에서 userId 를 어디서, 어떻게 받는 것이 좋을지 입니다.
getCouponsForProducts 함수가 호출되는 ProductController의 findProduct 메서드에서는 별도의 User 관련된 정보를 받아오지 않기 때문에 userId 를 받아올 수 없는 상황인 것 같습니다. 그런데 유저가 자신이 이미 다운로드 한 쿠폰을 중복해서 '다운로드 가능 쿠폰' 목록에서 보이지 않게 하는 소위 '개인 맞춤' 작업은 User가 꼭 필요한 정보라고 생각 되는데요.
이런 경우에 findProduct에 CouponController에서 처럼 User를 바로 넘겨주면 간단(?)하게 userId를 알 수는 있지만 이게 최선인 것 같진 않습니다. User를 파라미터로 넘겨주는 것을 인증 절차를 거친다고 생각해본다면 상품 상세 정보 보는 것은 꼭 인증을 하지 않더라도 볼 수 있어야 할테니까요. (그런데 User를 파라미터로 넘겨주는 것이 인증이 된 사용자만 이 API를 사용할 수 있다고 이해하는 것이 옳은 이해인지는 제가 잘 모르겠습니다🥹)
그래서 또 다른 접근법으로는 재민 님이 ProductController의 findProduct 메서드에서 쿠폰을 불러오는 부분 위에 주석으로 처리해놓으신 것처럼 별도의 API를 만들고 해당 API에서 User를 활용해서 진행하면 어떨까 하는 생각도 해봤습니다.
재민 님은 어떤 식으로 푸실지 궁금합니다!
감사합니다.
답변 1
1
코드를 수정해보시고 고민하고 계시다니 아주 멋집니다!! 👍👍👍👍👍
[포인트1]
우선 현재 Controller 에 User 를 추가하면 인증된 사용자만 받아오게 됩니다!
인증 기능 자체는 강의 초반에 저희 상황에서 설명드린 G/W에서 User Service를 호출하고 API Server로 넘어온다고 봐주시면 될 것 같습니다!
코드로는 UserArgumentResolver 참고하시면 될 것 같구요!
[포인트2]
고민을 꽤 하고 계신 것 같은데 이걸 이해해보시면 좋습니다!
ProductController의 findProduct 메서드에서 쿠폰을 불러오는 부분 위에 주석으로 처리해놓으신 것처럼 별도의 API를 만들고 해당 API에서 User를 활용해서 진행하면 어떨까
이것도 가능하겠죠! 사실 이것과 별개로 이 요구사항에는 숨은 문제가 있습니다! 
바로 비회원과 회원 모두 상품상세를 볼 수 있어야 한다는 것 입니다
이 숨은 요구사항이 지금 구현에서 왜 문제가 발생할까요?
왜냐면 UserArgumentResolver 의 구현을 보시면 인증 토큰이 없으면 예외를 던지고 있습니다
이 말은 비회원은 처리할 수 없도록 만들어 둔 것입니다 😱 고로 상품상세 API에 User 를 추가하고 비회원이 상품상세를 보면 저 예외가 발생한다는 것 입니다!
[포인트3]
그럼 어떻게 해야할까요? 상황에 따라 다르고 정답은 없지만 저는 보통 이럴때 UserOrGuestArgumentResolver 같은 리졸버를 추가하기도 합니다
그러면 회원/비회원 모두 한개의 API('상품상세 API' 나 '쿠폰 별도 API' 무엇으로 풀려고해도 이 문제는 동일하게 발생함)로 처리할 수 있을 것 같습니다!
이것에 대한것은 한번 직접 풀어보시면 좋을 것 같습니다 😃
해보시고 잘 모르시겠으면 다시 질문해주세요!
[별첨..?]
획기적인 아이디어로는 API 를 회원/비회원 나누고 클라이언트한테 로그인 상태따라 API를 호출하라 할 수도 있을텐데...... 이건 양쪽 모두 괴로운 일이 되겠죠ㅎㅎ
모쪼록 느낌이 잘 전달 되었으면 좋겠습니다! 수정 많이 해보시고 고민 많이 해보셔요! 완강까지 쭉쭉 화이팅입니다!
감사합니다! 😃
많이 고민해보시고 코드도 작성 해보셨군요!
적어주신 내용도 고민해보셔도 좋을 것 같습니다!
다른 관점 고민 포인트로 툭툭 던져드리면 요런 포인트가 있을듯하네요ㅎㅎ
- user.isGuest 가 비즈니스 구현 코드에 분기로 침투 된게 아쉽지 않나? 다른 전략은 무엇이 있을까 + 비슷한 관점으로 UserOrGuest 를 비즈니스 구현 코드에서 알게하는게 맞을까!? 
물론 별개로 지금 작성해주신 코드에서 구현영역을 도구로 나눠보시면 일차 정리는 깔끔해보입니다!
모쪼록 남은 강의도 화이팅입니다!






친절한 답변 감사드립니다! 조언해주신 것 바탕으로 코드를 수정해봤습니다 ㅎㅎ
UserOrGuestResolver 클래스 만들기 (UserResolver 참고)
UserOrGuest 개념 객체 만들기: UserOrGuestResolver에서 사용 + 앞으로 인증/미인증 사용자 모두에 대해서 필요할 것 같아서 만들어 봤습니다. 처음에는 어노테이션으로 만들었다가 개념 자체가 다른 요구사항에서도 자주 사용될 수도 있을 것 같아 클래스로 만드는 것으로 변경을 했네요.
UserService 코드 수정하기
최종적으로 CouponService.getCouponsForProducts 의 코드는 이렇게 생겼는데요. 코드를 짜고 보니깐 구현 부분이 계속 들어가 있어서 정확히 이 메서드가 어떤 일을 하는지 한 번에 파악이 쉽지 않은 것 같다는 느낌이 들기도 하더라구요. 이럴 때 재민 님이 말씀하신 Implement Layer를 도입해봐도 괜찮을 것 같다는 생각이 들었습니다😀