Skip to content

Commit

Permalink
Merge pull request #27 from this-is-spear/회원-쿠폰-보유-상태를-변경한다
Browse files Browse the repository at this point in the history
쿠폰 도메인에서 회원 상태 변경 방식을 변경한다.
  • Loading branch information
this-is-spear authored Jun 9, 2024
2 parents b073289 + dc226a3 commit 01149f3
Show file tree
Hide file tree
Showing 21 changed files with 164 additions and 116 deletions.
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -144,4 +144,8 @@ gradle-app.setting
# Java heap dump
*.hprof

### Jqwik ###

**/.jqwik-database

# End of https://www.toptal.com/developers/gitignore/api/gradle,intellij+all,kotlin
Original file line number Diff line number Diff line change
@@ -1,46 +1,51 @@
package com.example.estdelivery.coupon.application.port.out.adapter.persistence

import com.example.estdelivery.coupon.application.port.out.adapter.persistence.entity.MemberEntity
import com.example.estdelivery.coupon.application.port.out.adapter.persistence.entity.MemberCouponEntity
import com.example.estdelivery.coupon.application.port.out.adapter.persistence.entity.MemberCouponUseState
import com.example.estdelivery.coupon.application.port.out.adapter.persistence.mapper.fromCoupon
import com.example.estdelivery.coupon.application.port.out.adapter.persistence.mapper.toCoupon
import com.example.estdelivery.coupon.application.port.out.adapter.persistence.repository.MemberRepository
import com.example.estdelivery.coupon.application.port.out.adapter.persistence.repository.MemberCouponRepository
import com.example.estdelivery.coupon.domain.coupon.Coupon
import com.example.estdelivery.coupon.domain.coupon.CouponBook
import com.example.estdelivery.coupon.domain.member.UnusedCouponBook
import org.springframework.data.repository.findByIdOrNull
import org.springframework.stereotype.Component
import org.springframework.transaction.annotation.Transactional

@Component
class MemberPersistenceAdapter(
private val memberRepository: MemberRepository
private val memberCouponRepository: MemberCouponRepository,
) {
/**
* 회원이 사용하지 않은 쿠폰 정보를 조회한다. 회원 정보가 없는 경우 회원 정보를 새롭게 추가한다.
* `asSequence`를 이용해 사용자의 쿠폰 리스트를 중간마다 버퍼링하지 않고 최종적으로 버퍼링을 진행한다.
*/
@Transactional(readOnly = true)
fun findUnusedCouponByMemberId(memberId: Long) =
memberRepository.findByIdOrNull(memberId)?.let { memberEntity ->
memberEntity.unusedCoupons.map {
toCoupon(it)
}.let {
UnusedCouponBook(CouponBook(it))
}
} ?: memberRepository.save(
MemberEntity(
emptyList(),
memberId
)
).let {
UnusedCouponBook(CouponBook(emptyList()))
}
memberCouponRepository.findMembersUnusedCoupon(memberId)
.asSequence()
.map { it.coupon }
.map { toCoupon(it) }
.let { UnusedCouponBook(CouponBook(it.toList())) }

/**
* 추가된 쿠폰을 추가하거나 사용한 쿠폰만 찾아 상태를 변경한다.
* `dirty checking`을 사용했으니 주의하자.
*/
@Transactional
fun updateUnusedCouponBook(memberId: Long, unusedCoupons: List<Coupon>) {
val memberEntity = memberRepository.findByIdOrNull(memberId) ?: return
memberEntity.unusedCoupons = unusedCoupons
unusedCoupons
.map {
fromCoupon(it)
}.toList()
memberCouponRepository.findByMemberIdAndCoupon(memberId, fromCoupon(it))
?: memberCouponRepository.save(
MemberCouponEntity(
fromCoupon(it),
memberId,
MemberCouponUseState.UNUSED
)
)
}
.filterIsInstance<Coupon.UsedCoupon>()
.map { memberCouponRepository.findByMemberIdAndCoupon(memberId, fromCoupon(it)) }
.map { it!!.status = MemberCouponUseState.USED }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ class CouponEntity(
var amount: Int,
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
var id: Long? = null,
var id: Long = 0,
) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
Expand All @@ -32,6 +32,6 @@ class CouponEntity(
}

override fun hashCode(): Int {
return id?.hashCode() ?: 0
return id.hashCode()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package com.example.estdelivery.coupon.application.port.out.adapter.persistence.entity

import jakarta.persistence.Entity
import jakarta.persistence.EnumType
import jakarta.persistence.Enumerated
import jakarta.persistence.GeneratedValue
import jakarta.persistence.GenerationType
import jakarta.persistence.Id
import jakarta.persistence.JoinColumn
import jakarta.persistence.ManyToOne

@Entity
class MemberCouponEntity(
@ManyToOne
@JoinColumn(name = "coupon_id")
val coupon: CouponEntity,
val memberId: Long,
@Enumerated(EnumType.STRING)
var status: MemberCouponUseState,
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
val id: Long = 0,
) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is MemberCouponEntity) return false

if (other.id == 0L) return false
if (id != other.id) return false

return true
}

override fun hashCode(): Int {
return id.hashCode()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.example.estdelivery.coupon.application.port.out.adapter.persistence.entity

enum class MemberCouponUseState {
USED,
UNUSED,
EXPIRED,
;
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ private fun getCouponAmount(coupon: Coupon): Int {
return when (coupon) {
is Coupon.RateDiscountCoupon -> coupon.discountRate
is Coupon.FixDiscountCoupon -> coupon.discountAmount
else -> throw IllegalArgumentException("UsedCoupon is not supported")
}
}

Expand All @@ -65,6 +66,7 @@ private fun getCouponStateAmountType(coupon: Coupon): CouponStateAmountType {
return when (coupon) {
is Coupon.RateDiscountCoupon -> RATE
is Coupon.FixDiscountCoupon -> FIX
else -> throw IllegalArgumentException("UsedCoupon is not supported")
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.example.estdelivery.coupon.application.port.out.adapter.persistence.repository

import com.example.estdelivery.coupon.application.port.out.adapter.persistence.entity.CouponEntity
import com.example.estdelivery.coupon.application.port.out.adapter.persistence.entity.MemberCouponEntity
import org.springframework.data.jpa.repository.JpaRepository
import org.springframework.data.jpa.repository.Query

interface MemberCouponRepository : JpaRepository<MemberCouponEntity, Long> {
@Query(
"""
SELECT mc
FROM MemberCouponEntity mc
WHERE mc.memberId = :memberId
AND mc.status = com.example.estdelivery.coupon.application.port.out.adapter.persistence.entity.MemberCouponUseState.UNUSED
"""
)
fun findMembersUnusedCoupon(memberId: Long): List<MemberCouponEntity>
fun findByMemberIdAndCoupon(memberId: Long, coupon: CouponEntity): MemberCouponEntity?
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -4,24 +4,28 @@ sealed class Coupon(
open val name: String,
open val description: String,
internal val couponType: CouponType,
internal val id: Long? = null,
internal val id: Long = 0,
) {
class RateDiscountCoupon(
val discountRate: Int,
override val name: String,
override val description: String,
couponType: CouponType,
id: Long? = null,
id: Long = 0,
) : Coupon(name, description, couponType, id)

class FixDiscountCoupon(
val discountAmount: Int,
override val name: String,
override val description: String,
couponType: CouponType,
id: Long? = null,
id: Long = 0,
) : Coupon(name, description, couponType, id)

class UsedCoupon(
val coupon: Coupon,
) : Coupon(coupon.name, coupon.description, coupon.couponType, coupon.id)

fun isPublished(): Boolean {
return couponType == CouponType.IS_PUBLISHED
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ class CouponBook(
return coupons.toList()
}

fun deleteCoupon(coupon: Coupon) {
fun removeCoupon(coupon: Coupon) {
require(coupons.contains(coupon)) { "존재하지 않는 쿠폰입니다." }
coupons = coupons - coupon
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ class Member(
private val unusedCouponBook: UnusedCouponBook = UnusedCouponBook(),
) {
fun useCoupon(coupon: Coupon) {
unusedCouponBook.removeUsedCoupon(coupon)
unusedCouponBook.useCoupon(coupon)
}

fun showMyCouponBook(): List<Coupon> {
Expand All @@ -23,7 +23,7 @@ class Member(
coupon: Coupon,
target: Member,
) {
unusedCouponBook.removeUsedCoupon(coupon)
unusedCouponBook.useCoupon(coupon)
target.receiveCoupon(coupon)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,24 @@ import com.example.estdelivery.coupon.domain.coupon.Coupon
import com.example.estdelivery.coupon.domain.coupon.CouponBook

class UnusedCouponBook(
private val unUsedCoupons: CouponBook = CouponBook(),
private val coupons: CouponBook = CouponBook(),
) {
fun showUnusedCoupons(): List<Coupon> {
return unUsedCoupons.showCoupons()
return coupons.showCoupons()
}

fun addUnusedCoupon(coupon: Coupon) {
require(unUsedCoupons.showCoupons().contains(coupon).not()) { "이미 존재하는 쿠폰입니다." }
unUsedCoupons.addCoupon(coupon)
require(coupons.showCoupons().contains(coupon).not()) { "이미 존재하는 쿠폰입니다." }
coupons.addCoupon(coupon)
}

fun removeUsedCoupon(coupon: Coupon) {
require(unUsedCoupons.showCoupons().contains(coupon)) { "존재하지 않는 쿠폰입니다." }
unUsedCoupons.deleteCoupon(coupon)
fun useCoupon(coupon: Coupon) {
require(coupons.showCoupons().contains(coupon)) { "존재하지 않는 쿠폰입니다." }
coupons.removeCoupon(coupon)
coupons.addCoupon(Coupon.UsedCoupon(coupon))
}

override fun toString(): String {
return "UnusedCouponBook(unUsedCoupons=$unUsedCoupons)"
return "UnusedCouponBook(unUsedCoupons=$coupons)"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ class FindAvailableGiftCouponServiceTest : FreeSpec({
val availableGiftCoupons = findAvailableGiftCouponService.findAvailableGiftCoupon(memberId)

// then
availableGiftCoupons.coupons.map { it.id } shouldBe 일건창.showMyCouponBook().filter { !it.isPublished() }.map { it.id }
availableGiftCoupons.coupons.map { it.id } shouldBe 일건창.showMyCouponBook().filter { !it.isPublished() }
.map { it.id }
}
})
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,6 @@ class GiftCouponByMessageServiceTest : FreeSpec({
giftCouponByMessageService.sendGiftAvailableCoupon(일건창.id, 선물할_쿠폰.coupon.id!!, 선물_메시지)

// then
변경된_회원_정보.captured.showMyCouponBook().find { it.id == 선물할_쿠폰.coupon.id } shouldBe null
giftAvailableCoupon.senderName shouldBe 일건창.name
giftAvailableCoupon.enrollEndDate shouldBe 선물할_쿠폰.enrollEndDate
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package com.example.estdelivery.coupon.application.port.out.adapter

import com.example.estdelivery.coupon.application.port.out.adapter.persistence.repository.CouponRepository
import com.example.estdelivery.coupon.application.port.out.adapter.persistence.repository.MemberRepository
import com.example.estdelivery.coupon.application.port.out.adapter.persistence.repository.ShopOwnerRepository
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.BeforeEach
Expand All @@ -25,16 +24,13 @@ class EventAdapterTest(
@Autowired
private val memberAdapter: MemberAdapter,
@Autowired
private val memberRepository: MemberRepository,
@Autowired
private val eventAdapter: EventAdapter,
@Autowired
private val shopOwnerRepository: ShopOwnerRepository,
) {

@BeforeEach
fun setUp() {
memberRepository.deleteAll()
shopOwnerRepository.deleteAll()
couponRepository.deleteAll()
}
Expand Down
Loading

0 comments on commit 01149f3

Please sign in to comment.