본문 바로가기
Kotlin

스프링 부트와 코틀린으로 구현하는 온라인 쇼핑몰 결제 서비스 API

by ByteBridge 2023. 9. 28.
반응형

오늘은 스프링 부트와 코틀린을 활용하여 온라인 쇼핑몰의 다양한 결제 수단을 지원하는 API를 개발해볼 것입니다. 특히, 팩토리 패턴을 이용하여 각 결제 수단에 대응되는 서비스를 효과적으로 구현하는 방법에 대해서 알아보겠습니다.

1. 결제 수단 및 2차 결제 수단 Enum 타입 정의
먼저, 결제 수단 및 2차 결제 수단을 Enum 타입으로 정의합니다.

enum class CashPaymentType {
CREDIT_CARD,
BANK_TRANSFER
}

enum class SecondaryPaymentType {
POINT,
COUPON
}
📌 여기서, CashPaymentType은 현금 결제 수단을, SecondaryPaymentType은 포인트나 쿠폰과 같은 2차 결제 수단을 나타냅니다.
 
 
2. 결제 서비스와 팩토리 패턴 정의
각 결제 수단마다 결제를 처리하는 로직이 다를 수 있기 때문에, 각 결제 방법에 대응하는 서비스를 개별적으로 구현합니다.
interface PaymentService {
fun pay(orderId: Long, amount: Double): Boolean
}

class CreditCardPaymentService : PaymentService {
override fun pay(orderId: Long, amount: Double): Boolean {
// 신용카드 결제 로직
return true
}
}

class BankTransferPaymentService : PaymentService {
override fun pay(orderId: Long, amount: Double): Boolean {
// 은행 계좌 이체 로직
return true
}
}

class PointPaymentService : PaymentService {
override fun pay(orderId: Long, amount: Double): Boolean {
// 포인트 결제 로직
return true
}
}

class CouponPaymentService : PaymentService {
override fun pay(orderId: Long, amount: Double): Boolean {
// 쿠폰 결제 로직
return true
}
}

object PaymentServiceFactory {
fun getService(cashPaymentType: CashPaymentType?, secondaryPaymentType: SecondaryPaymentType?): PaymentService {
return when {
cashPaymentType == CashPaymentType.CREDIT_CARD -> CreditCardPaymentService()
cashPaymentType == CashPaymentType.BANK_TRANSFER -> BankTransferPaymentService()
secondaryPaymentType == SecondaryPaymentType.POINT -> PointPaymentService()
secondaryPaymentType == SecondaryPaymentType.COUPON -> CouponPaymentService()
else -> throw IllegalArgumentException("Invalid payment type")
}
}
}

📌 위의 PaymentService는 결제를 진행하는 기본적인 인터페이스입니다. 이를 구현하여 각 결제 수단별로 서비스를 만들어줍니다.

3. 결제 서비스 API 정의
결제를 진행하는 API를 정의합니다.

팩토리 패턴을 사용하여, 결제 요청이 들어왔을 때 적절한 결제 서비스를 반환해줄 수 있게 합니다.

📌 PaymentServiceFactory는 주어진 결제 수단에 따라 적절한 결제 서비스를 반환해주는 팩토리 객체입니다.

@RestController
@RequestMapping("/api/payment")
class PaymentController(private val paymentServiceFactory: PaymentServiceFactory) {

@PostMapping
fun makePayment(@RequestBody request: PaymentRequest): ResponseEntity<String> {
val service = paymentServiceFactory.getService(request.cashPaymentType, request.secondaryPaymentType)
val result = service.pay(request.orderId, request.amount)

return if (result) {
ResponseEntity.ok("Payment successful")
} else {
ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("Payment failed")
}
}
}

data class PaymentRequest(
val orderId: Long,
val amount: Double,
val cashPaymentType: CashPaymentType?,
val secondaryPaymentType: SecondaryPaymentType?
)

위에서는 PaymentServiceFactory 객체를 PaymentController에 주입하여 사용했습니다.
이를 통해 요청받은 결제 수단에 따라 적절한 결제 서비스를 선택하여 결제를 진행할 수 있습니다.
 

이렇게 스프링 부트와 코틀린을 활용하여 팩토리 패턴을 적용한 결제 서비스를 구현하는 방법을 살펴보았습니다. 이 방법을 통해, 다양한 결제 수단을 유연하게 지원하면서 코드의 가독성과 확장성도 보장할 수 있습니다.

 

고민해볼 부분: 확장 가능한 요청 처리를 위한 설계

결제 수단별로 요청을 받는 객체가 다를 경우, 즉, 각 결제 수단마다 요구하는 데이터나 파라미터가 다른 상황을 대비하기 위해선 몇 가지 고민과 전략이 필요합니다.

1. DTO (Data Transfer Object) 활용

각 결제 수단마다 요구하는 정보가 다를 수 있으므로, 각 결제 수단별로 별도의 DTO 객체를 구성하는 것이 좋습니다. 예를 들면, CreditCardPaymentRequest, BankTransferPaymentRequest와 같은 형태로 설계할 수 있습니다.

2. 전략 패턴 (Strategy Pattern) 활용

팩토리 패턴과 함께 전략 패턴을 사용하여 결제 수단별로 다양한 로직을 처리할 수 있도록 합니다. 각 결제 전략(서비스)에 해당하는 로직을 별도의 클래스로 분리하여 요청에 따라 동적으로 해당 로직을 선택하여 실행하도록 할 수 있습니다.

3. 빌더 패턴 (Builder Pattern) 활용

결제 정보를 조립하는 과정에서 필요한 정보가 많거나 복잡한 경우 빌더 패턴을 활용하여 요청 객체를 단계별로 쉽게 생성할 수 있도록 설계할 수 있습니다.

4. 요청 검증 (Validation)

각 결제 수단별로 요구되는 데이터의 형식이나 값의 범위가 다를 수 있으므로, 각 요청 DTO에 대한 검증 로직을 구현하는 것이 중요합니다. Spring의 @Valid 어노테이션과 함께 사용자 정의 검증 어노테이션을 활용하면 효율적인 검증 프로세스를 구현할 수 있습니다.

5. 확장성 고려한 설계

새로운 결제 수단이 추가될 때마다 기존 코드의 변경을 최소화하기 위해 인터페이스와 추상 클래스를 적절히 활용하여 설계합니다. 또한, 결제 관련 환경 설정이나 상수 값을 외부 설정 파일이나 데이터베이스로부터 로드하여 관리하는 것도 확장성 향상에 도움이 될 수 있습니다.


결론적으로, 결제 수단의 다양성과 확장성을 고려하여 설계하고 구현하는 것은 결제 서비스의 품질과 유지보수성에 큰 영향을 미칩니다. 따라서 다양한 설계 패턴과 기술적 전략을 적절히 활용하여 높은 품질의 결제 서비스를 제공하는 것이 중요합니다.

반응형