-
Notifications
You must be signed in to change notification settings - Fork 0
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
Refactor/#165: 채팅 메시지 도메인 리팩토링 #171
base: develop
Are you sure you want to change the base?
The head ref may contain hidden characters: "refactor/#165/\uCC44\uD305-\uBA54\uC2DC\uC9C0-\uB3C4\uBA54\uC778-\uB9AC\uD329\uD1A0\uB9C1"
Conversation
정적 팩토리 메소드 내에서 비즈니스 로직을 수행하는 경우에는 create, 값을 매핑만 하는 경우에는 of로 네이밍
- repository find 시 status 조건 미적용돼있던 부분 수정 - 코드 포매팅
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.
일급 컬렉션, 함수형 인터페이스, 람다, 스트림 등등 여러 자바 기술들을 적극 활용하셨네요!
덕분에 이것저것 많이 찾아보면서 재밌게 리뷰할 수 있었습니다.
처음에 서현님과 얘기하면서 component service와 module service를 분리해보자 라고 생각했을 때는 뭔가 component 서비스에는 repo 의존성이 없는 순수한 도메인 로직과 관련된 코드만이 존재하여 훨씬 깔끔해지겠다라고 생각했었는데,
여전히 의존성의 방향이 component 서비스 -> module 서비스 -> repo 이고, component 서비스에서도 영속성 계층의 모델인 jpa entity를 사용하니 비즈니스 로직만을 독립적으로 분리하였다고 보기 어려운거 같기는 하네요
뭔가 서비스 레이어를 sub 계층 2개로 나누었을때의 장/단점, 그리고 대안책에 대해서 좀 더 고민해봐야할 것 같습니다
저희가 하는 고민들과 관련된 블로그 링크 몇개 남깁니다!
https://coldpresso.tistory.com/24
https://jinalim-dev.tistory.com/80
this.space = space; | ||
this.name = name; | ||
this.img = img; | ||
} |
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.
아 채팅방 생성 시 이미지의 필수 여부가 개발 과정에 변경되었던 것 같아요.
초기에는 이미지가 필수가 아닌 것으로 설계해서 엔티티에 @Nullable
로 설정했는데,
이미지도 필수인 것으로 바뀌면서 반영이 안되었네요.
엔티티의 img
필드가 @Nullable
임에 따라 생성자에도 해당 어노테이션을 추가한 거라,
엔티티의 img
필드를 @NotNull
로 변경하면 해결될 것 같습니다~
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.
그럼 혹시 해당 엔티티의 생성시 bean validation 을 어겼을 경우에 대한 후처리 로직도 있을까요??
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.
흐름상 RequestDto
에서 validation
진행 후 controller
에서 이에 대한 bindingResult
가 올바른지 확인함으로써
API 요청을 통한 엔티티 생성 시 validatioin
처리가 되어있습니다!
말씀하시는 내용이 엔티티 자체적으로 validation
에 대한 처리가 있는지에 대한 것일까요?
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이 있어야하지 않을까? 라는 생각으로 답글을 단 것이었는데
다시 생각해보니 앞에서 이미 걸러진 친구들이기도하고, 혹시 여기서 문제가 발생할 경우에는 서버 내부 코드에서의 문제가 맞으니, 따로 validation 처리를 하는 것보다는 500 error 를 발생시키는것이 맞는건가? 싶네여
(뭔가 이 부분도 다같이 얘기해보면 좋을 거 같긴 합니다)
그러면 @NotNull 어노테이션을 controller에서 검증을 거친 인자에 대해서 엔티티 내부에서 한 번 더 안전장치를 거신 거라고 생각하면 될까요??
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.
음 의도했던 바는 안전장치라기보다는 ChatRoom 엔티티 자체의 특성을 반영한 설정을 한 것입니당
결론적으로 안전장치의 역할도 가능하구요!
다시 생각해보니 앞에서 이미 걸러진 친구들이기도하고, 혹시 여기서 문제가 발생할 경우에는 서버 내부 코드에서의 문제가 맞으니, 따로 validation 처리를 하는 것보다는 500 error 를 발생시키는것이 맞는건가? 싶네여
이 부분은 repository
를 통해 DB에 ChatRoom
엔티티를 저장할 때 null
인 경우 500 error를 발생시키는 걸 말씀하시는건가요??
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.
아하 이중으로 에러 처리를 해줘도 괜찮을 것 같아요 다음에 다른 분들이랑 다같이 얘기해봅시다!
Long userId, | ||
Function<ChatRoom, LastMessageInfoDto> lastMessageFinder, | ||
BiFunction<Long, ChatRoom, Integer> unreadMessageCounter) { | ||
return chatRooms.stream() |
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.
오오 함수형 인터페이스, 람다표현식, 스트림 까지 고급 자바 기술들의 집합체네요.
특히 함수를 매개변수로 받아들임으로써 마지막 메시지를 찾는 로직, 읽지 않은 메시지를 계산하는 로직을 현재 toChatRoomResponses 메서드의 주요로직인 엔티티 -> dto 변환로직과 분리할 수 있네요 !!
코드를 보고 개인적으로 자바8에 대한 공부가 많이 필요하다고 느꼈습니다 하하
음 제안드리고 싶은 사안이 있는데, 혹시 return type을 List 에서 DTOs 일급 컬렉션으로 변경하는건 어떤가요??
toChatRoomResponses 메서드 내부에서 일급 컬렉션에게 chatRoom 엔티티 중 dto 구성에 필요한 정보를 보내고, 일급 컬렉션은 필드로 가지고 있는 List 컬렉션에 이 정보들을 계속 추가하고, 최종적으로 구성된 List 컬렉션을 다시 toChatRoomResponses 메서드가 받으면 이걸 return 하는 플로우가 가능할 것 같습니다!
이러면 toChatRoomResponses 메서드의 책임을 ChatRooms 가 가지고 있는 CharRoom 엔티티들 중 필요한 정보를 뽑아낸다 라는 것으로 국한시키고, 뽑아낸 정보들을 반환하는 것에 대한 책임을 일급컬렉션으로 위임할 수 있지않을까 싶습니다.
또한 외부에서 해당 dto list 에 접근하는 로직이 추가될떄에도 일급 컬렉션에게 책임을 맡기면 되므로 유용할 것 같습니다!
this.chatRoom = chatRoom; | ||
this.user = user; | ||
this.lastReadTime = lastReadTime; | ||
} |
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.
이 부분도 ChatRoom 과 동일하게 엔티티에 유효성 검증하는 코드를 작성하신 이유가 궁금합니다!
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 UserChatRooms(List<UserChatRoom> userChatRooms) { | ||
this.userChatRooms = Collections.unmodifiableList(userChatRooms); | ||
} |
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.
오호 UserChatRooms 일급컬렉션 생성 시에 관련 UserChatRoom 엔티티의 list를 모두 인자로 주입해주고, list 컬렉션을 읽기 전용으로 제한하셨군요!
현 시점에서의 특정 채팅방의 모든 유저 정보 조회 api 를 구현하기 위해서는 UserChatRoom 엔티티들의 불변성 보장이 중요해보이므로 일급 컬렉션 생성 시에 읽기전용으로 제한을 거는것은 좋은 것 같습니다!
return userChatRooms.stream() | ||
.map(UserChatRoom::getUser) | ||
.collect(Collectors.toUnmodifiableList()); | ||
} |
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.
LGTM
@Slf4j | ||
@RequiredArgsConstructor | ||
public class ChatRoomService { | ||
|
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) 를 걸어놓고,
서비스 클래스 중 query가 아니라 command 에 해당하는 메서드에 한해서 @transactional 을 걸어놓는 방식은 어떤가요??
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.
오 좋은 것 같네요!
그럼 하위 module service에서의 transaction 설정은 삭제해도 괜찮은 건가요?
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.
음 하위 module service에 @transactional 어노테이션을 설정하더라도 이걸 호출한 상위 component service에서의 트랜잭션과 합쳐지는 것으로 알고있어서(default 설정에서는) 놔두시거나 제거하시거나 크게 상관은 없을 것 같습니다!
관련 블로그 링크입니닷
https://mangkyu.tistory.com/269
String chatRoomImgUrl) { | ||
User chatRoomOwner = userUtils.findUserByUserId(userId); | ||
Space space = spaceUtils.findSpaceBySpaceId(spaceId); | ||
|
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.
앗 이 부분은 현재 develop 브랜치에 반영되어있는 UserRepository, SpaceRepository 와 return type에 대한 null 체크로 변경해주시면 될 듯 합니다!
아 그런데 module service 도입을 고려하신다고 했으니
흠 같이 좀 더 고민해보입시다
return chatMessages.stream() | ||
.map(ChatMessageResponse::create) | ||
.collect(Collectors.toList()); | ||
} |
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.
LGTM
public ChatMessageResponse sendChatMessage(Long senderId, ChatMessageRequest chatMessageRequest, Long chatRoomId) | ||
throws IOException { | ||
UserSpace senderInSpace = userSpaceUtils.isUserInSpace(senderId, chatMessageRequest.getSpaceId()) | ||
.orElseThrow(() -> new CustomException(USER_IS_NOT_IN_SPACE)); |
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.
utils 여기도 있네요 하하
} | ||
default -> { | ||
} | ||
} |
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.
음 현재는 img, file, 일반 채팅(text) 에 대해서만 채팅타입을 다루고 있지만, 확장성을 고려하여 이 부분을 객체화 & enum 화 하여 chattingService 와 책임 분리를 하면 좋을 것 같습니다!
(하지만 레거시 기능이라고 하니 이러면 좀 더 좋을거같다~~ 라고만 생각하셔도 되지 않을까 싶습니다)
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.
네 저도 같은 생각입니다ㅜㅜ
content를 그냥 Map으로 넣어놔서 저런 사태가 발생했는데.. 여유가 된다면 이부분도 나중에 리팩토링해보겠습니다!
📝 요약
🔖 변경 사항
chat
도메인에서 일급컬렉션 적용Service
layer 추상화 :Module Service
와Component Service
도입Module Service
: Repository만을 의존하며 실제로 DB와 상호작용 및 null에 대한 간단한 예외처리를 거쳐 entity를 반환Component Service
:Module Service
만을 의존하며Module Service
로부터 얻은 entity를 이용한 비즈니스 로직만을 포함@Builder
는 기본적으로 클래스 전체에 대해 동작하여 접근 제어자(private)를 따르지 않음-> 외부에서
@Builder
사용을 막기 위해AccessLevel
제한 설정✅ 리뷰 요구사항
이슈명은 채팅 메시지 도메인이지만 수정하다보니 채팅방 도메인에도 수정이 많아졌네욤.. 다음부턴 주의하겠습니담
chat
도메인에 대해서만Module Service
를 적용했는데, 해당 부분이 적절한지 봐주시면 좋을 것 같습니다.피드백 반영하여 (현재 util로 작성되어있는)
chat
도메인 외부에 대한 공통Module Service
도 추가하겠습니다.chat
도메인에서 기존Repository
의findBy{Entity}
형태를,findBy{EntityId}
형태로 가능한 메소드들에 한해 수정했습니다.entity
자체를 넘겨줘도 JPA 내부적으로 id를 찾아서 비교하는 것으로 알고 있어, 오직Repository
의 메소드로 넘기기만을 위한불필요한 DB 조회를 최소화하고자했습니다.
📸 확인 방법 (선택)
📌 PR 진행 시 이러한 점들을 참고해 주세요