Skip to content

Commit

Permalink
refactor: Order 조회 시 목록으로 조회되도록 수정
Browse files Browse the repository at this point in the history
  • Loading branch information
Combi153 committed Apr 11, 2024
1 parent 34b0ea6 commit f0e8818
Show file tree
Hide file tree
Showing 9 changed files with 193 additions and 34 deletions.
50 changes: 30 additions & 20 deletions src/main/kotlin/com/petqua/application/payment/PaymentService.kt
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
package com.petqua.application.payment

import com.petqua.domain.order.OrderGroup
import com.petqua.domain.order.OrderNumber
import com.petqua.domain.order.OrderPayment
import com.petqua.domain.order.OrderPaymentRepository
import com.petqua.domain.order.OrderRepository
import com.petqua.domain.order.findByOrderNumberOrThrow
import com.petqua.domain.order.findLatestByOrderIdOrThrow
import com.petqua.domain.order.save
import com.petqua.domain.order.saveOrThrowOnIntegrityViolation
import com.petqua.domain.payment.tosspayment.TossPayment
import com.petqua.domain.payment.tosspayment.TossPaymentRepository
import com.petqua.exception.order.OrderException
import com.petqua.exception.order.OrderExceptionType.ORDER_NOT_FOUND
import com.petqua.exception.order.OrderPaymentException
import com.petqua.exception.order.OrderPaymentExceptionType
import com.petqua.exception.order.OrderPaymentExceptionType.FAIL_SAVE
import com.petqua.exception.order.OrderPaymentExceptionType.ORDER_PAYMENT_NOT_FOUND
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional

Expand All @@ -26,36 +27,45 @@ class PaymentService(

@Transactional(readOnly = true)
fun validateAmount(command: SucceedPaymentCommand) {
val order = orderRepository.findByOrderNumberOrThrow(command.orderNumber) {
val orders = orderRepository.findByOrderNumberOrThrow(command.orderNumber) {
OrderException(ORDER_NOT_FOUND)
}
order.validateOwner(command.memberId)
order.validateAmount(command.amount)
val orderGroup = OrderGroup(orders)
orderGroup.validateOwner(command.memberId)
orderGroup.validateAmount(command.amount)
}

fun processPayment(tossPayment: TossPayment): OrderPayment {
val order = orderRepository.findByOrderNumberOrThrow(tossPayment.orderNumber) {
fun processPayment(tossPayment: TossPayment) {
val orders = orderRepository.findByOrderNumberOrThrow(tossPayment.orderNumber) {
OrderException(ORDER_NOT_FOUND)
}
val orderGroup = OrderGroup(orders)
val payment = paymentRepository.save(tossPayment)
val orderPayment = orderPaymentRepository.findLatestByOrderIdOrThrow(order.id) {
OrderPaymentException(OrderPaymentExceptionType.ORDER_PAYMENT_NOT_FOUND)
}
return orderPaymentRepository.save(orderPayment.pay(payment.id)) {
OrderPaymentException(OrderPaymentExceptionType.FAIL_SAVE)
orderGroup.ordersWithSameOrderNumber.map {
val orderPayment =
orderPaymentRepository.findLatestByOrderIdOrThrow(it.id) {
OrderPaymentException(ORDER_PAYMENT_NOT_FOUND)
}
orderPaymentRepository.saveOrThrowOnIntegrityViolation(orderPayment.pay(payment.id)) {
OrderPaymentException(FAIL_SAVE)
}
}
}

fun cancelOrder(memberId: Long, orderNumber: OrderNumber) {
val order = orderRepository.findByOrderNumberOrThrow(orderNumber) {
val orders = orderRepository.findByOrderNumberOrThrow(orderNumber) {
OrderException(ORDER_NOT_FOUND)
}
order.validateOwner(memberId)
val orderPayment = orderPaymentRepository.findLatestByOrderIdOrThrow(order.id) {
OrderPaymentException(OrderPaymentExceptionType.ORDER_PAYMENT_NOT_FOUND)
}
orderPaymentRepository.save(orderPayment.cancel()) {
OrderPaymentException(OrderPaymentExceptionType.FAIL_SAVE)
val orderGroup = OrderGroup(orders)
orderGroup.validateOwner(memberId)

orderGroup.ordersWithSameOrderNumber.map {
val orderPayment = orderPaymentRepository.findLatestByOrderIdOrThrow(it.id) {
OrderPaymentException(ORDER_PAYMENT_NOT_FOUND)
}
orderPaymentRepository.saveOrThrowOnIntegrityViolation(orderPayment.cancel()) {
OrderPaymentException(FAIL_SAVE)
}
}
}
}
16 changes: 16 additions & 0 deletions src/main/kotlin/com/petqua/common/util/EntityManagerUtils.kt
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,22 @@ inline fun <reified T> EntityManager.createSingleQueryOrThrow(
}
}

inline fun <reified T> EntityManager.createFirstQueryOrThrow(
query: SelectQuery<*>,
context: JpqlRenderContext,
renderer: JpqlRenderer,
exceptionSupplier: () -> RuntimeException = { NoResultException("Query did not return any result") },
): T {
val rendered = renderer.render(query, context)
val results = this.createQuery(rendered.query, T::class.java)
.apply { rendered.params.forEach { (name, value) -> setParameter(name, value) } }
.resultList
return when {
results.isEmpty() -> throw exceptionSupplier()
else -> results[0]
}
}

inline fun <reified T> EntityManager.createQuery(
query: SelectQuery<*>,
context: JpqlRenderContext,
Expand Down
20 changes: 20 additions & 0 deletions src/main/kotlin/com/petqua/domain/order/OrderGroup.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.petqua.domain.order

import com.petqua.common.domain.Money

class OrderGroup(
val ordersWithSameOrderNumber: List<Order>,
) {

fun validateOwner(memberId: Long) {
ordersWithSameOrderNumber[FIRST].validateOwner(memberId)
}

fun validateAmount(amount: Money) {
ordersWithSameOrderNumber[FIRST].validateAmount(amount)
}

companion object {
private const val FIRST = 0;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.petqua.domain.order

interface OrderPaymentCustomRepository {

fun findLatestByOrderIdOrThrow(orderId: Long, exceptionSupplier: () -> RuntimeException): OrderPayment
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package com.petqua.domain.order

import com.linecorp.kotlinjdsl.dsl.jpql.jpql
import com.linecorp.kotlinjdsl.render.jpql.JpqlRenderContext
import com.linecorp.kotlinjdsl.render.jpql.JpqlRenderer
import com.petqua.common.util.createFirstQueryOrThrow
import jakarta.persistence.EntityManager
import org.springframework.stereotype.Repository

@Repository
class OrderPaymentCustomRepositoryImpl(
private val entityManager: EntityManager,
private val jpqlRenderContext: JpqlRenderContext,
private val jpqlRenderer: JpqlRenderer,
) : OrderPaymentCustomRepository {

override fun findLatestByOrderIdOrThrow(
orderId: Long,
exceptionSupplier: () -> RuntimeException,
): OrderPayment {
val query = jpql {
select(
entity(OrderPayment::class),
).from(
entity(OrderPayment::class),
).whereAnd(
path(OrderPayment::orderId).eq(orderId)
).orderBy(
path(OrderPayment::id).desc()
)
}

return entityManager.createFirstQueryOrThrow(
query,
jpqlRenderContext,
jpqlRenderer,
exceptionSupplier
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,14 @@ import org.springframework.data.jpa.repository.Query

fun OrderPaymentRepository.findLatestByOrderIdOrThrow(
orderId: Long,
exceptionSupplier: () -> Exception = { IllegalArgumentException("${OrderPayment::class.java.name} entity 를 찾을 수 없습니다.") }
exceptionSupplier: () -> Exception = { IllegalArgumentException("${OrderPayment::class.java.name} entity 를 찾을 수 없습니다.") },
): OrderPayment {
return findTopByOrderIdOrderByIdDesc(orderId) ?: throw exceptionSupplier()
}

fun OrderPaymentRepository.save(
fun OrderPaymentRepository.saveOrThrowOnIntegrityViolation(
orderPayment: OrderPayment,
exceptionSupplier: () -> Exception = { IllegalArgumentException("${OrderPayment::class.java.name} entity 를 저장할 수 없습니다.") }
exceptionSupplier: () -> Exception = { IllegalArgumentException("${OrderPayment::class.java.name} entity 를 저장할 수 없습니다.") },
): OrderPayment {
try {
return save(orderPayment)
Expand Down
9 changes: 5 additions & 4 deletions src/main/kotlin/com/petqua/domain/order/OrderRepository.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,13 @@ import org.springframework.data.jpa.repository.JpaRepository

fun OrderRepository.findByOrderNumberOrThrow(
orderNumber: OrderNumber,
exceptionSupplier: () -> Exception = { IllegalArgumentException("${Order::class.java.name} entity 를 찾을 수 없습니다.") }
): Order {
return findByOrderNumber(orderNumber) ?: throw exceptionSupplier()
exceptionSupplier: () -> Exception = { IllegalArgumentException("${Order::class.java.name} entity 를 찾을 수 없습니다.") },
): List<Order> {
val orders = findByOrderNumber(orderNumber)
return orders.ifEmpty { throw exceptionSupplier() }
}

interface OrderRepository : JpaRepository<Order, Long> {

fun findByOrderNumber(orderNumber: OrderNumber): Order?
fun findByOrderNumber(orderNumber: OrderNumber): List<Order>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package com.petqua.domain.order

import com.petqua.exception.order.OrderPaymentException
import com.petqua.exception.order.OrderPaymentExceptionType.ORDER_PAYMENT_NOT_FOUND
import com.petqua.test.DataCleaner
import io.kotest.assertions.throwables.shouldThrow
import io.kotest.core.spec.style.BehaviorSpec
import io.kotest.matchers.shouldBe
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment.NONE
import kotlin.Long.Companion.MIN_VALUE

@SpringBootTest(webEnvironment = NONE)
class OrderPaymentRepositoryTest(
private val orderPaymentRepository: OrderPaymentRepository,
private val dataCleaner: DataCleaner,
) : BehaviorSpec({

Given("주문번호로 최신 OrderPayment 를 조회할 때") {
val orderId = 1L
orderPaymentRepository.saveAll(
listOf(
OrderPayment(
id = 1L,
orderId = orderId,
prevId = null,
),
OrderPayment(
id = 2L,
orderId = orderId,
prevId = 1L,
),
OrderPayment(
id = 3L,
orderId = orderId,
prevId = 2L,
)
)
)

When("주문번호를 입력하면") {
val latestOrderPayment = orderPaymentRepository.findLatestByOrderIdOrThrow(orderId) {
OrderPaymentException(ORDER_PAYMENT_NOT_FOUND)
}

Then("최신 OrderPayment 를 반환한다") {
latestOrderPayment.id shouldBe 3L
latestOrderPayment.prevId shouldBe 2L
latestOrderPayment.orderId shouldBe orderId
}
}

When("존재하지 않는 주문번호를 입력하면") {

Then("예외를 던진다") {
shouldThrow<OrderPaymentException> {
orderPaymentRepository.findLatestByOrderIdOrThrow(MIN_VALUE) {
OrderPaymentException(ORDER_PAYMENT_NOT_FOUND)
}
}.exceptionType() shouldBe ORDER_PAYMENT_NOT_FOUND
}
}
}

afterContainer {
dataCleaner.clean()
}
})

Original file line number Diff line number Diff line change
Expand Up @@ -185,15 +185,12 @@ class OrderControllerTest(
totalAmount = productA1.discountPrice + productA2.discountPrice + Money.from(3000),
)

val response = requestSaveOrder(request, accessToken)
requestSaveOrder(request, accessToken)

Then("예외가 발생한다") {
// 한 업체에서는 같은 배송 방법으로만 주문 가능. 다른 방법으로 주문할 때에는 다시 새로 구매.
val errorResponse = response.`as`(ExceptionResponse::class.java)
Then("배송번호는 두 개 생성된다") {
val orders = orderRepository.findAll()

assertSoftly(response) {
statusCode shouldBe BAD_REQUEST.value()
}
orders.distinctBy { it.orderProduct.shippingNumber }.size shouldBe 2
}
}

Expand Down

0 comments on commit f0e8818

Please sign in to comment.