-
간단한 알림 기능 구현하기Today I Learned 2022. 12. 4. 00:00
현재 앱에서는 사용자가 운동에 참가를 신청하거나, 작성자가 사용자의 운동 참가 신청을 수락하는 등의 행동을 했을 때, 각 사용자가 그런 동작이 일어난 사실을 알기 위해서는 게시글을 직접 찾아가 확인해야 한다. 지금까지 구현된 수준의 기능에서 어떻게 하면 사용자의 사용 편의성을 늘려줄 수 있을까 고민한 끝에 '알림' 기능을 추가해보기로 결정했다.
현재 앱에서 발생하는 가장 주요한 이벤트인 '운동 참가 신청'과 '신청 수락'을 타겟으로 잡아 다음의 기능을 구현했다.
- 운동 참가 희망자가 운동 모집 게시글에 참가를 신청했을 때, 게시글 작성자에게 '특정 사용자가 참가를 신청했다는' 알림이 전달된다.
- 운동 모집 게시글 작성자가 특정 사용자의 참가 신청을 수락했을 때, 해당 사용자에게 '작성자가 참가 신청을 수락했다는' 알림이 전달된다.
- 모든 사용자는 로그인 시 헤더의 '알림' 버튼을 눌러 자신에게 전달된 알림을 확인할 수 있다.
'알림' 객체는 다음과 같이 구성된다.
// models/Notice.java @Entity @Table(name = "notices") public class Notice { @Id @GeneratedValue private Long id; private Long userId; @Embedded private NoticeContents contents; @Embedded private NoticeStatus status; @CreationTimestamp private LocalDateTime createdAt; // constructors // ... public boolean active() { return status.equals(NoticeStatus.unread()) || status.equals(NoticeStatus.read()); } }
// models/NoticeStatus.java @Embeddable public class NoticeStatus { private static final NoticeStatus UNREAD = new NoticeStatus("unread"); private static final NoticeStatus READ = new NoticeStatus("read"); private static final NoticeStatus DELETED = new NoticeStatus("deleted"); @Column(name = "status") private String value; // constructors // ... public static NoticeStatus unread() { return UNREAD; } public static NoticeStatus read() { return READ; } public static NoticeStatus deleted() { return DELETED; } // equals, hashCode, toString }
알림 객체를 생성하는 로직은 사용자가 참가를 신청하는 joinGameService와, 작성자가 참가 신청을 수락하는 PatchRegisterToAcceptedService에서 이루어진다. 기존의 register를 생성하거나 변경하는 로직 뒤에 다음의 소스코드들을 추가했다.
// services/JoinGameService.java Register register = game.join(currentUser, registers); if (register != null) { Register savedRegister = registerRepository.save(register); Post post = postRepository.findById(game.postId()) .orElseThrow(PostNotFound::new); User postAuthor = userRepository.findById(post.userId()) .orElseThrow(() -> new UserNotFound(post.userId())); Notice notice = savedRegister.createRegisterNotice( currentUser, postAuthor ); noticeRepository.save(notice); }
// services/PatchRegisterToAcceptedService.java register.acceptRegister(); if (register.accepted()) { User user = userRepository.findById(register.userId()) .orElseThrow(() -> new UserNotFound(register.userId())); Game game = gameRepository.findById(register.gameId()) .orElseThrow(GameNotFound::new); Notice notice = register.createAcceptNotice(user, game); noticeRepository.save(notice); }
참가 신청과 신청 완료에 대한 알림의 생성은 Register가 자신이 생성되거나 변경되었음을 알리는 것에 착안해 Register가 Notice를 생성하도록 했다.
// models/Register.java public Notice createRegisterNotice(User currentUser, User postAuthor) { return new Notice( postAuthor.id(), new NoticeContents( "작성한 모집 게시글에 새로운 신청이 등록되었습니다.", "등록한 신청자: " + currentUser.name().toString() ), NoticeStatus.unread() ); } public Notice createAcceptNotice(User user, Game game) { return new Notice( user.id(), new NoticeContents( "신청한 운동 모집 게시글에 참가가 확정되었습니다.", "신청한 게임 종목: " + game.exercise() + "\n" + "신청한 게임 시간: " + game.dateTime().joinDateAndTime() ), NoticeStatus.unread() ); }
알림 기능을 설계하기 시작하면서 다음의 부분들을 고민했다.
- Notice 객체를 생성하는 주체는 누가 되어야 하는가?
이 부분은 User와 Register 중에 고민하다가 Register로 정했다. User가 알림을 생성하는 경우는 '사용자가 다른 사용자에게 알림을 보낸다'는 의미를, Register가 알림을 생성하는 경우는 'Register가 자신의 생성 또는 변화를 사용자에게 알린다'는 의미를 고려했다. 일단은 후자를 선택해 구현을 진행했다. - Register뿐만 아니라 다른 여러 로직에서 발생하는 알림도 처리하기 위해서는 어떻게 해야 할 것인가?
'알림 종류'를 속성으로 둘 수 있을지 고민했다. 과정이 끝난 뒤에도 프로젝트를 개인적으로 계속 진행한다면 신청/수락 뿐만 아니라 다양한 상황에서 알림이 생성되어 전달되는 경우를 고려해야 할 것이라는 생각이 들었다. 일단은 register 객체가 개별 메서드에서 신청/수락에 대한 메시지를 직접 작성하는 식으로 구현했지만, 앞으로도 이런 식으로 '1 알람 - 1 메서드' 식으로 확장해나가야 하는지는 의문이다.
일단은 알림이 생성되었을 때, 생성된 알림을 각 사용자가 확인할 수 있는 수준까지 구현했다.
사용자 4의 계정으로 사용자 3의 계정이 작성한 게시글에 참가를 신청했다.
사용자 3의 계정에서 알림 목록을 확인할 경우 다음의 메시지를 확인할 수 있다.
사용자 3의 계정으로 자신이 작성한 게시글에서 사용자 4의 신청을 수락한다.
사용자 4의 계정에서 알림 목록을 확인하면 다음의 메세지를 확인할 수 있다.
알림 기능에 대해서는 다음의 기능들을 추가적으로 구현을 시도해볼 계획이다.
- 알림 상세 내용 확인, 알림 메시지에서 직접 게시글로 이동
- 헤더에서 읽지 않은 알림 개수 표시
- 알림 삭제
작업 내역
- https://github.com/hsjkdss228/smash-frontend/pull/40
- https://github.com/hsjkdss228/smash-backend/pull/28
'Today I Learned' 카테고리의 다른 글
어드민 페이지를 구상해야 하는 이유 (0) 2022.12.05 두 가지의 서로 다른 영역이 충돌해 만들어내는 문제 해결하기 (navigate, react-modal) (0) 2022.12.04 책임감 (0) 2022.12.02 사용성을 고려해 기능을 수정하다 (0) 2022.12.01 우리 가게 다시 영업합니다 (0) 2022.11.30 - 운동 참가 희망자가 운동 모집 게시글에 참가를 신청했을 때, 게시글 작성자에게 '특정 사용자가 참가를 신청했다는' 알림이 전달된다.