보틀러 프로젝트에서 알림이 보내지는 경우는 총 6가지입니다.
- 계정 일시정지
- 계정 경고
- 키워드 편지 도착
- 타겟 편지 도착
- 답장 편지 도착
- 키워드
- 지도
이 6가지의 기능에서 알림 보내기를 실패했을 때, 해당 기능도 실패해야 하는지에 대해 의문이 들었습니다.
알림 전송은 2가지 단계가 존재합니다.
- 알림 저장
- 알림 푸시
이때, 알림의 주요사항은 다음과 같습니다.
- 알림 전송에 실패해도, 알림을 호출한 기능에 영향을 주면 안 된다.
- 알림 저장에 실패해도, 알림을 호출한 기능에 영향을 주면 안 된다.
- 알림 푸시에 실패해도, 알림 저장에 영향을 주면 안 된다.
- 알림 저장에 실패해도, 알림을 호출한 기능에 영향을 주면 안 된다.
알림 푸시 실패
포스트맨에서 생각한 비즈니스 로직 상 알림 푸시에 실패하더라도, 알림을 저장하는 데에 영향을 주지 않도록 결정하였습니다.
사용자 정지 시, 알림을 요청하는 기능은 다음과 같습니다.
public class NotificationService {
private final NotificationRepository notificationRepository;
private final SubscriptionRepository subscriptionRepository;
private final PushNotificationProvider pushNotificationProvider;
@Transactional
public void sendBanNotification(Long userId) {
sendNotification(NotificationType.BAN, userId, null, null);
}
public NotificationResponseDTO sendNotification(NotificationType type, Long userId, Long letterId, String label) {
Notification notification = Notification.create(type, userId, letterId, label);
Subscriptions subscriptions = subscriptionRepository.findByUserId(userId);
NotificationResponseDTO result = NotificationResponseDTO.from(notificationRepository.save(notification));
if (subscriptions.isPushEnabled()) {
pushMessage(type, subscriptions);
}
return result;
}
}
public class FirebasePushProvider implements PushNotificationProvider {
private final FirebaseMessageMapper messageMapper;
@Override
public void pushAll(PushMessages pushMessages) {
List<Message> firebaseMessages = messageMapper.mapToFirebaseMessages(pushMessages);
try {
FirebaseMessaging.getInstance().sendEach(firebaseMessages);
} catch (FirebaseMessagingException e) {
log.error("알림 푸시 실패 : {}", e.getMessage());
}
}
}
Firebase의 경우, 푸시 알림 전송 시 비검사 예외를 통해 예외 처리를 강제하고 있습니다. 현재 코드에서는 try-catch를 통해 예외를 잡아 처리하였습니다. 따라서 NotificationService에서 알림 푸시에 예외가 발생하여도 문제가 되지 않습니다.
알림 저장 실패
알림은 현재 Redis를 통해 관리되고 있습니다. 만약 알림을 저장하는 데 실패하더라도, 유저 정지는 이루어져야 합니다.
트랜잭션 전파
알림 저장에 실패하여 예외가 UserService까지 넘어가는 경우, UserService는 트랜잭션을 커밋할 수 없습니다.
sendBanNotification에서 발생한 예외가 타고 넘어가서 UserService까지 예외로 인해 중단됩니다. 그렇다면 UserService에서 try-catch를 통해 처리한다면 어떨까요?
// 유저 정지 로직
try {
notificationService.sendBanNotification(userId);
} catch (RuntimeException e) {
log.error("알림 전송 오류");
}
단순히 코드만 보면 유저 정지 처리 내용이 커밋될 것 같지만, 실제로는 롤백이 됩니다. 그 이유는 NotificationService 내에서 발생한 예외로 인해 NotificationService를 감싼 트랜잭션 AOP는 해당 트랜잭션의 rollbackOnly를 true로 수정합니다.
따라서 UserService는 rollbackOnly=true로 인해 커밋하지 못하고 유저를 정지 처리한 내용을 롤백하게 됩니다. 이를 해결하기 위해서 어떻게 해야 할까요? NotificationService가 UserService의 트랜잭션 rollbackOnly를 건드리지 않게 하기 위해서는 트랜잭션 전파를 사용할 수 있습니다.
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void sendBanNotification(Long userId) {
sendNotification(NotificationType.BAN, userId, null, null);
}
이렇게 하면 UserService와 NotificationService의 트랜잭션이 분리되어 별개로 동작하게 됩니다. 즉, 같은 커넥션을 공유하지 않고 추가적으로 하나 더 사용하게 됩니다.
알림 내에서의 트랜잭션..?
위에서 언급하였듯이, 알림 내 두 가지 주요 사항이 있습니다.
- 알림 저장에 실패해도, 알림을 호출한 기능에 영향을 주면 안 된다.
- 알림 푸시에 실패해도, 알림 저장에 영향을 주면 안 된다.
위 내용을 보면 알림 푸시에 실패하여도 굳이 롤백할 필요가 없음을 알 수 있습니다. 따라서 트랜잭션 적용을 제외하였습니다.
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void sendBanNotification(Long userId) {
sendNotification(NotificationType.BAN, userId, null, null);
}
Propagation.NOT_SUPPORTED 옵션은 부모 트랜잭션이 없다면 없이 진행하고, 만약 부모 트랜잭션이 존재한다면 잠시 중지하여 진행하게 됩니다. 이를 이용하여 다른 서비스에서 알림 기능을 사용할 때, 트랜잭션을 잠시 중지시킬 수 있습니다.
NOT_SUPPORTED를 통해 sendBanNotification에서 예외가 발생하더라도 rollbackOnly=false로 유지됩니다.
그런데..
타 서비스에서 요청하는 알림 기능은 필수적이지 않다고 판단하였기 때문에 send~Notification을 사용하는 서비스 모두 try-catch문이 모두 들어가게 됩니다. 따라서 send~Notification 자체에서 try-catch문을 적용하기로 하였습니다.
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void sendBanNotification(Long userId) {
try {
sendNotification(NotificationType.BAN, userId, null, null);
} catch (RuntimeException e) {
log.error("알림 저장 실패 : {}", e.getMessage());
}
}
사실 위처럼 try-catch를 통해 예외를 바로 잡아버린다면, 굳이 NOT_SUPPORTED를 적용하지 않고 @Transactional 자체를 제거해도 같게 실행됩니다. 하지만 해당 알림 기능은 트랜잭션을 사용하지 않는다는 것을 명시하기 위해 그대로 두기로 하였습니다.
마치며
각 기능 별 알림을 보내야 하는 여러 상황이 존재하지만, 비즈니스 로직 상 실패하여도 주 기능에 영향을 주면 안 되는 상황이었습니다. 이러한 상황에서 트랜잭션의 범위를 고려해 보며 여러 방면으로 생각해 볼 기회가 되었습니다. 트랜잭션 전파에 대해 알아볼 수 있는 좋은 기회가 되어 다행이었습니다.
'데브코스' 카테고리의 다른 글
인덱스를 통한 받은 편지 조회 개선하기 (0) | 2025.03.21 |
---|---|
모니터링 서버 구축하기 (0) | 2025.01.27 |
FCM을 통한 백엔드에서의 푸시 알림 전송 (0) | 2024.12.10 |
이벤트스토밍 적용기 (0) | 2024.11.24 |