-
Notifications
You must be signed in to change notification settings - Fork 0
6‐5. 전화번호를 입력해 쿠폰을 나눠준다.
- 쿠폰 조회 API 링크 : https://github.com/this-is-spear/est-delivery/pull/20
이커머스 세계에서 우린 친구들의 아이디를 알고 있는가?
라는 고민에서 이커머스에서 우리는 독립된 구매자로 인식받고 있어서 지인이 어떤 행동을 하는지 식별할 수가 없다. 그럼 독립된 구매자 간에 쿠폰을 어떻게 나눠줄 수 있을지 고민했고, 회원 정보를 몰라도 전화 번호를 이용해 쿠폰을 나눠 줄 수 있겠다 생각했다.
요즘 sns 피싱 문제가 대두되고 있는데 문제를 해결하려면 서비스에서 sns 를 전송하는 것보다 회원이 직접 전송하도록 구현했다. 메시지 템플릿을 반환받고 sender가 메시지 템플릿을 받자마자 바로 공유 할 수 있는 창이 떠지면 좋겠다는 생각도 들었다. 나중에 화면을 만들게 되면 추가해보도록 하겠다.
요구사항을 정리하면 다음과 같다.
- 회원은 쿠폰을 전달하기 위한 메시지 템플릿을 얻는다.
- 전달하는 쿠폰은 유효해야 한다.
- 전달하게 되면 더이상 회원이 가진 쿠폰이 아니다.
- 전달한 쿠폰의 등록 기간은 6개월이다. 6개월 이내 쿠폰을 등록하지 않으면 소멸한다.
- 전달 가능한 쿠폰은 사용자가 가지고 있어야 하고 게시한 쿠폰이 아니어야 한다.
게시한 쿠폰은 가게에서 직접 가져 갈 수 있으니 전달 가능한 쿠폰에서 제외했다. 이벤트 쿠폰은 이벤트에 한 번만 참여 가능하지만 발급된 이벤트 쿠폰 사용에는 제한이 없으니 큰 문제는 생기지 않는다.
모델링은 다음과 같다.
---
title: Gift Coupon Message
---
classDiagram
GiftMessage *-- GiftCoupon
GiftMessage *-- GiftCode
class GiftMessage{
+String message
+Long senderId
+LocalDate sendDate
+GiftCoupon gift
+GiftCode code
}
class GiftCoupon{
+Long coupontId
+Boolean isUsed
+LocalDate validDate
}
class GiftCode {
+String enrollCode
}
사용자 플로우를 정리하면 다음과 같다.
sequenceDiagram
actor shop owner
box send to coupon by phone number
actor sender
participant coupon service
actor receiver
end
shop owner ->> sender : event coupon or hand out coupon
rect rgb(191, 123, 255)
note right of sender: send coupon
sender ->> coupon service : find giftable coupons
coupon service ->> sender :
sender ->> coupon service: send coupon
coupon service ->> coupon service: find coupon
coupon service ->> coupon service: make message template
coupon service ->> sender: return message template
sender ->> receiver : send message
end
rect rgb(201, 23, 255)
note left of receiver: enroll coupon
opt 가입하지 않은 경우
receiver ->> coupon service: join service
coupon service ->> receiver:
end
receiver ->> coupon service: enroll coupon
coupon service ->> coupon service: validate coupon
coupon service ->> receiver:
end
receiver ->> shop owner : use coupon
사용자 플로우를 봤을 때 생성이 필요한 API는 두 개로 선물가능한 쿠폰 조회 API
, 쿠폰 전달 API
와 쿠폰 등록 API
가 필요하다.
선물가능한 쿠폰 조회 API
는 다음과 같다.
sequenceDiagram
actor Sender
participant GiftCouponMessageController
participant FindAvailableGiftCouponsUseCase
Sender ->> GiftCouponMessageController: findGiftableCouopns(coupon id)
GiftCouponMessageController ->> FindAvailableGiftCouponsUseCase: findGiftableCoupon(member id, coupon id)
FindAvailableGiftCouponsUseCase ->> MemberAdapter: findMember
MemberAdapter ->> FindAvailableGiftCouponsUseCase:
FindAvailableGiftCouponsUseCase ->> CouponAdapter: findCoupon
CouponAdapter ->> FindAvailableGiftCouponsUseCase:
FindAvailableGiftCouponsUseCase ->> ValidateGiftableAdapter: isGiftable
ValidateGiftableAdapter ->> FindAvailableGiftCouponsUseCase:
FindAvailableGiftCouponsUseCase ->> GiftCouponMessageController: return GiftableCouopns
GiftCouponMessageController ->> Sender: return List<Coupon>
쿠폰 전달 API
는 다음과 같다.
sequenceDiagram
actor Sender
participant GiftCouponMessageController
participant GiftCouponByMessageUseCase
Sender ->> GiftCouponMessageController: sendGiftableCouopns(coupon id)
GiftCouponMessageController ->> GiftCouponByMessageUseCase: sendGiftableCouopns(member id, coupon id)
GiftCouponByMessageUseCase ->> MemberAdapter: findMember
MemberAdapter ->> GiftCouponByMessageUseCase:
GiftCouponByMessageUseCase ->> CouponAdapter: findCoupon
CouponAdapter ->> GiftCouponByMessageUseCase:
GiftCouponByMessageUseCase ->> ValidateGiftableAdapter: isGiftable
ValidateGiftableAdapter ->> GiftCouponByMessageUseCase:
GiftCouponByMessageUseCase ->> WrapGiftAdapter: wrap gift
WrapGiftAdapter ->> GiftCouponByMessageUseCase:
GiftCouponByMessageUseCase ->> GiftCouponMessageController: return gift
GiftCouponMessageController ->> Sender: return gift message
쿠폰 등록 API
는 다음과 같다.
sequenceDiagram
actor Sender
participant GiftCouponMessageController
participant EnrollCouponByMessageUseCase
Sender ->> GiftCouponMessageController: enrollGiftCoupon(coupon code)
GiftCouponMessageController ->> EnrollCouponByMessageUseCase: enroll(member id, coupon code)
EnrollCouponByMessageUseCase ->> MemberAdapter: findMember
MemberAdapter ->> EnrollCouponByMessageUseCase:
EnrollCouponByMessageUseCase ->> LoadGiftCouponStatePort: findGiftCoupon
LoadGiftCouponStatePort ->> EnrollCouponByMessageUseCase:
EnrollCouponByMessageUseCase ->> EnrollCouponByMessageUseCase: member receive coupon
EnrollCouponByMessageUseCase ->> UpdateMemberStatePort: update
UpdateMemberStatePort ->> EnrollCouponByMessageUseCase:
EnrollCouponByMessageUseCase ->> GiftCouponMessageController:
GiftCouponMessageController ->> Sender:
책임 분리로 인해 레이어 사이 너무 많은 계층이 생기는 건 관리가 어려워진다는 생각을 했다.
현실적으로 SRP
를 지키는건 무조건적으로 좋을까 고민했을 때 계층 사이 의존되는 클래스가 많아지면 사람의 인지 범위를 넘어서는 건 동일한게 아닐까 생각 들었다.
결론은 새로운 계층이 생길 때까지 상위 의존 영역에서 관리하기로 마음먹고 협의 후 쉽게 수정할 수 있도록 신경썼다. 함수를 파리머터에 관리하며 하나의 책임을 맡고 클래스가 맡아야 할 책임을 본문에 선언하며 관리했다.
class EnrollCouponByMessageService(
...
// 쿠폰 코드를 사용하는 책임
private val useGiftCouponCode: (GiftCouponCode) -> GiftCoupon = {
useGiftCouponCodeStatePort.useBy(it)
loadGiftCouponStatePort.findGiftCoupon(it)
},
private val updateMembersCoupon: (Member) -> Unit = { updateMemberStatePort.updateMembersCoupon(it) },
) : EnrollCouponByMessageUseCase {
// 쿠폰을 등록하는 책임
override fun enroll(memberId: Long, code: GiftCouponCode) {
transactionArea.run {
val member = findMember(memberId)
val giftCoupon = useGiftCouponCode(code)
member.receiveCoupon(giftCoupon.coupon)
updateMembersCoupon(member)
}
}
}
레이어 사이 생성되는 의존 클래스는 어디에 둘지 어떻게 관리할지 항상 고민이다. 이런 규칙이 정해지면 쉽게 관리하면 되지만 생성이 필요할 때마다 팀을 불러세울 수 없다. 이런 상황에서는 해당 방법으로 관리하면서 추출 여부를 결정하는게 좋아보인다.
쿠폰 등록 과정을 찾아봤을 때 수동 입력 방식
, URL 등록 방식
두 분류로 나뉘었다.
사용자는 적은 액션일 수록 쉽게 접근하기 때문에 URL 방식을 고려했다.
URL 등록 방식을 고려하면 다음과 같은 문제점이 발생한다.
- 등록 API가 GET 방식이어야 한다.
- 변경되는 URL에 대비해야 한다.
카카오톡은 tiny url
로 한 계층 더 추상화해 API와의 의존성을 떨어뜨렸다.
덕분에 GET 방식이 아니어도 되고, 변경되는 URL에 대비할 수 있다. 하지만 필자는 단축 URL을 사용하지 않고 진행해보려 한다. 단축 URL까지 사용할만큼 확장성있게 운영할 생각이 없기 때문이다.
고려한 아키텍처에 맞지 않은 레이어가 생성되려고 할 때마다 머리를 뜯어왔다.
도메인 처럼 구분해야 할까?
사용하는 레이어에만 사용하니 해당 레이어에서 디렉토리를 생성해야 하나?
등 선택에 혼란이 왔다.
이런 경험은 팀에게 명확한 기준을 제시할 필요성을 경험했다.
기준을 제시할 때 어렵지 않고 불편하지 않는 선에서 정하는게 좋지 않을까 생각든다.