전략 패턴 도입 계기

이제 우리가 지금 당장 사용할 수 있는 메일 발송 서비스는 2가지가 있다.

  • Gmail-SMTP
  • AWS SES

현재까지 개발된 상황을 보면 Gmail-smtp를 사용하면 비동기가 필수적이고, 
AWS SES를 사용하면 동기만으로도 충분히 빠른 속도를 보인다. (개수가 10000개 내외일때)

이런 상황에서 앞으로 겪을 수 있는 상황을 고려했을 때,
사용하는 메일 서비스를 바꾸는 상황이 올 수도 있을 거란 판단이 들었다.

  • 프리티어 이용을 위한 AWS 계정 변경 시, SES 프로덕션 모드 변경 재신청
  • 이용자 수가 500명 이하인데 AWS SES 비용이 많이 청구되는 경우

그래서 메일 서비스 발송 방식을 쉽게 추가 및 변경하기 위해서 전략 패턴을 도입하기로 결정했다.


전략 패턴 도입

1. 공통 행위 추상화

먼저 추상화할 공통 행위를 꼽았다.

  1. 메일 내용 구성 및 발송
  2. RateLimiter로 초당 요청량 제어

선별한 공통 행위를 다음과 같이 인터페이스로 추상화하였다.

public interface MailSenderStrategy {
    void sendQuizMail(MailDto mailDto);

    boolean tryConsume(Long num);
}

 

2. 각 서비스 별 구현체 생성

public class JavaMailSenderStrategy implements MailSenderStrategy{
    private final JavaMailService javaMailService;
    private final Bucket bucket; //Gmail 초당 요청량에 맞게 설정한 Bucket

    @Override
    public void sendQuizMail(MailDto mailDto) {
        javaMailService.sendQuizEmail(mailDto.getSubscription(), mailDto.getQuiz());  // 커스텀 메서드로 정의
    }

    @Override
    public boolean tryConsume(Long num){
        return bucket.tryConsume(num);
    }
}
public class SesMailSenderStrategy implements MailSenderStrategy{

    private final SesMailService sesMailService;
    private final Bucket bucket; //SES 초당 요청량에 맞춘 Bucket

    @Override
    public void sendQuizMail(MailDto mailDto) {
        sesMailService.sendQuizEmail(mailDto.getSubscription(), mailDto.getQuiz());
    }

    @Override
    public boolean tryConsume(Long num){
        return bucket.tryConsume(num);
    }
}

 

3. Context 클래스를 통해 적용

strategyMap에 있는 메일 전략이 맞는지 아닌지 확인한 후,
전략키에 해당하는 구현체의 메서드를 호출한다.

public class MailSenderContext {
    private final Map<String, MailSenderStrategy> strategyMap;

    public void send(MailDto dto, String strategyKey) {
        MailSenderStrategy strategy = getValidStrategy(strategyKey);
        strategy.sendQuizMail(dto);
    }

    public boolean tryConsume(String strategyKey, Long num) {
        MailSenderStrategy strategy = getValidStrategy(strategyKey);
        return strategy.tryConsume(num);
    }

    private MailSenderStrategy getValidStrategy(String strategyKey) {
        MailSenderStrategy strategy = strategyMap.get(strategyKey);
        if (strategy == null) {
            throw new IllegalArgumentException("메일 전략이 존재하지 않습니다: " + strategyKey);
        }
        return strategy;
    }
}

 

처음에는 메일 발송 부분만 추상화하였었는데
튜터님께서 이왕 전략 패턴을 쓰는거 RateLimiter 관련된 부분까지 함께 관리하면 더 좋을 것 같다고 하셔서
그 부분을 추가했다.

일관된 발송 주체(주소)를 유지하기 위해서 런타임 내에서 발송 방식이 변경되도록 구현하지는 않았다.
(SES로 발송이 실패하면 Gmail로 보내도록 시도하는 FallBack 구조)

+ Recent posts