예전에 작성했던 글에 이어지는 내용입니다. 처음부터 계획했던 글은 아니지만, 봄싹 코드가 개선될 수록 전해드리고픈 이야기도 늘어나네요.

지난 번까지의 스프링 이메일 확장에서는 한 가지 단점이 있었습니다. HTML 메시지, 첨부 파일이 있는 메일 등을 작성할 수 없다는 것이었습니다. SimpleMailMessage만을 확장한 구조기 때문이죠. 이와 같은 기능을 사용하려면 MimeMessage를 사용해야 합니다.

그런데 이 MimeMessage는 SimpleMailMessage처럼 간단하게 만들 수 있는 것이 아니라 스프링의 JavaMailSender로부터 가져와야 하는 객체입니다.

처음에는 다음과 같은 구조로 설계를 했었습니다.

MemberService {
    @Autowired JavaMailSender mailSender;
    ...
    public void addMember(Member member) {
        dao.add(member);
        sendMail(member);

    }
    ...
    private void sendMail(Member member){
        MimeMessage message = mailSender.createMimeMessage();
        ConfirmMail mail = new ConfirmMail(message, member);
        mailSender.send(mail.getMessage());
    }
}

이와 비슷한 구조들이 비밀번호를 재전송할 때도 나타났고, 스터디나 모임이 추가/변경 될 때에도 나타났습니다. 무언가 마음 한켠이 불편해지더군요.

(1) MemberService에서 메일 메시지를 만들고 보내는 것까지 관리할 필요가 없다는 판단이 들었습니다. 메일 메시지를 확장한 방법도 맘에 들지 않았습니다. (2) ConfirmMail 내부에서 MimeMessageHelper를 이용해서 MimeMessage에 필요한 정보들을 채워넣었는데 Mail에서는 Mail과 관련된 정보가 담겨야지 Mail 정보를 MimeMessage에 채우는 일은 메일을 보내는 사람의 담당이 아닐까 하는 생각이 들었습니다.

코드가 있어야 할 위치로 옮겨주는 것이 좋겠다는 생각에 설계를 바꾸게 되었고, 그결과 Mail과 관련된 모든 코드(클라이언트와 서비스까지)를 손봐야했습니다.

우선, ConfirmMail은 POJO로 to, from, content, title, isHTML 같은 속성들을 관리하도록 했습니다. MimeMessage를 꾸미는 일은 별도의 서비스 클래스를 하나 만들어서 그쪽으로 옮겼습니다. SendMailService라는 녀석이지요. 위에 표시해둔 (2)번 문제를 해결했습니다. 그리고 MemberService에서 JavaMailSender를 참조하는 대신 SendMailService를 참조하도록 했고, SendMailService.send(new ConfirmMail(member)); 이런식으로 사용할 수 있게하여 (1)번 문제를 해결했습니다. 즉, MimeMessage를 만들고, 그것을 꾸미고, 전송하는 기능을 다른곳(MemberService와 ConfirmMail)에서 가져다가 한 곳(SendMailService)로 모아뒀습니다.

그 결과 스프링 이메일을 확장한 설계도는 다음과 같이 변했습니다.


이제 별도의 메일 전송 서비스가 필요하다면, 그에 해당하는 메일관련 내용만 SpringSproutMail을 확장해서 만들면 되고, 모듈 서비스에서는 다음의 SendMailService를 이용해서 해당 메일을 send()에 넣어주기만 하면 됩니다. HTML 메시지를 보내는 SendMailService 코드는 다음과 같습니다. 이 클래스는 차후에 첨부파일 등을 추가한 메시지를 보낼 때 다시 수정 될 듯 합니다.

@Service
public class SendMailService {
   
    @Autowired JavaMailSender mailSender;

    public void sendMail(SpringSproutMail mail) {
        MimeMessage message = mailSender.createMimeMessage();
        MimeMessageHelper helper = new MimeMessageHelper(message, SpringSprout2System.ENCODING);
        try {
            helper.setTo(mail.getTos());
            helper.setFrom(mail.getFrom());
            helper.setSubject(mail.getSubject());
            helper.setText(mail.getContent(), mail.isHTML());
        } catch (MessagingException e) {
            throw new MailPreparationException(e);
        }
        mailSender.send(message);
    }

}

한가지 의문은 MimeMessageHelper에서는 왜 MailException이라는 RuntimeException 계층 구조를 만들어 놓고도.. 그걸 사용하지 않았느냐 입니다. 코드도 유겐이 작성한거라 뭔가 이유가 있을법한데 말이죠. 뭔가를 복구할 만한 일을 해야 한다고 생각한걸까요? 흠.. 귀찮습니다. 그냥 MailException 계층구조를 사용해서 던져줬으면 try-catch 안해도 될텐데. 일단은 잡아서 MailException의 하위 클래스 중 하나인 MailPreparationException으로 감싸서 다시 던져줬습니다.

이후에도.. 한가지 더 개선한 점이 있는데 그것은 다음에~