댓글이 달렸을 때 포스터 주인에게, 좋아요를 눌렀을 때 포스터 주인에게 알림을 보내는 기능을 구현을 해봤어요. 첫 구현에는 단순하게 구현만 생각하여 댓글을 작성할 때, 알림 서비스에서 알림을 넣어주었죠. 좋아요 눌렀을 때도 이와 같이 구현헀어요.
기능 작동이 잘 되어서 뿌듯했지만 문득 생각에 잠겼어요. 댓글 작성과 알림 보내는 기능이 한 서비스 로직안에 존재하게 되었는데, 이렇게 되면 알람이 보내지지 않을 때, 댓글 작성도 실패하게 되는 것이지요.
알림은 부가적으로 사용자의 편의성을 도와줄 뿐이지, 댓글 작성과는 별개여야해요. 즉, 알림의 유무가 댓글에 영향을 주어서 안되는데, 저는 이에 어긋난 설계를 했어요.
옵저버 이름처럼 행위자, 행위자를 관찰하는 관찰자가 존재해요.
이벤트를 발생시키는 publisher와 관찰하는 observer(=EventListener)가 있는 것이지요.
다양한 상황에서 사용할 수 있지만 요약하면 객체가 다른 객체에 메시지를 알릴 수 있어야 하고 이러한 객체가 밀접하게 결합되는 것을 원하지 않을 때 옵저버 패턴을 적용할 수 있다고 말할 수 있습니다. 비동기 이벤트가 하나 이상의 그래픽 구성 요소에 알려야 할 때 이 패턴을 사용했습니다.
(스프링 4.2부터 ApplicationEvent를 상속하지 않아도 되요)
@Getter
public class AlarmEvent {
private final Alarm alarm;
public AlarmEvent(Alarm alarm) {
this.alarm = alarm;
}
public static AlarmEvent from(Alarm.AlarmType alarmType, User target, User from) {
return new AlarmEvent(Alarm.builder()
.alarmType(alarmType)
.targetUser(target)
.fromUser(from)
.build());
}
}
@Component
public class AlarmEventHandler {
private final AlarmRepository alarmRepository;
public AlarmEventHandler(AlarmRepository alarmRepository) {
this.alarmRepository = alarmRepository;
}
@EventListener
@Async
public void createAlarm(AlarmEvent event) {
alarmRepository.save(event.getAlarm());
}
}
@Service
@RequiredArgsConstructor
public class CommentService {
private final CommentRepository commentRepository;
private final PostRepository postRepository;
private final UserRepository userRepository;
private final ApplicationEventPublisher publisher;
@Transactional
public CommentCreateResponseDto createComment(CommentCreateRequestDto commentCreateRequestDto,
Integer postId, String userName) {
Post post = findPost(postId);
User user = findUser(userName);
Comment comment = commentCreateRequestDto.toEntity(user, post);
commentRepository.save(comment);
// 내 게시물에 내 댓글을 알람 저장 x
if (!post.getUser().getUserName().equals(userName)) {
publisher.publishEvent(AlarmEvent.from(NEW_COMMENT_ON_POST, post.getUser(), user));
}
return CommentCreateResponseDto.from(comment);
}
}
한 트랜잭션에 두 기능이 묶여져 있어요.
@TransactionalEventListener은 phase 옵션을 통해 트랜잭션에 따른 이벤트처리를 지원해요.
default 값이며, 트랜잭션이 Commit 됐을 때 이벤트를 실행해요.
트랜잭션이 Rollback 됐을 때 이벤트를 실행해요.
트랜잭션이 COMPLETION 됐을 때 이벤트를 실행해요.
AFTER_COMMIT 또는 AFTER_ROLLBACK이 발생했을 때를 의미해요.
위 1,2번을 합친 것이죠.
트랜잭션이 COMMIT 되기 전에 이벤트를 실행해요.
@Component
public class AlarmEventHandler {
private final AlarmRepository alarmRepository;
public AlarmEventHandler(AlarmRepository alarmRepository) {
this.alarmRepository = alarmRepository;
}
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
@Async
public void createAlarm(AlarmEvent event) {
alarmRepository.save(event.getAlarm());
}
}
ApplicationEventPublisher (Spring Framework 5.3.22 API)
https://www.javacodegeeks.com/2012/08/observer-pattern-with-spring-events.html