밤빵's 개발일지
[WIL]20240825 결제 기능을 구현할 때 꼭 숙지해야 할 문제점 본문
결제기능은 어려운 기능이라고 해서, 간단하게 구현하기로 했지만 알아둬야 할 문제점들이 많다. 기술매니저님께서 중요하다고 하는 문제점에 대해선 따로 더 세세하게 작성 할 예정이고, 오늘은 결제시스템 구현에 대해 알아보면서 알게 된 문제점들에 대해 간단하게 정리해보기로 했다.
결제 기능을 구현할 때 꼭 숙지해야 할 문제점들은 다양하다. 결제는 사용자와의 신뢰를 구축하는 중요한 기능이기 때문에 보안, 데이터 일관성, 성능 등 여러 측면에서 신중하게 설계하고 구현해야 한다. 오늘 개발일지에서는 결제 기능을 구현할 때 반드시 고려해야 할 몇 가지 주요 문제점과 이를 해결하기 위한 간단한 예시와 코드 예시를 정리했다!
1. 동시성 이슈
→ 문제점:
여러 사용자가 동시에 결제를 요청하면 데이터베이스의 동일한 상품에 접근할 때 문제가 발생할 수 있다. 예를 들어, 재고 관리 시스템에서 여러 사용자가 동시에 같은 상품을 결제하면, 재고 부족 오류가 발생하거나 중복 결제가 일어날 수 있다.
→ 해결 방안:
이를 해결하기 위해 낙관적 락(Optimistic Locking)과 비관적 락(Pessimistic Locking) 전략을 사용할 수 있다. 낙관적 락은 데이터를 업데이트할 때 버전 번호를 사용하여 충돌을 감지하고, 비관적 락은 데이터를 읽을 때부터 락을 걸어 다른 트랜잭션이 접근하지 못하도록 한다.
▽ 낙관적 락 (Optimistic Locking) 예시
import jakarta.persistence.*;
@Entity
public class Product {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private int quantity;
@Version // 낙관적 락을 위한 버전 필드
private int version;
// Getter 생략
}
→ 이 예시에서는 @Version 어노테이션을 사용하여 낙관적 락을 구현했다. 이 필드는 데이터베이스 테이블에 버전 정보를 추가하여 동시에 업데이트되는 경우 충돌을 감지할 수 있게 해준다.
▽ 비관적 락(Pessimistic Locking) 예시
package com.example.payment.repository;
import com.example.payment.entity.Payment;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Lock;
import org.springframework.stereotype.Repository;
import jakarta.persistence.LockModeType;
import java.util.Optional;
@Repository
public interface PaymentRepository extends JpaRepository<Payment, Long> {
// 비관적 락을 사용하여 결제 엔티티를 가져옴
@Lock(LockModeType.PESSIMISTIC_WRITE)
Optional<Payment> findById(Long id);
}
→ @Lock(LockModeType.PESSIMISTIC_WRITE) 어노테이션을 사용하여 findById() 메서드에서 해당 레코드를 읽을 때, 다른 트랜잭션이 해당 데이터를 수정할 수 없도록 한다. 이를 통해 결제 중 데이터 충돌을 방지할 수 있다.
▷ 동시성 이슈는 기술매니저님이 중요하다고 하셔서 따로 세세하게 다룰 예정!
2. 멱등성(Idempotency)
→ 문제점:
결제 API는 멱등성을 가져야 한다. 예를 들어, 사용자가 결제 버튼을 여러 번 클릭했을 때, 동일한 결제가 여러 번 처리되지 않도록 해야 한다.
→ 해결 방안:
결제 요청마다 고유한 ID(예: requestId)를 발급하고, 해당 ID로 요청을 구분하여 처리한다. 서버는 이미 처리된 요청의 requestId를 기록하고, 동일한 requestId가 다시 들어올 경우 중복 결제를 방지한다.
▽ 코드 예시 (Idempotency Key 사용)
import org.springframework.stereotype.Service;
import java.util.HashSet;
import java.util.Set;
@Service
public class PaymentService {
private final Set<String> processedRequestIds = new HashSet<>();
public String processPayment(String requestId, PaymentRequest paymentRequest) {
if (processedRequestIds.contains(requestId)) {
return "이미 처리된 요청입니다."; // 멱등성 보장
}
processedRequestIds.add(requestId);
// 결제 처리 로직
return "결제 완료";
}
}
→ 이 코드에서는 processedRequestIds라는 Set을 사용하여 이미 처리된 요청의 requestId를 저장하고, 동일한 requestId가 다시 들어올 경우 결제를 처리하지 않는다.
▷ 멱등성은 PUT과PATCH로 공부하면 이해가 될거라고 하셔서 세세하게 따로 다룰 예정!
3. 데이터 일관성 유지
→ 문제점:
결제 처리 과정에서 여러 단계의 데이터베이스 조작이 필요할 수 있다. 예를 들어, 결제 완료 후 주문 상태를 업데이트하고, 결제 내역을 저장하며, 사용자 포인트를 적립하는 등의 작업이 연속적으로 이루어진다. 이러한 작업 중 하나라도 실패하면 데이터가 일관되지 않을 수 있다.
→ 해결 방안:
이러한 문제를 해결하기 위해 트랜잭션(Transaction)을 사용한다. 트랜잭션은 여러 데이터베이스 작업을 하나의 작업으로 묶어, 모든 작업이 성공하거나 모두 롤백되도록 보장한다.
▽ 코드 예시 (트랜잭션 사용)
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class OrderService {
@Transactional // 트랜잭션 어노테이션
public void completeOrder(PaymentRequest paymentRequest) {
// 결제 처리
processPayment(paymentRequest);
// 주문 상태 업데이트
updateOrderStatus(paymentRequest.getOrderId(), "COMPLETED");
// 사용자 포인트 적립
addUserPoints(paymentRequest.getUserId(), 100);
}
private void processPayment(PaymentRequest paymentRequest) {
// 결제 처리 로직
}
private void updateOrderStatus(Long orderId, String status) {
// 주문 상태 업데이트 로직
}
private void addUserPoints(Long userId, int points) {
// 사용자 포인트 적립 로직
}
}
→ @Transactional 어노테이션을 사용하여 completeOrder 메서드를 트랜잭션으로 묶었다. 이로 인해 메서드 내의 모든 작업이 성공해야만 최종적으로 커밋되고, 하나라도 실패하면 롤백된다.
4. 보안 문제
→ 문제점:
결제와 관련된 모든 요청과 데이터는 민감한 정보를 포함하고 있다. 따라서 이러한 데이터가 노출되거나 변조되는 경우 심각한 보안 문제가 발생할 수 있다.
→ 해결 방안:
HTTPS 프로토콜을 사용하여 데이터 전송을 암호화하고, 결제 API 요청 시 JWT 또는 OAuth와 같은 인증 토큰을 사용하여 인증 및 인가를 관리한다.
▽ 코드 예시 (HTTPS 설정)
# application.properties 예시 (HTTPS 설정)
server.ssl.key-store=classpath:keystore.p12
server.ssl.key-store-password=password
server.ssl.key-store-type=PKCS12
server.ssl.key-alias=tomcat
→ 이 예시에서는 Spring Boot 애플리케이션에서 HTTPS를 설정하는 방법을 보여준다. application.properties 파일에서 SSL 설정을 추가하여 서버가 HTTPS를 사용하도록 설정할 수 있다.
▶정리
결제 기능을 구현할 때 초보 백엔드 개발자가 꼭 숙지해야 하는 문제점들을 정리했다. 동시성 이슈와 멱등성, 데이터 일관성 유지, 보안 문제를 주요 고려사항으로 다루고, 각 문제를 해결하기 위한 방법과 코드 예시를 만들어봤다. 결제 기능은 사용자 경험과 직접적인 관련이 있는 중요한 기능이라 이런 문제들을 신중히 고려하고 해결해야하는데.. 지금은 기본적인 흐름도 잘 잡히지않아서 문제점을 보안 할 수 있는 결제 기능을 구현할 수 있을지 모르겠다😟
'개발Article' 카테고리의 다른 글
[TIL]20240827 Spring batch (0) | 2024.08.27 |
---|---|
[TIL]20240826 파인튜닝(Fine-Tuning) (0) | 2024.08.26 |
[TIL]20240824 MSA(Microservices Architecture) (0) | 2024.08.24 |
[TIL]20240823 결제기능구현을 위한 시퀀스다이어그램 & 포트원 결제플로우 (0) | 2024.08.23 |
[TIL]20240822 페이지네이션(Pagination) (0) | 2024.08.22 |