Igloo-Club / Igloo-Club-BE

1 stars 1 forks source link

[refactor ] 스프링 이벤트 적용 (미완) #81

Closed ryuseunghan closed 6 months ago

ryuseunghan commented 6 months ago

🔥 Related Issues

💜 작업 내용

✅ PR Point

reference

[Spring Event + Async + AOP 적용해보기](https://supawer0728.github.io/2018/03/24/spring-event/)

[[Spring] 스프링에서 이벤트의 발행과 구독 방법과 주의사항, 이벤트 사용의 장/단점과 사용 예시](https://mangkyu.tistory.com/292)

요구사항


기존 코드


/**
     * 눈길을 보내는 api입니다
     * member에게 recommend status의 눈길을 SENT로 수정하며
     * receiver에게 status가 RECEIVED인 눈길을 생성합니다
     *
     * @param nungilId 눈길 id
     */
    @Transactional
    public void sendNungil(Member member, Long nungilId){
                .....
        //이미 눈길을 보냈을 시 중단
                .....
        //사용자의 눈길 상태를 SENT, 만료일을 일주일 뒤로 설정
                .....
        //눈길 받는 사용자 눈길 객체 생성 및 저장
                .....
        // 눈길 받은 사용자에게 알림 전송
        String phoneNumber = receiver.getPhoneNumber();
        String url = BASE_URL + "/receiveddetailpage/" + newNungil.getId();
        String text = "[눈길] 새로운 눈길이 도착했어요. 얼른 확인해보세요!\n" + url;

        coolSMS.send(phoneNumber, text);
    }
    /**
     * 인증번호를 생성하고, 주어진 회사 이메일로 발송하는 메서드이다.
     * @param email 회사 이메일
     */
    public void sendAuthEmail(String email, Member member) {

        // 사용이 불가능한 회사 도메인인지 확인한다.
                ...
        // 이미 가입된 이메일인지 확인한다.
                ...
        // code: 알파벳 대문자와 숫자로 구성된 랜덤 문자열의 인증번호
                ...
        // redis에 이메일을 키로 하여 인증번호를 저장한다.
        ...

        // 이메일을 발송한다.
        String subject = "[눈길] 회사 인증 메일입니다.";
        String filename = "company-authentication.html";

        emailSender.send(EmailMessage.create(email, subject, filename).addContext("code", code));
    }

리팩토링


matchNungil

    @Transactional
    public void matchNungil(Long nungilId){

        //사용자의 눈길을 MATCHED 상태로 변경
                .....

        //수취자의 눈길 조회 후 MATCHED 상태로 변경
                .....

        // 서로에 대한 Acquaintance 객체 생성 및 저장
                .....

        // 매칭된 사용자 간에 겹치는 시간, 마커를 조회하여 저장
                .....

        // 매칭된 사용자들을 채팅방에 초대
                .....

        // 눈길 보낸 사용자에게 알림 전송
//        String phoneNumber = sender.getPhoneNumber();
//        String url = BASE_URL + "/finishmatch/" + sentNungil.getId();
//        String text = "[눈길] 축하해요! 서로의 눈길이 닿았어요. 채팅방을 통해 두 분의 첫만남 약속을 잡아보세요.\n" + url;
        this.sendMatchSMS(sender, sentNungil);
//        coolSMS.send(phoneNumber, text);
    }
    public void sendMatchSMS(Member sender, Nungil sentNungil){
        publisher.publishEvent(new NungilMatchedEvent(sender, sentNungil));
    }

sendNungil

    @Transactional
    public void sendNungil(Member member, Long nungilId){
        Nungil nungil = nungilRepository.findById(nungilId)
                .orElseThrow(()->new GeneralException(NungilErrorResult.NUNGIL_NOT_FOUND));
        Member receiver = nungil.getReceiver();
        Acquaintance memberAcquaintance = getAcquaintance(member, receiver);
        Acquaintance receiverAcquaintance = getAcquaintance(receiver, member);

        if(!nungil.getStatus().equals(NungilStatus.RECOMMENDED)){
            throw new GeneralException(NungilErrorResult.NUNGIL_WRONG_STATUS);
        }

        //이미 눈길을 보냈을 시 중단
        List<Nungil> receiverNungilList = nungilRepository.findAllByMemberAndReceiverAndStatus(receiver, member, NungilStatus.RECEIVED);
        if(receiverNungilList.size() > 0){
            return ;
        }

        //사용자의 눈길 상태를 SENT, 만료일을 일주일 뒤로 설정
        nungil.setStatus(NungilStatus.SENT);
        nungil.setExpiredAt7DaysAfter();
        memberAcquaintance.update(NungilStatus.SENT);

        //눈길 받는 사용자 눈길 객체 생성 및 저장
        Nungil newNungil = Nungil.create(receiver, member, NungilStatus.RECEIVED);
        newNungil.setExpiredAt7DaysAfter();
        receiverAcquaintance.update(NungilStatus.RECEIVED);
        acquaintanceRepository.save(receiverAcquaintance);
        nungilRepository.save(newNungil);

        // 눈길 받은 사용자에게 알림 전송
//        String phoneNumber = receiver.getPhoneNumber();
//        String url = BASE_URL + "/receiveddetailpage/" + newNungil.getId();
//        String text = "[눈길] 새로운 눈길이 도착했어요. 얼른 확인해보세요!\n" + url;
//
//        coolSMS.send(phoneNumber, text);
        this.sendNungilSMS(receiver, newNungil);
    }
    public void sendNungilSMS(Member sender, Nungil sentNungil){
        publisher.publishEvent(new NungilSentEvent(sender, sentNungil));
    }

NungilMatchedEvent

@Getter
@Validated
@RequiredArgsConstructor
public class NungilMatchedEvent {

    @NotNull
    private final Member sender;

    @NotNull
    private final Nungil sentNungil;
}

NungilSentEvent

@Getter
@Validated
@RequiredArgsConstructor
public class NungilSentEvent {

    @NotNull
    private final Member sender;

    @NotNull
    private final Nungil sentNungil;
}

NungilMatchedEventListener

@Component
@RequiredArgsConstructor
public class NungilEventListener {
    private final CoolSMS coolSMS;

    private static final String BASE_URL = "https://nungil.com";

    @Async
    @EventListener
    public void matchNungilListen(NungilMatchedEvent nungilMatchedEvent){
        // 눈길 보낸 사용자에게 알림 전송
        String phoneNumber = nungilMatchedEvent.getSender().getPhoneNumber();
        String url = BASE_URL + "/finishmatch/" + nungilMatchedEvent.getSentNungil().getId();
        String text = "[눈길] 축하해요! 서로의 눈길이 닿았어요. 채팅방을 통해 두 분의 첫만남 약속을 잡아보세요.\n" + url;

        coolSMS.send(phoneNumber, text);
    }

    @Async
    @EventListener
    public void sentNungilListen(NungilSentEvent nungilSentEvent){
        // 눈길 보낸 사용자에게 알림 전송
        String phoneNumber = nungilSentEvent.getSender().getPhoneNumber();
        String url = BASE_URL + "/receiveddetailpage/" + nungilSentEvent.getSentNungil().getId();
        String text = "[눈길] 새로운 눈길이 도착했어요. 얼른 확인해보세요!\n" + url;

        coolSMS.send(phoneNumber, text);
    }
}

리뷰 이후 수정 사항


  1. NungilEventListener 의 각 메소드가 디미티의 법칙을 지키지 못하고 있다는 리뷰를 받았다.

    • matchNungilListensentNungilListen 각 event 객체에 대한 정보를 너무 많이 알고 있어 객체지향스럽지 못하다는 의견을 받아 각 event 클래스에 메서드를 만들어 디미터의 법칙을 준수하고자 수정했다.
    public class NungilSentEvent {
    
        @NotNull
        private final Member sender;
    
        @NotNull
        private final Nungil sentNungil;
    
        public String getSenderPhoneNumber(){
            return sender.getPhoneNumber();
        }
    }
    public class NungilMatchedEvent {
    
        @NotNull
        private final Member sender;
    
        @NotNull
        private final Nungil sentNungil;
    
        public String getMatcherPhoneNumber(){
            return sender.getPhoneNumber();
        }
    }
  2. @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT) 적용

    NungilService의 sendNungilSMS 메서드의 트랜잭션이 성공적으로 커밋되어야만 해당 listner의 메서드를 실행시킨다. 이렇게 함으로써 sendNungilSMS 메서드가 정상적인 처리에 실패해서 롤백이 발생할 경우 외부 API는 호출되는 것을 방지할 수 있다.

    @Component
    @RequiredArgsConstructor
    public class NungilEventListener {
        private final CoolSMS coolSMS;
    
        private static final String BASE_URL = "https://nungil.com";
    
        @Async
        @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
        public void matchNungilListen(NungilMatchedEvent nungilMatchedEvent){
            // 눈길 보낸 사용자에게 알림 전송
            String phoneNumber = nungilMatchedEvent.getMatcherPhoneNumber();
            String url = BASE_URL + "/finishmatch/" + nungilMatchedEvent.getSentNungil().getId();
            String text = "[눈길] 축하해요! 서로의 눈길이 닿았어요. 채팅방을 통해 두 분의 첫만남 약속을 잡아보세요.\n" + url;
    
            coolSMS.send(phoneNumber, text);
        }
    
        @Async
        @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
        public void sentNungilListen(NungilSentEvent nungilSentEvent){
            // 눈길 보낸 사용자에게 알림 전송
            String phoneNumber = nungilSentEvent.getSenderPhoneNumber();
            String url = BASE_URL + "/receiveddetailpage/" + nungilSentEvent.getSentNungil().getId();
            String text = "[눈길] 새로운 눈길이 도착했어요. 얼른 확인해보세요!\n" + url;
    
            coolSMS.send(phoneNumber, text);
        }
    }
    

결론