본문 바로가기
프로젝트/토이 프로젝트) 오늘도 휴일

좀 더 객체 지향에 걸맞게 메일 서비스를 수정해보자 !

by 휴일이 2023. 6. 13.

 

오늘도 휴일을 개발하며 느낀 점은

아 이렇게 하면 좀 더 맛깔나게(유지보수와 확장에 유리한) 코드를 작성할 수 있지 않을까?

이렇게 하니까 내가 해당 로직을 수정하려고 할 때 어렵구나 등등

객체지향을 이해하고 잘 사용하는 것이 스프링의 꽃이 아닐까? 그런 생각

 

그래서 해당 프로젝트에서 아쉬웠던 부분을

내가 생각한대로 한 번 리팩토링하는 시간을 가져보고

왜 일케 했는지 설명을 좀 해보겠다(난 이런 거 설명하는 거 좋아함ㅋㅋ)

 

 

 

일단 원래 코드와 수정 코드를 각각 비교해보겠다

 

 

원래 서비스 인터페이스

public interface MailService {
    String joinCodeSend(String toEmail);
    MimeMessage joinMailForm(String toEmail);
    String contextJoin(String code);

}

 

수정

public interface EmailService {

    /**
     * 1. 유저의 이메일을 받음
     * 2. 메일 보낼 준비
     * 2-1. 코드 생성
     * 2-2. 템플릿 생성
     * 2-3. 전송
     * 3. return 코드
     */
    String mailSend(Mail mail, String userEmail) throws MessagingException; // 1, 3
    String createCode(); // 2-1
    Message setTemplate(Mail type, String userEmail, String randomCode) throws MessagingException;
    String getContext(String key, String value, String template);
    void sendMail(MimeMessage message); // 2-3

}

 

원래는 셋/아래는 다섯개로 메서드 갯수 자체는 많지만

사실 원래 코드는 자체 사용으로 private 로 숨긴 메서드도 있다

원래 코드는 메일을 보내는 서비스가 늘어날 때마다 메서드가 3개씩 추가되는 것!

 

수정 코드는 서비스를 사용하는 컨트롤러가 늘어나도

Mail Enum 을 각각 다르게 주기만 하면

PasswordMailSend 같은 메서드를 추가하지 않아도 코드를 약간만 수정하면

메서드 단 5개 만으로 서비스 사용이 가능하다!

 

 

 

원래 서비스 구현 클래스

@Service
@PropertySource("classpath:application.yml")
public class MailServiceImpl implements MailService {

    private final JavaMailSender mailSender;
    private final SpringTemplateEngine templateEngine; //타임리프를 사용하기 위한 객체
    private MimeMessage message;
    @Value("${mail.id}")
    private String fromEmail;

    private String title;
    private String randomCode;
    private String randomPwd;
    private final String charset = "UTF-8";
    private final String html = "html";

    public MailServiceImpl(JavaMailSender mailSender, SpringTemplateEngine templateEngine) {
        this.mailSender = mailSender;
        this.templateEngine = templateEngine;
    }

    @Override
    public String joinCodeSend(String toEmail) {
        createCode();
        title = "오늘도 휴일 * 가입 코드";

        try {
            message = mailSender.createMimeMessage();
            message.addRecipients(RecipientType.TO, toEmail);
            message.setSubject(title);
            message.setFrom(fromEmail);
            message.setText(contextJoin(randomCode), charset, html);
            mailSender.send(message);

        } catch (MessagingException e) {
            return "전송 오류";
        }

        return randomCode;
    }

    @Override
    public MimeMessage joinMailForm(String toEmail) {
        return null;
    }

    @Override
    public String contextJoin(String code) {
        Context context = setContext("code", code);
        return templateEngine.process("email/joinMailForm", context);
    }

    public void createCode() {
        int min = 1000;
        int max = 9999;
        StringBuffer buffer = new StringBuffer();
        Random random = new Random();
        int code = random.nextInt(max-min)+min;

        randomCode = buffer.append(code).toString();
    }

    private Context setContext(String key, String value) {
        Context context = new Context();
        context.setVariable(key, value);
        return context;
    }
}

 

타임리프 템플릿에 넣을 컨텍스트를 넣는 private 메서드와 코드 생성 메서드를 제외하고는

전부 join 이 직접적으로 선언되어 있어서

만약 비밀번호 변경 로직을 넣고 싶을 경우에는

contextPassword

PwdChangeMailForm 등... 코드를 계속 추가해야한다

 

 

 

하지만!

리팩토링한 코드는?

 

@Service
@PropertySource("classpath:application.yml")
public class EmailServiceImpl implements EmailService {

    private final JavaMailSender mailSender;
    private final SpringTemplateEngine templateEngine;

    private MimeMessage message;
    @Value("${mail.id}")
    private String fromEmail;
    private String title;
    private String template;

    private String randomCode = "";
    private final String charset = "UTF-8";
    private final String html = "html";

    public EmailServiceImpl(JavaMailSender javaMailSender, SpringTemplateEngine templateEngine) {
        this.mailSender = javaMailSender;
        this.templateEngine = templateEngine;
    }

    @Override
    public String mailSend(Mail mail, String userEmail) throws MessagingException {
        String result = "";
        if (mail.equals(Mail.JOIN)) {
            randomCode = createCode();
            result = randomCode;
        }
        MimeMessage template = setTemplate(mail, userEmail, randomCode);
        sendMail(template);
        return result;
    }

    @Override
    public String createCode() {
        String values = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
        int codeLength = 5;

        Random random = new Random();
        StringBuilder builder = new StringBuilder();

        /**
         * values 길이 안에서 랜덤한 숫자를 가져오고
         * 랜덤 숫자를 values 의 인덱스로 가져와서 builder 로 String 에 추가함.
         */
        for (int i = 0; i < codeLength; i++) {
            int randomIndex = random.nextInt(values.length());
            builder.append(values.charAt(randomIndex));
        }
        return builder.toString();
    }
    @Override
    public MimeMessage setTemplate(Mail type, String userEmail, String randomCode) throws MessagingException {

        String key = "";
        String value = "";

        if (type.equals(Mail.JOIN)) {
            title = "오늘도 휴일 * 가입 코드 전송";
            template = "email/joinMailForm";
            // randomCode , template , key=randomCode , value = code
            key = "randomCode";
            value = randomCode;
        }

        message = mailSender.createMimeMessage();
        message.addRecipients(RecipientType.TO, userEmail);
        message.setSubject(title);
        message.setFrom(fromEmail);
        message.setText(getContext(key, value, template), charset, html);
        return message;
    }

    @Override
    public String getContext(String key, String value, String template) {
        Context context = setContext(key, value);
        return templateEngine.process(template, context);
    }

    @Override
    public void sendMail(MimeMessage message) {
        mailSender.send(message);
    }

    private Context setContext(String key, String value) {
        Context context = new Context();
        context.setVariable(key, value);
        return context;
    }
}

 

보면 직접 join이 선언되어 있는 게 아니라

Enum을 새로 만들어서 어떤 이넘 값이 들어오느냐에 따라

로직이 조금씩 다르게 작동한다 ㅎㅎ

그래서 요청할 때

" 난 너네가 어떻게 작동하는지는 모르겠지만 걍 회원가입 전송 메일이나 보내줘잉~ " 하면

얘네가 알아서 들어오는 요청에 맞는 메일을 보내는 것이다

 

참고로 Enum 은

public enum Mail {
    JOIN
}

걍 이렇게 해놓음 (다른 메일 서비스가 추가된다면 해당 이름을 추가하면 되겠죠?)

 

 

    @Override
    public String mailSend(Mail mail, String userEmail) throws MessagingException {
        String result = "";
        if (mail.equals(Mail.JOIN)) {
            randomCode = createCode();
            result = randomCode;
        }
        MimeMessage template = setTemplate(mail, userEmail, randomCode);
        sendMail(template);
        return result;
    }

보면 Mail.JOIN 이 들어온다면

randomCode를 생성 후 템플릿을 생성한다

코드가 필요없는 메일 서비스가 있다면 "" 값이 들어가겠지?

 

결과값은 Mail.이넘이 어떻게 들어오느냐에 따라 달라질 수 있는데

가입같은 경우는 randomCode 를 리턴해야하기 때문에 result 에 code 를 셋팅해준다

 

 

    @Override
    public MimeMessage setTemplate(Mail type, String userEmail, String randomCode) throws MessagingException {

        String key = "";
        String value = "";

        if (type.equals(Mail.JOIN)) {
            title = "오늘도 휴일 * 가입 코드 전송";
            template = "email/joinMailForm";
            // randomCode , template , key=randomCode , value = code
            key = "randomCode";
            value = randomCode;
        }

        message = mailSender.createMimeMessage();
        message.addRecipients(RecipientType.TO, userEmail);
        message.setSubject(title);
        message.setFrom(fromEmail);
        message.setText(getContext(key, value, template), charset, html);
        return message;
    }

템플릿은 Mail 타입이 어떻게 들어오느냐에 따라 다른 제목과 템플릿을 사용하도록 셋팅하고 컨텍스트에 넣을 랜덤코드값도 셋팅해준다!

메세지 셋팅 로직 자체는 동일하기때문에 돌려써도 된다

 

 

    @Override
    public String getContext(String key, String value, String template) {
        Context context = setContext(key, value);
        return templateEngine.process(template, context);
    }

메일에 넣을 context 값을 셋팅한다

쉽게 얘기하면 서버에서 메일에 어떤 값을 보내고 싶을 때...이렇게 컨텍스트로 셋팅해주면 된당ㅎ_ㅎㅋㅋ

뭐 컨텍스트가 없는 메일이 필요하다면 아무 값도 설정되지 않을 것이다

 

    @Override
    public void sendMail(MimeMessage message) {
        mailSender.send(message);
    }

그리고 메일을 전송해준다

 

 

 

    // TODO Email Code 전송
    @ResponseBody
    @PostMapping("/emailSend")
    public ResponseEntity<String> emailSend(@RequestParam String email) {
        JsonObject jsonObject = new JsonObject();
        try {
            randomCode = emailService.mailSend(Mail.JOIN, email);
        } catch (MessagingException e) {
            e.printStackTrace();
            jsonObject.addProperty("error", "SEND_ERROR");
            webService.badResponseEntity(jsonObject);
        }
        jsonObject.addProperty("body", "SEND_OK");
        return webService.okResponse(jsonObject);
    }

그러면 컨트롤러는 Mail.JOIN 값을 보내어 내가 어디에서 요청했는지 알려주기만 하면

randomCode 값을 받을 수 있다

 

 

결론적으로 메서드 단 5개 만으로

메일을 이용하는 서비스가 추가되어도 서비스 메서드 자체는 추가되지 않아도 되는 것!!^___^ㅋㅋㅋ

이게 최고의 코드는 아니겠지만 나에게는 최선의 리팩토링이었음..ㅎㅎ

아주 뿌듯뿌듯한 시간...

728x90