티스토리 뷰
뭔가 올드해
평소에 나는 디자인패턴에 대해서 회의적인 느낌을 받았다.
'헤드퍼스트 디자인패턴'이란 책이 개발자라면 필수로 구입해야 하는 책이라고 듣고
서점에서 이 책을 들춰봤을때. 생각했다.
'아 디자인패턴을 알아야 할 이유가 없구나'
책에서 소개하는 싱글톤은 이미 스프링 컨테이너 내에서 객체를 싱글톤으로 관리한다고 알고 있고
프록시 패턴의 경우도 마찬가지.
@ControllerAdvice, @RestControllerAdvice
이 두개의 어노테이션이 프록시패턴을 구현하고 있다고 배웠다.
그러니 책에서 소개하는 팩토리, 옵저버, 커맨드 등의 디자인패턴은 분명
스프링부트에서 사용하는 'MVC패턴'이나
Controller-Service-Repository의 레이어가 위의 역할을 대신해서 동작할 것이다.라고 생각해 왔다.
하지만
코드를 작성하다 보면 분명히 존재한다.
중복된 코드 혹은 비슷한 기능을 가지고 있는데 파라미터만 다르게 구현된 메서드,
Service 코드에서 비즈니스 로직을 전부 구현하다 보니 가독성이 심히 안 좋아진 코드,
단순하게 아키텍처 레이어만 생각하다 보니 하나의 레이어에서만 코드가 과도하게 몰려
비슷한 코드가 쌓이고 또 그렇게 코드 양이 증가하는 부분이 마음에 걸리게 되었다.
만약 그렇다면 패키지 내에 Util 클래스를 분리해서 작성해 볼까 싶었지만
내 멘토가 했던 말이 계속 떠올랐다.
Util 클래스가 특정 Service 혹은 Repository를 주입받아야 된다면 그건 Util 클래스가 아니게 된다.
이것에 관해 고민하다 보니 구현하게 된 게 '디자인패턴'이다.
https://www.youtube.com/watch?v=31wI-c7ExiQ&ab_channel=%ED%95%98%EC%B0%AE%EC%9D%80%EC%98%A4%ED%9B%84
평소 구독하고 보고 있는 개발자 분의 유튜브다.
스프링부트에서 구현할 수 있는 디자인패턴을 생각하다가 위 유튜버를 보고 내 코드에도 적용해 보자 싶었다.

단순하다. 결제승인 로직까지는 동일한 과정을 진행하다가
일반결제와 결제연장의 로직은 PaymentStatus라는 Enum으로 분류된다.
위 부분을 PaymentStrategy 인터페이스를 사용하여 분리해 주었다.
PaymentStatus
@Getter
@RequiredArgsConstructor
public enum PaymentStatus {
CONFIRM("confirmStrategy"),
EXTENDS("extendStrategy");
//value값의 부분은 전략패턴 구현체의 이름을 camelCase로 적어준다.
private final String value;
}
//전략패턴 핸들러 인터페이스
public interface PaymentStrategy {
/**
* 결제 승인 핸들러
*/
void paymentHandler(PaymentTemporal paymentTemporal, PaymentSuccessResponse paymentSuccessResponse);
}
//결제승인 구현체
@Component
@RequiredArgsConstructor
public class ConfirmStrategy implements PaymentStrategy {
...
/**
* 결제 승인 핸들러
* @param paymentTemporal 임시 결제 테이블
* @param paymentSuccessResponse 결제 성공 response
*/
@Override
public void paymentHandler(PaymentTemporal paymentTemporal, PaymentSuccessResponse paymentSuccessResponse) {
...
}
}
//결제 연장 구현체
@Component
@RequiredArgsConstructor
public class ExtendStrategy implements PaymentStrategy {
...
/**
* 연장 결제 승인 핸들러
* @param paymentTemporal 임시 결제 테이블
* @param paymentSuccessResponse 결제 성공 response
*/
@Override
public void paymentHandler(PaymentTemporal paymentTemporal, PaymentSuccessResponse paymentSuccessResponse) {
...
}
}
서비스 코드에선
@Service
@RequiredArgsConstructor
public class PaymentService {
private final Map<String, PaymentStrategy> paymentStrategyMap;
...
/**
* 결제 성공시 상점 테이블 저장 후 리다이렉트
*/
private void paymentSuccessRedirectView(HttpURLConnection connection, PaymentTemporal paymentTemporal, RedirectView redirectView, PaymentStatus paymentStatus) throws IOException {
...
//strategy Payment
PaymentStrategy paymentStrategy = paymentStrategyMap.get(paymentStatus.getValue());
paymentStrategy.paymentHandler(paymentTemporal, paymentResponse);
...
}
...
}
전략패턴을 구현하는 paymentStrategyMap을 구현해 주었다.
이게 재밌는 게 PaymentStrategy를 구현한 구현체를 @Component로 컨테이너에 등록시키고
위와 같은 방식으로 paymentStatus.getValue()를 통해 Componenet를 불러주니 정상적으로 동작하였다.
만약 이 방식이 아니었다면 if/else 문으로 분리해 가면서 어떤 구현체를 가져올지 정해야 하는데
그렇게 if문이 생기게 되면 전략패턴을 사용하는 이유가 없어지게 된다.
개인적으로 전략패턴의 의도를 정확하게 구현하지 않았나 싶다.
만약 결제에 관한 추가적인 로직이 필요하다면 PaymentService에서 코드를 늘려 가독성을 해치는 것이 아닌
새로운 구현체를 만들어 작성해 주면 되고
각각의 결제로직에서 수정할 부분이 있다면 그 결제로직 부분만 수정하면 된다.
(OCP 원칙을 지키게 된 코드가 아닐까 싶다?)
끝으로
굳이 디자인패턴을 공부해 가면서 적용해 보는 것보다 차라리 그 시간에 다른 필요한 공부를 하는 게 낫다고 생각한다.
딱히 어려운 부분이 있는 것도 아니기에 필요에 의해서 구현하게 된다면
충분히 깔끔한 코드를 객체지향적으로 구현하게 되지 않을까 싶다.
물론 단순히 두 개로 분류할 수 있는 코드를 굳이 인터페이스로 나눈 게 성능에 좋다고 보긴 어렵지만
그 미묘한 확장가능성과 성능의 고민하는 경계선에서 나 나름대로 최선의 선택을 했다고 생각한다.
참조.
'Backend > SpringBoot' 카테고리의 다른 글
토스페이먼츠 결제모듈 구현 (0) | 2023.12.01 |
---|---|
네이버 문자(sms) 서비스 구현 (0) | 2023.09.12 |
Swagger Opendoc API (0) | 2023.07.18 |
Jwt + SpringSecurity + Mybatis 구현 (0) | 2023.07.06 |
SpringBoot 동적인(?) 파일 수정 (0) | 2023.05.15 |
- 한권으로끝내기리눅스마스터2급
- vue.js
- java 플레이그라운드
- for
- 짝지어제거하기
- Vue.js3
- mybatis구현
- 함께모으기
- vuex
- 스프링부트
- CompositionAPI
- 책리뷰
- 객체 지도
- script setup
- 프로그래머스
- 정수형으로 변환
- springboot
- 객체지향
- 리눅스마스터2급
- LEVEL2
- pinia
- 알고리즘
- it책 리뷰
- 타임리프
- 객체지향의 사실과 오해
- 다음 큰 숫자
- JWT
- SpringSecurity
- 맥 error
- 토스페이먼츠
- Total
- Today
- Yesterday