ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [알림 서비스] 알림 서비스 구조 개선 - 알림 전송 외부 라이브러리 의존성 분리 리팩토링
    STOVE DEVCAMP 3기/알림 서비스 2023. 3. 10. 03:08

    📌 개선 대상

    • 알림 요청 처리 구조

     

    📌 기존 처리 구조의 문제점 및 보완할 점 파악

    Q. 알림 전송 수단이 추가되었을 때 확장 가능한 구조인가?

    • 알림 요청 처리 비즈니스 로직에 알림 전송 로직이 포함되어 있어 알림 전송 외부 라이브러리와의 의존성이 높은 구조임
    • 알림 전송 수단 추가 시 비즈니스 로직 수정 불가피함

     

    <코드 상세>

    // 알림생성API 요청을 처리하는 비즈니스 로직
    @Service
    public class CreateNotificationService implements CreateNotificationUseCase {
    
        ...
         
        @Override
        public void createNotification(CreateNotificationCommand command) {
            saveNotification.saveAll(); // 알림 데이터 저장 (알림 목록 조회를 위함)
            getDevicePort.findAllByUserIdsWithOptTrue(); // 알림 수신 유저의 FCM 디바이스 토큰 목록 조회
            sendNotificationPort.sendPushNotification(); // 푸시 알림 전송 (FCM에 전송할 알림 메시지 저장 수행)
        }
    }
     
    • 알림 전송 수단이 추가될 경우, 위의 비즈니스 로직의 수정이 필수임
    • 이는 클린 아키텍처의 목적을 지키지 못한 형태임
    • 알림 요청을 처리하는 비즈니스 로직과 알림 전송 외부 라이브러리와의 결합도가 높음

     

    📌 개선 구조

    • 알림 요청 처리 비즈니스 로직에 이벤트 발행/구독 패턴 적용
    • 역할과 구현을 분리하고자 Port & Adapter 패턴 적용 (현재 알림 서비스 아키텍처 구조를 따름)

     

    <코드 상세>

    알림 요청 처리하는 비즈니스 로직

    // 알림생성API 요청을 처리하는 비즈니스 로직
    @Service
    public class CreateNotificationService implements CreateNotificationUseCase {
    
        private final SaveNotificationPort saveNotificationPort;
        private final PublishEventPort publishEventPort;
    
        ...
         
        @Override
        public void createNotification(CreateNotificationCommand command) {
            saveNotificationPort.saveAll(command); // 알림 데이터 저장 (알림 목록 조회를 위함)
            publishEventPort.createNotification(command); // 알림생성이벤트 발행
        }
    }
     

    이벤트 발행 어댑터

    @Component
    public class ApplicationEventAdapter implements PublishEventPort {
     
        private final ApplicationEventPublish eventPublisher; // Spring의 ApplicationEvent 라이브러리 사용
        private final SaveEventPort saveEventPort;  
     
        ...
     
        @Override
        public void createNotification(CreateNotificationCommand command) {
            ...
            NotificationCreationEvent savedEvent = (NotificationCreationEvent) saveEventPort.save(event); // 알림생성이벤트 저장
            eventPublisher.publishEvent(savedEvent); // 알림생성이벤트 발행
        }
    }

    이벤트 핸들러

    @Component
    public class FcmEventHandler {
     
        private final GetDevicePort getDevicePort;  
        private final SaveMessagePort saveMessagePort;
     
        ...
     
        @EventListener
        public void saveMessage(NotificationCreationEvent event) {
            List<Device> devices = getDevices(event.getReceivers()); // 알림 수신 디바이스 목록 조회
            List<NotificationMessage> messages = getNotificationMessages(event, devices); // 알림메시지 생성
            saveMessagePort.saveMessageAll(messages); // 알림메시지 저장
        }
    }

    상세 설명

    • 동작 방식
      • 알림생성API 호출 시 알림 요청을 처리하는 비즈니스 로직에서는 알림 데이터를 저장하고, '알림생성이벤트' 발행을 호출함
      • '알림생성이벤트' 발행을 처리하는 어댑터(구현체)에서는 해당 이벤트를 저장하고, 이벤트를 발행함
      • 해당 이벤트 핸들러인 FcmEventHandler가 알림 수신 디바이스별 알림메시지 생성 후 저장함 
    • 개선 사항
      • 이벤트 발행/구독 패턴, Port & Adapter 패턴 적용
      • 이벤트 발행 방식이 달라질 경우, 이벤트 발행 어댑터를 추가하여 확장 가능함
        • 현재는 SpringApplicationEvent를 사용하지만, 카프카 프로듀서 등으로 교체 가능함
      • 알림 전송 수단이 추가될 경우, EventHandler 클래스를 추가하여 확장 가능함
        • 전달받은 이벤트를 전송 수단에 맞게 처리하도록 구현하면 되므로, 기존의 비즈니스 로직 수정이 필요하지 않음
      • 알림 요청을 처리하는 비즈니스 로직과 외부 라이브러리와의 결합도를 낮춤
        • DB, SpringApplicationEvent 라이브러리 등
      • 추가적으로,
        • 알림과 이벤트가 저장되는 공간이 변경되거나 추가되는 경우를 대비해 save() 메서드를 인터페이스로 추상화함
        • 현재는 이벤트 발행 및 이벤트 핸들러의 처리가 하나의 스레드에서 수행되지만, 이후 이벤트 핸들러의 작업을 비동기로 실행하여 별도의 스레드에서 수행되도록 변경 가능함

     

    구성 그림

    댓글

Designed by Tistory.