Skip to content

refactor: 알림 발송 로직 통합 및 알림 발송 DSL 도입#131

Merged
soonduck-dreams merged 5 commits into
developfrom
refactor/noti
Aug 13, 2025
Merged

refactor: 알림 발송 로직 통합 및 알림 발송 DSL 도입#131
soonduck-dreams merged 5 commits into
developfrom
refactor/noti

Conversation

@soonduck-dreams

Copy link
Copy Markdown
Contributor

📝 요약(Summary)

이번 PR은 알림 발송 로직을 DSL(Domain-Specific Language) 기반으로 리팩터링해서
다른 도메인에서 알림을 훨씬 쉽고 유연하게 보낼 수 있게 만든 게 핵심이에요.


1. 알림의 종류: 행동 알림 vs 시스템 알림

  • 알림은 크게 두 가지로 나눌 수 있다고 생각해요.

    1. 행동 알림
      → 특정 사용자의 행동(좋아요, 댓글, 제안 등)에 의해 다른 사용자에게 전달되는 알림
      → 예: “철수님이 내 컬렉션에 댓글을 남겼어요.”
    2. 시스템 알림
      → 서비스 운영이나 시스템 상태 변화에 따라 전송되는 알림
      → 예: “새로운 기능이 추가되었어요”, “점검 안내”
  • 이번에 만든 NotifyActionDsl은 이 중에서 행동 알림에 특화된 DSL이에요.
    → 행동 주체(Actor), 대상(Target), 행동 종류(ActionKind)를 자연스럽게 지정할 수 있게 설계했어요.


2. DSL(Domain-Specific Language) 소개

  • DSL은 특정 도메인(여기서는 “알림 발송”)에 특화된 작은 언어예요.

  • 범용 프로그래밍 언어보다 그 도메인의 작업을 간결하고 직관적으로 표현할 수 있게 설계돼요.

  • 이번 PR에서 DSL은 행동 알림을
    by(누가) → on(무엇에 대해) → did(무슨 행동을) → 옵션 설정 → to(누구에게)
    순서로 자연스럽게 작성할 수 있게 해줘요.

  • 예:

    notifyAction
        .by(Actor.of(userId, user.getNickname()))
        .on(Target.collection(collectionId))
        .did(ActionKind.COMMENT)
        .comment(req.content())
        .to(collection.getUser().getId());
  • 이렇게 쓰면 읽기 쉽고, IDE 자동완성 도움으로 실수를 줄일 수 있어요.
    그리고 알림 종류별로 메서드를 따로 외울 필요도 없어요.


3. 주요 변화

  1. PushNotificationService 제거

    • 기존에는 알림 종류별로 sendCollectionLikeNotification, sendCollectionCommentNotification처럼 메서드가 여러 개 있었어요.
    • 이제 이런 메서드 없이, DSL로 필요한 데이터만 지정하면 돼요.
  2. 다른 도메인 서비스에서 DSL 사용

    • BirdIdSuggestion, CollectionComment, CollectionLike 서비스에서 모두
      PushNotificationService 의존성을 없애고 NotifyActionDsl을 사용하게 바뀌었어요.
    • 덕분에 호출부가 간결하고 읽기 쉬워졌어요.

4. NotificationPublisher: 단 하나의 메서드로 공통 파이프라인 처리

이제 모든 알림은 NotificationPublisher단 하나의 메서드에서 처리돼요.
알림 저장과 푸시 발송을 같은 흐름에서 공통으로 수행하죠.

@Service
@RequiredArgsConstructor
public class NotificationPublisher {

    private final NotificationRenderer renderer;
    private final InAppNotificationWriter inAppWriter;
    private final NotificationRepository notificationRepository;
    private final PushGateway pushGateway;
    private final DeepLinkResolver deepLinkResolver;

    @Transactional
    public void push(NotificationPayload payload, Target target) {
        // 1) 렌더링
        RenderedNotification r = renderer.render(payload);

        // 2) 딥링크
        String deepLink = deepLinkResolver.resolve(target);

        // 3) 인앱 저장
        inAppWriter.save(payload, r, deepLink);

        // 4) 배지 카운트
        int unread = notificationRepository.countUnreadByUserId(payload.recipientId()).intValue();

        // 5) 푸시 전송
        PushMessageCommand cmd = PushMessageCommand.createPushMessageCommand(
                r.title(), r.body(), payload.type().name(), payload.relatedId(), deepLink, unread
        );

        // 실제 디바이스/설정 필터링 + FCM 전송
        pushGateway.sendToUser(payload.recipientId(), payload.type(), cmd);
    }
}

📌 포인트

  • 어떤 알림 타입이든 push() 한 곳에서
    ① 메시지 렌더링 → ② 딥링크 생성 → ③ 인앱 저장 → ④ 배지 카운트 → ⑤ 푸시 전송
    이 순서로 처리돼요.
  • 새로운 알림 타입을 추가해도, 발송 로직은 이 메서드를 그대로 재사용하면 돼요.

5. 기대 효과

  • 호출이 간단해요: DSL 체인만 알면 바로 발송할 수 있어요.
  • 유지보수가 쉬워요: 새로운 알림은 DSL 설정만 추가하면 돼요.
  • 코드가 읽기 좋아요: 자연어처럼 읽히고, 발송 흐름이 한눈에 보여요.
  • 중복이 줄어요: 저장/푸시 로직이 한 곳으로 모였어요.

✅ PR Checklist

PR이 다음 요구 사항을 충족하는지 확인하세요.

  • [v] PR 제목을 커밋 메시지 컨벤션에 맞게 작성했습니다.

토큰뿐 아니라 사용자 디바이스 정보를 등록한다는 개념으로 확장
기존 서비스 클래스 제거 후 일관된 DSL 체인 방식으로 알림 처리 흐름 단순화
보내야 할 토큰이 여러 개일 경우, 한 번의 요청으로 전송 가능해 성능 향상
@soonduck-dreams

Copy link
Copy Markdown
Contributor Author

RenderedNotification 수정 예정입니다 (푸시 알림 title, 푸시 알림 body, 인앱 알림 body 분리)

notification 테이블은 "인앱알림"이므로 title 제거
@soonduck-dreams soonduck-dreams merged commit b1ae13e into develop Aug 13, 2025
1 check passed
@soonduck-dreams soonduck-dreams deleted the refactor/noti branch August 13, 2025 02:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant