-
Notifications
You must be signed in to change notification settings - Fork 8
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[BE] feat: 스태프 인증 처리시 FCM 을 통해 유저 핸드폰에 입장 처리 완료 메시지를 보낸다(#444) #449
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
수고하셨습니다아 !!
멋지네요..
@@ -23,7 +23,7 @@ public AuthFacadeService(AuthService authService, OAuth2Clients oAuth2Clients, | |||
} | |||
|
|||
public LoginResponse login(LoginRequest request) { | |||
LoginMemberDto loginMember = authService.login(getUserInfo(request)); | |||
LoginMemberDto loginMember = authService.login(getUserInfo(request), request.fcmToken()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
사소한 제안인데 getUesrInfo(request)를 인라인 말고 변수 추출하는건 어떨까요?
Oauth2Clients에게 요청을 하고 -> tx 시작하고 -> 토큰 발급하고
이 흐름에서 첫번째 단계가 눈에 잘 보이지 않는 것 같아요
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
인정인정 무조건 줄 수 줄이는게 좋은건 아닌 것 같아요 👍
private void enrollFcm(Member member, String fcmToken) { | ||
// TODO : 멤버의 모든 FCM 을 지우지 않기 위해서는 멤버의 디바이스에 대한 식별자가 필요합니다. | ||
deleteFCM(member); | ||
memberFCMRepository.save(new MemberFCM(member, fcmToken)); | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
delete해주는건 해당 기기에서의 이전 FCM 토큰을 지워주기 위해서일까요 ?!
꼼꼼한 처리 좋네요 👍
@@ -50,6 +55,7 @@ public TicketValidationResponse validate(TicketValidationRequest request) { | |||
EntryCodePayload entryCodePayload = entryCodeManager.extract(request.code()); | |||
MemberTicket memberTicket = findMemberTicket(entryCodePayload.getMemberTicketId()); | |||
memberTicket.changeState(entryCodePayload.getEntryState()); | |||
publisher.publishEvent(new EntryProcessEvent(memberTicket.getOwner().getId())); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
어떤 memberTicket에 대한 fcm인지는 구분해주지 않아도 되나요?!
(fcm 메세지에 memberTicketId는 함께 보내지 않아도 되나요?)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
지금은 이벤트가 딱 하나니까 그냥 이벤트가 날라왔다? -> 다음 화면으로 이동 이렇게 구현되어 있어요!!
애쉬가 말씀 주신 것처럼 memberTicketId 를 같이 보내서 android 에서도 검증을 할 수 있게 만들면 더 좋겠네요 ㅎㅎ
이건 안드로이드 분들과 한번 같이 이야기해보져
private void validate(Member member, String fcmToken) { | ||
if (member == null || fcmToken == null) { | ||
throw new IllegalArgumentException("MemberFCM 은 허용되지 않은 null 값으로 생성할 수 없습니다."); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
얘는 왜 InternalServerException이 아닌 IllegalArgumentException인가요?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
도메인 validation 을 잡을 때 이게 BadRequest 인지 InteralServerException 인지 어떻게 결정하면 좋을까 고민이 됐습니다.
또한 도메인 안에서 Http status 가 결정되는 것이 맞나..? 라는 생각을해서 도메인에서 표준 예외를 발생시키도록 만들었습니다.
그 에외에 대한 판단은 Service 에서 해당 도메인의 생성에서 예외가 발생한 것은 InteralServerException 이다, BadRequest 이다. 이렇게 판단하면 좋지 않을까 생각했었습니다!!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
kotlin을 썼으면 이런일이 없었을텐데 ㅠㅠ
private Optional<FirebaseApp> defaultFirebaseApp() { | ||
List<FirebaseApp> firebaseAppList = FirebaseApp.getApps(); | ||
if (firebaseAppList == null || firebaseAppList.isEmpty()) { | ||
return Optional.empty(); | ||
} | ||
return firebaseAppList.stream() | ||
.filter(firebaseApp -> firebaseApp.getName().equals(FirebaseApp.DEFAULT_APP_NAME)) | ||
.findAny(); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
사용하는 메서드 아래에 위치시키면 좋을 것 같아요!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
앗 ㅋㅋ 감사합니다
create table if not exists member_fcm | ||
( | ||
id bigint not null auto_increment, | ||
created_at datetime(6), | ||
updated_at datetime(6), | ||
member_id bigint, | ||
fcm_token varchar(255), | ||
primary key (id) | ||
) engine innodb | ||
default charset = utf8mb4 | ||
collate = utf8mb4_0900_ai_ci; | ||
|
||
alter table member_fcm | ||
add constraint fk_member_fcm__member | ||
foreign key (member_id) | ||
references member (id); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
#443 PR에 V3가 있는데, merge할 때 주의해야할 것 같네요!!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
해당 이슈 때문에 버전을 V202309180138
이렇게 사용하기도 한다네요
// then | ||
assertThat(actual).contains(expect); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
EOF!!
몇몇 파일에 EOF 문구가 떠요!! 전체적으로 확인해주세용
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
edu 설정을 빼먹었네요 ㅠㅠ 설정 달아놓고 개행 추가했어요!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
edu도 이 기능이 지원돼요 ? 😁
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
코멘트 몇가지 더 남겼어요!
public record EntryProcessEvent(Long memberId) { | ||
|
||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
public record EntryProcessEvent(Long memberId) { | |
} | |
public record EntryProcessEvent( | |
Long memberId) { | |
} |
private final FirebaseMessaging firebaseMessaging; | ||
|
||
private final MemberFCMService memberFCMService; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
이친구들 공백 없이 붙여줘도 괜찮을 것 같아요
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
앗 싸웠었나봐요 붙혀뒀습니다
|
||
@TransactionalEventListener | ||
@Async | ||
public void receiveEvent(EntryProcessEvent event) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
receiveEvent보다 의미있는 메서드명을 사용하면 좋을 것 같아요
이벤트 리스너가 여러개로 늘어나면 메서드명으로 해당 리스너가 어떤 일을 하는지 나타내는것이 필요할 것 같기도 하구요!
sendFcmNotification 과 같은 메서드가 있을 것 같네요
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
오 그렇네요 메서드명 감사합니다
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
푸우 테스트도 꼼꼼하게 작성하셨네요!! 고생하셨습니다, 간단한 리뷰 몇개 남겼습니다.
this.memberFCMService = memberFCMService; | ||
} | ||
|
||
@TransactionalEventListener |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
default after-commit이지만 명시적으로 해주는거 어떨까용
@Async | ||
public void sendFcmNotification(EntryProcessEvent event) { | ||
// TODO: channelId로 이벤트 구분 하는 로직 필요 | ||
List<Message> messages = createMessages(getMemberFCMToken(event.memberId()), null); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
푸우가 channelId에 관한 부분에 대한 이야기, null을 할당한 이유에 대한 부분을 구체적으로 작성해주셨는데
그래도 명시적으로 channelId를 할당 해주는 것이 좋지 않을까요? enum으로 할당해주면 될 것 같은데, 이 부분을 추가하는데 성능이나 시간이나 우리가 크게 리소스가 드는 부분은 아니니까요.
private void validate(Member member, String fcmToken) { | ||
if (member == null || fcmToken == null) { | ||
throw new IllegalArgumentException("MemberFCM 은 허용되지 않은 null 값으로 생성할 수 없습니다."); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
kotlin을 썼으면 이런일이 없었을텐데 ㅠㅠ
import org.springframework.transaction.event.TransactionalEventListener; | ||
|
||
@Service | ||
public class FCMNotificationFacade { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
이건 다들 생각이 다르실 것 같은데, Facade라는 명칭이(이미 쓰긴 했지만) 이해하기 애매한 부분이 분명히 있는 것 같아요.
(우리 같이 다 아는 상황이 아니라, 새롭게 저희 코드를 본다고 생각해볼때)
여기선 FCM을 찾아오는 MemberFcmService가 존재하니까, 전 FCMNotification이나 FCMNotificationService로 가는게 더 좋다고 생각하는데 다들 어떻게 생각하시나요?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
저도 FCMNotificationService 좋은 것 같아요!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
해당 클래스는 이벤트 리스너로 작동하고 있으니, Serivce
라는 명칭보다는 다른 이름을 갖게 하는 것도 좋아보이네요.
또한 알람을 보내는 것은 비즈니스 로직에 관련된 부분이 아니니까 @Profile
을 사용해서 프로덕션이나 데브 환경에서만 빈 등록을 하는 것도 좋아보이네요.
@@ -14,18 +18,34 @@ | |||
public class AuthService { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
AuthService에서 수행되는 FCM 로직들을 MemberFCMService가 하는건 어떨까요?
AuthService 입장에선 코드가 조금이나마 간결해질 것 같아요.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
✅
.build(); | ||
} | ||
|
||
private void checkAllSuccess(BatchResponse batchResponse, Long memberid) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
private void checkAllSuccess(BatchResponse batchResponse, Long memberid) { | |
private void checkAllSuccess(BatchResponse batchResponse, Long memberId) { |
발견
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
옥상갈뻔했네요
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
옥상 슈우웃
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
애쉬랑 오리가 대부분 리뷰 남겨주셔서 크게 리뷰 남길 것은 없었네요.
구현체에 많이 의존하는 부분이 보여서, 해당 부분 개선해주시면 좋을 것 같아요.
|
||
@Entity | ||
@Table(name = "member_fcm") | ||
public class MemberFCM extends BaseTimeEntity { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
NoArgsConstruct가 필요할 듯 싶네요
private final Long id; | ||
|
||
@NotNull | ||
@ManyToOne |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fetch = FetchType.LAZY
속성이 누락된 것 같아용
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
✅
import org.springframework.transaction.event.TransactionalEventListener; | ||
|
||
@Service | ||
public class FCMNotificationFacade { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
해당 클래스는 이벤트 리스너로 작동하고 있으니, Serivce
라는 명칭보다는 다른 이름을 갖게 하는 것도 좋아보이네요.
또한 알람을 보내는 것은 비즈니스 로직에 관련된 부분이 아니니까 @Profile
을 사용해서 프로덕션이나 데브 환경에서만 빈 등록을 하는 것도 좋아보이네요.
public LoginMemberDto login(UserInfo userInfo, String fcmToken) { | ||
Optional<Member> loginMember = memberRepository.findBySocialIdAndSocialType(userInfo.socialId(), userInfo.socialType()); | ||
if (loginMember.isPresent()) { | ||
Member member = loginMember.get(); | ||
enrollFcm(member, fcmToken); | ||
return LoginMemberDto.isExists(member); | ||
} | ||
Member newMember = signUp(userInfo); | ||
enrollFcm(newMember, fcmToken); | ||
return LoginMemberDto.isNew(newMember); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Auth
도메인에 외부 API 구현체인 FCMRepository
가 섞여있네요.
해당 부분은 이벤트를 사용해보는 것은 어떤가요?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
✅
create table if not exists member_fcm | ||
( | ||
id bigint not null auto_increment, | ||
created_at datetime(6), | ||
updated_at datetime(6), | ||
member_id bigint, | ||
fcm_token varchar(255), | ||
primary key (id) | ||
) engine innodb | ||
default charset = utf8mb4 | ||
collate = utf8mb4_0900_ai_ci; | ||
|
||
alter table member_fcm | ||
add constraint fk_member_fcm__member | ||
foreign key (member_id) | ||
references member (id); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
해당 이슈 때문에 버전을 V202309180138
이렇게 사용하기도 한다네요
@MockBean | ||
FirebaseMessaging firebaseMessaging; | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
DIP를 지켜야 할 것 같네요
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Profile 로 Test 환경에서 보이지 않도록 했습니다 !!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Profile 설정으로 테스트 환경에서 상관쓰지 않도록 변경했습니다 !!
# Conflicts: # backend/src/main/java/com/festago/auth/application/AuthService.java
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
수고많으셨습니다 푸우 🫡
} | ||
|
||
@Transactional(readOnly = true) | ||
public MemberFCMsResponse findMemberFCM(Long memberId) { | ||
List<MemberFCM> memberFCM = memberFCMRepository.findByMemberId(memberId); | ||
if (memberFCM.size() < LEAST_MEMBER_FCM) { | ||
logger.error("member {} 가 FCM 코드가 발급되지 않았습니다.", memberId); | ||
log.error("member {} 의 FCM 코드가 발급되지 않았습니다.", memberId); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
log.error("member {} 의 FCM 코드가 발급되지 않았습니다.", memberId); | |
log.error("member {} 의 FCM 토큰이 발급되지 않았습니다.", memberId); |
사소한건데 이거 어떤가요 ㅋㅋㅋ
FAIL_SEND_FCM_MESSAGE("FCM Message 전송에 실패했습니다."), | ||
FCM_NOT_FOUND("유효하지 않은 MemberFCM 이 감지 되었습니다."); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FAIL_SEND_FCM_MESSAGE("FCM Message 전송에 실패했습니다."), | |
FCM_NOT_FOUND("유효하지 않은 MemberFCM 이 감지 되었습니다."); | |
FAIL_SEND_FCM_MESSAGE("FCM Message 전송에 실패했습니다."), | |
FCM_NOT_FOUND("유효하지 않은 MemberFCM 이 감지 되었습니다."), | |
; |
@Profile({"dev", "prod"}) | ||
public class FCMNotificationEventListener { | ||
|
||
private static final Logger logger = LoggerFactory.getLogger(FCMNotificationEventListener.class); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
private static final Logger logger = LoggerFactory.getLogger(FCMNotificationEventListener.class); | |
private static final Logger log = LoggerFactory.getLogger(FCMNotificationEventListener.class); |
아래 코드와 통일하면 좋을 것 같아요!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
고생하셨습니다!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
고생하셨어요.
* feat: FCMConfig 생성 * chore: FCMConfig 패키지 이동 * feat: MemberFCM 생성 * feat: MemberFCMService 생성 * feat: NotificationFacade 생성 * feat: 로그인 시 fcm 을 등록한다. * feat: 입장 처리 시 EntryProcessEvent 를 발행한다. * feat: 유저 탈퇴시 유저의 FCM 을 모두 삭제한다. * feat: 테스트 환경에서 FirebaseMessaging 을 MockBean 으로 등록한다 * chore: 테스트 컨벤션 적용 * chore: Notification -> FCMNotificationFacade 네이밍 변견 * feat: flyway 추가 * chore: 마지막 줄 개행 추가 * feat: submodule 업데이트 * refactor: 메서드 네이밍, 메서드 순서, 파라미터 순서 변경 * refactor: fcm bean 을 테스트에서 제외 * feat: EventListener phase 명시 * chore: FCMEventListener 네이밍 변경 * feat: MemberFCM 의 Member 의존성 제거 * chore: EntryProcessEvent 패키지 분리 * refactor: AuthService 가 MemberFCM 을 의존하지 않도록 변경 * feat: MemberFCM 빈 생성자 추가 * chore: flyway version 변경 * feat: FCMChannel 을 Enum 으로 관리 * chore: 메서드 접근자 및 네이밍 변경, log 메세지 변경 * feat: local prod, dev 환경에서만 FCM Bean 들이 생성되도록 변경 * refactor: eventListen 로직을 비동기적으로 처리한다 * refactor: LoginService 와 MemberFCMService 를 분리한다 * chore: 파라미터 네이밍 변경 * chore: logger -> log 네이밍 변경 * chore: log 메시지 변경 * chore: flyway 버전 변경
* feat: FCMConfig 생성 * chore: FCMConfig 패키지 이동 * feat: MemberFCM 생성 * feat: MemberFCMService 생성 * feat: NotificationFacade 생성 * feat: 로그인 시 fcm 을 등록한다. * feat: 입장 처리 시 EntryProcessEvent 를 발행한다. * feat: 유저 탈퇴시 유저의 FCM 을 모두 삭제한다. * feat: 테스트 환경에서 FirebaseMessaging 을 MockBean 으로 등록한다 * chore: 테스트 컨벤션 적용 * chore: Notification -> FCMNotificationFacade 네이밍 변견 * feat: flyway 추가 * chore: 마지막 줄 개행 추가 * feat: submodule 업데이트 * refactor: 메서드 네이밍, 메서드 순서, 파라미터 순서 변경 * refactor: fcm bean 을 테스트에서 제외 * feat: EventListener phase 명시 * chore: FCMEventListener 네이밍 변경 * feat: MemberFCM 의 Member 의존성 제거 * chore: EntryProcessEvent 패키지 분리 * refactor: AuthService 가 MemberFCM 을 의존하지 않도록 변경 * feat: MemberFCM 빈 생성자 추가 * chore: flyway version 변경 * feat: FCMChannel 을 Enum 으로 관리 * chore: 메서드 접근자 및 네이밍 변경, log 메세지 변경 * feat: local prod, dev 환경에서만 FCM Bean 들이 생성되도록 변경 * refactor: eventListen 로직을 비동기적으로 처리한다 * refactor: LoginService 와 MemberFCMService 를 분리한다 * chore: 파라미터 네이밍 변경 * chore: logger -> log 네이밍 변경 * chore: log 메시지 변경 * chore: flyway 버전 변경
📌 관련 이슈
✨ PR 세부 내용
참고한 글을 다음과 같습니다.
글 1 , 글2
구현한 내용에 대한 설명은 다음과 같습니다.
여기서 추가적으로 논의해야할 포인트는
유저가 여러 디바이스에서 로그인할 경우 한 유저에 대한 FCM 은 다수가 저장될 수 있습니다.
현재는 로그인 시 넘어오는 FCM 으로 유저의 모든 FCM 을 지우고 새로운 FCM 하나로 대체하는 방식으로 구현해두었습니다.
Token 을 통해 유저를 식별할 수 있었다면 해당 유저에게 어떤 이벤트를 보낼 것인가는 Channel 이란 단위로 구분할 수 있습니다.
해당 개념을 반영한 부분은 다음과 같습니다.
추후에 이벤트가 늘어남에 따라서 이벤트에 대한 명세를 맞추면서 개발을 지속해 나가야할 것 같습니다.
현재는 이벤트 하나이기 때문에 이런 구분이 필요없기에 null 을 넣어놨습니다.