문제 상황
현재 서비스에는 카카오페이만 결제수단으로 지원되고 있습니다. 비즈니스 요구사항이 증가하면서 네이버 페이 페이코, 토스페이 등 다양한 결제 수단을 추가해야 하는 상황입니다. 핵심은OCP(개방-폐쇄 원칙)를 지키면서 확장할수있는 구조를 만드는 것입니다.
OCP(개방- 폐쇄 원칙)란?
"소프트웨어 개체는 확장에는 열려 있어야하고, 변경에는 닫혀 있어야한다"
- 기존코드를 수정하지 않고도 새로운 가능을 추가 할수 있어야함
┌─────────────────────────────────┐
│ PaymentService │ ← 확장에 닫혀 있음
├─────────────────────────────────┤
│ - processPayment() │
│ - cancelPayment() │
│ - refundPayment() │
└──────────────┬──────────────────┘
│ 의존성 주입
▼
┌─────────────────────────────────┐
│ PaymentProcessor (인터페이스) │ ← 확장에 열려 있음
├─────────────────────────────────┤
│ + process() │
│ + cancel() │
│ + refund() │
│ + supports() │
└─────┬────────────┬──────────────┘
│ │
▼ ▼
┌─────────┐ ┌─────────┐ (추가 가능)
│KakaoPay │ │NaverPay │ │ Payco │ ...
└─────────┘ └─────────┘ └───────┘
단계별 구현가이드
step1: 공통 인터페이스 정의
/**
* 모든 결제 수단이 구현해야 할 공통 인터페이스
*/
public interface PaymentProcessor {
/**
* 결제 처리
*/
PaymentResult processPayment(PaymentRequest request);
/**
* 결제 취소
*/
PaymentResult cancelPayment(String paymentId);
/**
* 결제 환불
*/
PaymentResult refundPayment(String paymentId, BigDecimal amount);
/**
* 지원하는 결제 수단 확인
*/
boolean supports(PaymentMethod method);
}
Step 2: 결제수단 Enum 정의
/**
* 지원하는 결제 수단 목록
*/
public enum PaymentMethod {
KAKAO_PAY("카카오페이"),
NAVER_PAY("네이버페이"),
PAYCO("페이코"),
TOSS_PAY("토스페이");
private final String description;
PaymentMethod(String description) {
this.description = description;
}
}
Step 3: 기존 카카오 페이 리팩토링
@Component
public class KakaoPayProcessor implements PaymentProcessor {
private final KakaoPayClient kakaoPayClient;
@Override
public PaymentResult processPayment(PaymentRequest request) {
// 카카오페이 전용 로직
KakaoPayResponse response = kakaoPayClient.pay(
request.getAmount(),
request.getOrderId()
);
return PaymentResult.builder()
.success(response.isSuccess())
.paymentId(response.getPaymentId())
.transactionId(response.getTid())
.build();
}
@Override
public boolean supports(PaymentMethod method) {
return PaymentMethod.KAKAO_PAY.equals(method);
}
@Override
public PaymentResult cancelPayment(String paymentId) {
// 카카오페이 취소 로직
}
@Override
public PaymentResult refundPayment(String paymentId, BigDecimal amount) {
// 카카오페이 환불 로직
}
}
Step4: 결제 프로세서 팩토리 구현
@Component
public class PaymentProcessorFactory {
private final Map<PaymentMethod, PaymentProcessor> processorMap;
// Spring의 의존성 주입을 활용한 자동 등록
public PaymentProcessorFactory(List<PaymentProcessor> processors) {
processorMap = processors.stream()
.flatMap(processor ->
Arrays.stream(PaymentMethod.values())
.filter(processor::supports)
.map(method -> Map.entry(method, processor))
)
.collect(Collectors.toMap(
Map.Entry::getKey,
Map.Entry::getValue
));
}
public PaymentProcessor getProcessor(PaymentMethod method) {
return Optional.ofNullable(processorMap.get(method))
.orElseThrow(() -> new UnsupportedPaymentMethodException(
"지원하지 않는 결제 수단: " + method));
}
}
Step 5: 결제 서비스 구현 (OCP 의핵심)
@Service
public class PaymentService {
private final PaymentProcessorFactory processorFactory;
/**
* 결제 실행 - 새로운 결제 수단이 추가되어도 이 메서드는 변경되지 않음!
*/
public PaymentResult pay(PaymentRequest request) {
PaymentProcessor processor = processorFactory
.getProcessor(request.getPaymentMethod());
// 공통 검증 로직
validatePaymentRequest(request);
// 결제 처리 위임
PaymentResult result = processor.processPayment(request);
// 공통 후처리 로직
savePaymentHistory(request, result);
return result;
}
/**
* 결제 취소
*/
public PaymentResult cancel(String paymentId, PaymentMethod method) {
PaymentProcessor processor = processorFactory.getProcessor(method);
return processor.cancelPayment(paymentId);
}
// ... 기타 공통 메서드들
}
새로운 결제 수단 추가하기 (OCP 실현)
예시: 네이버페이 추가하기
@Component
public class NaverPayProcessor implements PaymentProcessor {
private final NaverPayClient naverPayClient;
@Override
public PaymentResult processPayment(PaymentRequest request) {
// 네이버페이 전용 로직
NaverPayResponse response = naverPayClient.requestPayment(
request.getAmount(),
request.getOrderId(),
request.getCustomerId()
);
return PaymentResult.builder()
.success("SUCCESS".equals(response.getStatus()))
.paymentId(response.getPaymentId())
.transactionId(response.getTransactionId())
.build();
}
@Override
public boolean supports(PaymentMethod method) {
return PaymentMethod.NAVER_PAY.equals(method);
}
// ... cancelPayment, refundPayment 구현
}
이런식으로 공통로직 수정할필요없고 따로 상속해서 구현하면 유지보수성 향상한다
'면접복기' 카테고리의 다른 글
| (면접복기)자바 컴파일 과정 완벽 정리: .java → 실행까지 (0) | 2026.05.15 |
|---|---|
| (면접복기)자바 기본 타입(Primitive Type) 완벽 정리 (0) | 2026.05.15 |
| (면접복기)트랜잭션 격리 수준 (Transaction Isolation Levels) (0) | 2025.11.19 |
| (면접복기)쓰레드 풀 vs 커넥션 풀 (0) | 2025.11.19 |