오늘은 약 70%의 MVP 기능 개발을 마무리했다!
오늘은 하루 종일 이번 프로젝트의 MVP 기능 개발을 진행했는데, 이제 약 70% 정도 마무리되었다.
나는 오늘 일단 모임 참여에 관련된 API들을 구현하고, 알림 생성 기능을 구현했다.
우선 모임 참여 관련 API들은 정말 말 그래도 POST, DELETE, GET 3가지 종류 뿐이었기에 구현하는 데에 시간만 조금 걸리고, 큰 어려움은 없었다.
(사실 쿼리DSL이 조금 어려워서 시간이 걸렸다..)
알림 생성 기능에서는 고민할 부분들이 너무 너무 많았다.
우리가 정한 알림의 종류는
이렇게 4가지가 있는데, 알림은 비즈니스 핵심 로직이라기보다 있으면 편한 기능이기도 하고,
알림 생성 실패 시에 본 요청이 실패하면 안 되기 때문에 트랜잭션 분리가 필요했다.
우선 알림 생성을 AOP를 통해 할 것인지, 서비스에서 다른 서비스를 호출하여 진행할 것인지부터가 고민이었다.
여태껏 이런 기능들을 모두 AOP를 사용했었는데, 이번에 알림을 구현하다보니 AOP를 사용해도 알림 타입별로 필요한 내용들이 너무 달라 AOP가 괜히 더 복잡해보였다.
그래서 이번에는 정말 나중에 알림의 종류가 더 추가되었을 때 AOP를 사용하기로 하고, 지금은 서비스에서 다른 서비스의 메서드를 호출하기로 하였다.
알림 생성을 진행하던 중에 새로운 트러블이 생겨 트러블 슈팅을 진행해야 했는데..
모임 생성에 대한 알림을 생성하면서 MeetingService.createMeeting() 메서드 안에서 Notification.notify() 메서드를 호출하였다.
이때 내부적으로는
createMeeting() 메인 트랜잭션에서 시작
meetingRepository.save(meeting)으로 meeting 저장
→ commit 되지 않았기에 meeting 테이블에 X-lock이 걸려있음
userMeetingRepository.save(userMeeting)으로 host 정보 저장
→ commit 되지 않았기에 마찬가지로 X-lock 걸려있음 (meeting에 대한 X-lock도 유지 중)
notificationService.notify…()로 새 트랜잭션으로 알림 서비스 호출
notificationRepository.saveAll(notificationList) meeting_id, user_id에 대한 FK 검사
⇒ 이때 부모 트랜잭션에서 X-lock을 여전히 유지 중이기에 S-lock을 얻을 수 없음
※ InnoDB는 FK 검사를 위해 S-lock을 획득해야 하는데, X-lock이 있는 상태에서는 S-lock을 얻을 수 없음
이런 순서로 동작하는데, 위 설명처럼 X-lock으로 인해 S-lock을 획득할 수 없어서 MySQLTransactionRollbackException: Lock wait timeout exceeded 이런 예외가 생겨버렸다.
이 상황에서 내가 할 수 있는 선택은 테이블 간의 매핑을 끊어서 FK를 확인하지 않는 방법과 createMeeting 메서드의 트랜잭션을 끝낸 후 notify 메서드의 트랜잭션을 시작시키는 것이다.
그래도 데이터의 정합성을 지키기 위해 테이블 간의 매핑을 끊기보다는 후자의 방법을 선택하였다.
@PostMapping
public ResponseEntity<...> createMeeting(...) {
meetingService.createMeeting(...);
notificationService.notify...(...);
return ...;
}
이때 위와 같이 나는 컨트롤러에서 2개의 서비스를 호출해야겠다고 생각했는데,
public MeetingCreateResponse createMeetingAndNotify(AuthUser authUser, MeetingCreateRequest request) {
User user = userRepository.findActivateUserById(authUser.getId());
// 모임 생성 메서드 호출
Meeting meeting = createMeeting(user, request);
// 알림 생성 메서드 호출
notificationService.notifyNewFollowerPost(meeting, user);
return MeetingCreateResponse.from(meeting);
}
튜터님께 여쭤보니 두 개의 서비스를 사용할 때는 위와 같이 서비스 퍼사드 패턴을 사용한다고 한다.
퍼사드 패턴은 여러 클래스로 구성된 복잡한 로직을 하나의 클래스로 감싸서 외부에서는 간단한 메서드 호출만으로 모든 기능을 사용할 수 있게 만드는 패턴이다.
위와 같이 사용하면 컨트롤러에서는 createMeetingAndNotify 메서드만을 호출하면 된다.
컨트롤러는 메서드를 조합하는 곳이 아니기 때문에, 이런 조합은 서비스 계층에서 사용해야 하고,
실제 현업에서는 이렇게 여러 서비스의 메서드를 조합해서 사용해야 하는 일이 많다고 한다.
여태까지는 다른 서비스에 기능이 구현되어 있어도 내가 레파지토리를 통해 다시 구현하는 일이 많았다.
예를 들어, 프로필 조회 API에서 팔로워/팔로잉 수 조회가 필요했는데, 팔로우 서비스의 메서드를 호출하기보다 팔로우 레파지토리를 통해 다시 계산하는 방법을 사용했다.
그런데, 이렇게 서비스의 메서드를 조합해서 사용하는 방법이 있더라!
앞으로는 이런 방법을 많이 사용해봐야겠다.
오늘 개발한 내용은 이 정도이고, 내일은 알림 리스트 조회와 단건 조회 API를 개발하고,
모임의 상태를 관리하는 EventListener를 도입해볼 생각이다.
우리 팀이 작성한 코드는 깃허브를 통해 업로드해두었다.
GitHub 보러가기
역시 세상엔 내가 모르는 기술들이 너무 너무 많다.
내가 구글링해서 나오지 않는 기술들도 너무 너무 많다...
어쩌면 그냥 구글링을 잘 못하는 것일지도 모르겠지만..
앞으로도 이런 저런 것들을 많이 찾아보고 적용해보면서 여러 내용들에 대해 찾아봐야겠다.