오늘도 휴일을 개발하며 느낀 점은
아 이렇게 하면 좀 더 맛깔나게(유지보수와 확장에 유리한) 코드를 작성할 수 있지 않을까?
이렇게 하니까 내가 해당 로직을 수정하려고 할 때 어렵구나 등등
객체지향을 이해하고 잘 사용하는 것이 스프링의 꽃이 아닐까? 그런 생각
그래서 해당 프로젝트에서 아쉬웠던 부분을
내가 생각한대로 한 번 리팩토링하는 시간을 가져보고
왜 일케 했는지 설명을 좀 해보겠다(난 이런 거 설명하는 거 좋아함ㅋㅋ)
일단 원래 코드와 수정 코드를 각각 비교해보겠다
원래 서비스 인터페이스
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개 만으로
메일을 이용하는 서비스가 추가되어도 서비스 메서드 자체는 추가되지 않아도 되는 것!!^___^ㅋㅋㅋ
이게 최고의 코드는 아니겠지만 나에게는 최선의 리팩토링이었음..ㅎㅎ
아주 뿌듯뿌듯한 시간...
'프로젝트 > 토이 프로젝트) 오늘도 휴일' 카테고리의 다른 글
Service 를 테스트해서 오류를 잡아내다! (0) | 2023.07.01 |
---|---|
보안을 위해 닉네임 대신 id 검증으로 코드를 리팩토링 하자! (0) | 2023.06.22 |
NullCheck 를 @Valid 로 손쉽게 ^__^ (0) | 2023.06.22 |
Spring Security ) Spring 3.0 에 맞춰 SecurityConfig 를 수정하자! (1) | 2023.06.14 |
토이 프로젝트 ) 휴일 팬사이트 "오늘도 휴일" : 사이트 소개 및 설명 (0) | 2023.05.26 |