기획자로부터 새로운 기능 요청을 받을 때, 기존 기능과 비슷하지만 세부적으로 다른 요구사항이 있을 때가 있다. 이럴 때 API를 새롭게 만들기 보단 기존 컨트롤러를 재사용하면서도 새로운 기능을 유연하게 추가하고 싶었다.
문제 상황
아래 예시 상황과 코드는 실제 회사의 기획과 코드가 아니며 포스팅을 위해 유사하게 만든 것 뿐이다.
기존에는 한국에서만 진행하던 이벤트를 글로벌에서도 운영하려는 요구사항이 생겼다. 해당 이벤트의 기본적인 기능은 동일하다.
- 특정 기간 동안 특정 아이템을 할인된 가격에 구매 가능하다.
- 이 때 아이템 목록은 기간마다 변경된다.
- 구매 시 보상을 지급하고, 사용자에게 알림을 전송한다.
그러나 보상 지급 방식이 다르다. 한국 서비스의 경우 아이템을 3번 구매해야 보상을 지급한다. 글로벌 서비스의 경우 아이템을 구매할 때마다 보상 지급하는데 최대 3번까지 지급된다.
참고로 한국과 글로벌 서비스 모두 하나의 프로젝트이며, 환경에 따라 타임존이 다르게 설정된다. 처음에는 컴파일 타임에 동작을 분기하도록 구현했으며, 컨트롤러에서 타임존에 따라 서로 다른 서비스를 호출하는 방식이었다.
하지만 이 방식은 두 가지 문제점이 있다.
첫 번째, 컨트롤러에 비즈니스 로직이 존재하게 된다. 위 코드를 보면, "한국에서는 A 로직, 글로벌에서는 B 로직 수행"이라는 규칙이 컨트롤러에 직접 포함되고 있다. 컨트롤러가 환경별 비즈니스 로직을 직접 판단하면서, 서비스 계층과의 책임이 불명확해졌다.
두 번째, 중복된 로직이 발생한다.
두 서비스 모두 보상 지급 전 공통적으로 수행하는 로직이 있다. 이벤트 정보를 데이터베이스에서 조회하고 이벤트가 진행 중인지 검증해야 하며, JSON 형태로 저장되어 있던 보상 정보를 자바 객체로 파싱해야 한다.
전략 패턴 적용
이 문제를 해결하기 위해 전략 패턴(Strategy Pattern)을 적용했다. 전략 패턴은 실행(런타임) 중에 알고리즘을 변경할 수 있도록 하는 디자인 패턴이다. 즉, 보상 지급 방식이 다르므로 이를 전략으로 분리하여 유연하게 교체할 수 있도록 했다.
먼저, 전략(보상 지급 방식)을 추상화한 인터페이스를 만든다. 그리고 각 전략의 구현체들이 handle 메서드를 통해 자신이 실행할 수 있는 환경인지 판단하고, 해당 로직을 수행하도록 한다.
이 전략들은 컨텍스트에서 관리된다. Spring의 기능을 활용하면 같은 타입의 빈을 List로 한 번에 주입받을 수 있다. 따라서 빈으로 등록된 EventCountryService의 구현체를 List로 한 번에 주입받는다. 그리고 공통 로직을 수행한 후 적절한 EventCountryService 구현체를 선택하여 실행한다. 이렇게 하면 중복된 로직을 제거하고, 보상 지급 방식만 전략으로 분리할 수 있다.
이제 컨트롤러는 컨텍스트(EventService)만 바라보면 된다. 즉, 컨트롤러에서 비즈니스 로직이 제거되고, 계층 간 책임이 명확해진다. 새로운 보상 지급 방식이 추가될 경우에도 기존 코드를 수정할 필요 없이 확장할 수 있다.
참고
'프로젝트 > 트러블슈팅' 카테고리의 다른 글
ObjectMapper는 스프링 빈으로 주입받기 (0) | 2024.11.24 |
---|---|
@RedisHash 주의해서 사용하기 (2) | 2024.11.10 |
[트러블 슈팅] 인덱스 컨디션 푸시다운, 인덱스를 이용한 정렬, 커버링 인덱스로 슬로우 쿼리 튜닝하기 (1) | 2024.02.13 |
[트러블 슈팅] n + 1 문제를 IN절로 해결하기 (0) | 2024.02.12 |
[트러블 슈팅] 리팩토링을 통한 복잡했던 모듈 구조를 단순화 (0) | 2024.02.06 |