Skip to content
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

Open
wants to merge 17 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
c6f25fd
refactor: chatroom 및 chatting 도메인 구조로 변경
hyunn522 Nov 7, 2024
88e15b7
refactor: UserChatRoom 및 ChatMessage 디렉토리 위치 변경
hyunn522 Nov 13, 2024
81d9097
refactor: chat domain의 model 생성 방식 변경
hyunn522 Nov 14, 2024
443a2af
refactor: ChatService 메소드 분리
hyunn522 Nov 14, 2024
f2295bd
refactor: 매직넘버 상수화 및 메소드명 변경
hyunn522 Nov 16, 2024
f4e05e4
refactor: 불필요한 주석 삭제
hyunn522 Nov 17, 2024
9be06d2
refactor: ChatMessage의 status 타입을 enum으로 변경
hyunn522 Nov 18, 2024
8d57a15
refactor: ChattingRepository의 메소드에 status 조건 추가
hyunn522 Nov 18, 2024
1f239be
refactor: 가독성 및 확장성 향상을 위한 네이밍 및 로직 수정
hyunn522 Nov 18, 2024
800e0ad
refactor: Chatting 도메인에서 일급컬렉션 래핑 적용
hyunn522 Nov 19, 2024
c1edef3
refactor: ChatRoom 도메인에서 일급컬렉션 래핑 적용
hyunn522 Nov 21, 2024
2d64406
refactor: ChatRoom, Chatting 도메인에 module service 도입
hyunn522 Nov 21, 2024
06a4167
refactor: ChattingService에서 불필요한 변수 제거 및 가독성 향상
hyunn522 Nov 21, 2024
757d0db
refactor: ChatRoomService 메소드 분리
hyunn522 Nov 21, 2024
234ab5f
refactor: Builder에 대한 AccessLevel 제한
hyunn522 Nov 21, 2024
de4ee39
refactor: 테스트 코드 임의로 주석 처리
hyunn522 Nov 21, 2024
ebf1898
refactor: 엔티티의 일급컬렉션 클래스 디렉토리 위치 변경
hyunn522 Nov 21, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

This file was deleted.

17 changes: 0 additions & 17 deletions src/main/java/space/space_spring/dao/chat/ChattingRepository.java

This file was deleted.

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
package space.space_spring.controller;
package space.space_spring.domain.chat.chatroom.controller;

import lombok.RequiredArgsConstructor;
import org.springframework.validation.BindingResult;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import space.space_spring.argumentResolver.jwtLogin.JwtLoginAuth;
import space.space_spring.argumentResolver.userSpace.UserSpaceAuth;
import space.space_spring.dto.chat.request.CreateChatRoomRequest;
import space.space_spring.dto.chat.request.JoinChatRoomRequest;
import space.space_spring.dto.chat.response.ChatSuccessResponse;
import space.space_spring.dto.chat.response.CreateChatRoomResponse;
import space.space_spring.dto.chat.response.ReadChatRoomMemberResponse;
import space.space_spring.dto.chat.response.ReadChatRoomResponse;
import space.space_spring.domain.chat.chatroom.model.request.CreateChatRoomRequest;
import space.space_spring.domain.chat.chatroom.model.request.JoinChatRoomRequest;
import space.space_spring.domain.chat.chatroom.model.response.ChatSuccessResponse;
import space.space_spring.domain.chat.chatroom.model.response.CreateChatRoomResponse;
import space.space_spring.domain.chat.chatroom.model.response.ReadChatRoomMemberResponse;
import space.space_spring.domain.chat.chatroom.model.response.ReadChatRoomResponse;
import space.space_spring.exception.CustomException;
import space.space_spring.response.BaseResponse;
import space.space_spring.service.ChatRoomService;
import space.space_spring.domain.chat.chatroom.service.component.ChatRoomService;
import space.space_spring.service.S3Uploader;

import java.io.IOException;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,24 +1,22 @@
package space.space_spring.entity;
package space.space_spring.domain.chat.chatroom.model;

import jakarta.annotation.Nullable;
import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import org.hibernate.annotations.Comment;

import java.time.LocalDateTime;
import java.time.ZoneId;
import space.space_spring.entity.BaseEntity;
import space.space_spring.entity.Space;

@Entity
@Getter
@Builder
@NoArgsConstructor
@AllArgsConstructor
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Comment("채팅방")
@Table(name = "Chat_Room")
public class ChatRoom extends BaseEntity{
public class ChatRoom extends BaseEntity {

@Id
@GeneratedValue
Expand All @@ -39,6 +37,14 @@ public class ChatRoom extends BaseEntity{
@Column(name = "chat_room_img")
private String img;

@Builder(access = AccessLevel.PRIVATE)
private ChatRoom(Long id, Space space, String name, @Nullable String img) {
this.id = id;
this.space = space;
this.name = name;
this.img = img;
}

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

여기서 @nullable 어노테이션을 이용한 인자 유효성 검증을 위한 코드를 왜 추가하셨는지 궁금합니다!
ChatRoomController에서 HTTP request를 받는 입력 모델에서의 유효성 검증과정이 있고, 이 유효성 검증을 통과한 입력 모델이 서비스단으로 전달되고, 이 모델의 정보를 바탕으로 엔티티를 생성하는 로직인거 같은데
제 생각으로는 엔티티에서도 검증을 위한 코드가 있어야할까? 싶긴합니다

그리고 추가적으로 컨트롤러의 입력모델에서는 @NotNull 인데 엔티티에서는 @nullable 인 것 같습니다. 확인부탁드려요~~

Copy link
Member Author

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로 변경하면 해결될 것 같습니다~

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

그럼 혹시 해당 엔티티의 생성시 bean validation 을 어겼을 경우에 대한 후처리 로직도 있을까요??

Copy link
Member Author

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에 대한 처리가 있는지에 대한 것일까요?

Copy link
Collaborator

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에서 검증을 거친 인자에 대해서 엔티티 내부에서 한 번 더 안전장치를 거신 거라고 생각하면 될까요??

Copy link
Member Author

@hyunn522 hyunn522 Nov 29, 2024

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를 발생시키는 걸 말씀하시는건가요??

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

넵 맞습니다

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

아하 이중으로 에러 처리를 해줘도 괜찮을 것 같아요 다음에 다른 분들이랑 다같이 얘기해봅시다!

public static ChatRoom of(Space space, String chatRoomName, String chatRoomImgUrl) {
return ChatRoom.builder()
.space(space)
Expand All @@ -51,7 +57,4 @@ public void updateName(String name) {
this.name = name;
}

// // 양방향 매핑
// @OneToMany(mappedBy = "chatRoom", cascade = CascadeType.ALL)
// private List<UserChatRoom> userChatRooms;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package space.space_spring.domain.chat.chatroom.model;

import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.List;
import java.util.Objects;
import java.util.function.BiFunction;
import java.util.function.Function;
import space.space_spring.domain.chat.chatroom.model.dto.LastMessageInfoDto;
import space.space_spring.domain.chat.chatroom.model.response.ChatRoomResponse;

public class ChatRooms {

private List<ChatRoom> chatRooms;

private ChatRooms(List<ChatRoom> chatRooms) {
this.chatRooms = chatRooms;
}

public static ChatRooms of(List<ChatRoom> chatRooms) {
return new ChatRooms(chatRooms);
}

public List<ChatRoomResponse> toChatRoomResponses(
Long userId,
Function<ChatRoom, LastMessageInfoDto> lastMessageFinder,
BiFunction<Long, ChatRoom, Integer> unreadMessageCounter) {
return chatRooms.stream()
Copy link
Collaborator

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 에 접근하는 로직이 추가될떄에도 일급 컬렉션에게 책임을 맡기면 되므로 유용할 것 같습니다!

.map(chatRoom -> {
LastMessageInfoDto lastMessageInfo = lastMessageFinder.apply(chatRoom);
LocalDateTime lastUpdateTime = lastMessageInfo.getLastUpdateTime();
HashMap<String, String> lastContent = lastMessageInfo.getLastContent();

int unreadMsgCount = unreadMessageCounter.apply(userId, chatRoom);

return ChatRoomResponse.create(chatRoom, lastContent, String.valueOf(lastUpdateTime),
unreadMsgCount);
})
.filter(Objects::nonNull)
.toList();
}
}
Original file line number Diff line number Diff line change
@@ -1,23 +1,23 @@
package space.space_spring.entity;
package space.space_spring.domain.chat.chatroom.model;

import jakarta.annotation.Nullable;
import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import org.hibernate.annotations.Comment;

import java.time.LocalDateTime;
import space.space_spring.entity.BaseEntity;
import space.space_spring.entity.User;

@Entity
@Getter
@NoArgsConstructor
@AllArgsConstructor
@Builder
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Comment("유저별 채팅방")
@Table(name = "User_Chat_Room")
public class UserChatRoom extends BaseEntity{
public class UserChatRoom extends BaseEntity {

@Id
@GeneratedValue
Expand All @@ -39,6 +39,14 @@ public class UserChatRoom extends BaseEntity{
@Column(name = "last_read_time")
private LocalDateTime lastReadTime;

@Builder(access = AccessLevel.PRIVATE)
private UserChatRoom(Long id, ChatRoom chatRoom, User user, @Nullable LocalDateTime lastReadTime) {
this.id = id;
this.chatRoom = chatRoom;
this.user = user;
this.lastReadTime = lastReadTime;
}

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이 부분도 ChatRoom 과 동일하게 엔티티에 유효성 검증하는 코드를 작성하신 이유가 궁금합니다!

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

위와 같은 이슈입니다. 이 부분도 수정하겠습니다!

public static UserChatRoom of(ChatRoom chatRoom, User user, LocalDateTime lastReadTime) {
return UserChatRoom.builder()
.chatRoom(chatRoom)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package space.space_spring.domain.chat.chatroom.model;

import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
import space.space_spring.entity.User;

public class UserChatRooms {

private final List<UserChatRoom> userChatRooms;

private UserChatRooms(List<UserChatRoom> userChatRooms) {
this.userChatRooms = Collections.unmodifiableList(userChatRooms);
}
Copy link
Collaborator

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 엔티티들의 불변성 보장이 중요해보이므로 일급 컬렉션 생성 시에 읽기전용으로 제한을 거는것은 좋은 것 같습니다!


public static UserChatRooms of(List<UserChatRoom> userChatRooms) {
return new UserChatRooms(userChatRooms);
}

public boolean isUserJoined(Long userId) {
return userChatRooms.stream().anyMatch(userChatRoom -> userChatRoom.getUser().getUserId().equals(userId));
}

public List<User> getUsers() {
return userChatRooms.stream()
.map(UserChatRoom::getUser)
.collect(Collectors.toUnmodifiableList());
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM


}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package space.space_spring.domain.chat.chatroom.model.dto;

import lombok.AccessLevel;
import lombok.Builder;
import lombok.Getter;

import java.time.LocalDateTime;
import java.util.HashMap;

@Getter
public class LastMessageInfoDto {

private LocalDateTime lastUpdateTime;

private HashMap<String, String> lastContent;

@Builder(access = AccessLevel.PRIVATE)
private LastMessageInfoDto(LocalDateTime lastUpdateTime, HashMap<String, String> lastContent) {
this.lastUpdateTime = lastUpdateTime;
this.lastContent = lastContent;
}

public static LastMessageInfoDto of(LocalDateTime lastUpdateTime, HashMap<String, String> lastContent) {
return LastMessageInfoDto.builder()
.lastUpdateTime(lastUpdateTime)
.lastContent(lastContent)
.build();
}
}

Original file line number Diff line number Diff line change
@@ -1,19 +1,15 @@
package space.space_spring.dto.chat.request;
package space.space_spring.domain.chat.chatroom.model.request;

import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
import org.springframework.web.multipart.MultipartFile;

import java.util.List;

@Getter
@Setter
@Builder
public class CreateChatRoomRequest {

@Size(min = 2, max = 15, message = "채팅방 이름은 2자 이상, 15자 이내의 문자열이어야 합니다.")
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,11 @@
package space.space_spring.dto.chat.request;
package space.space_spring.domain.chat.chatroom.model.request;

import jakarta.validation.constraints.NotEmpty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

import java.util.List;

@Getter
@Builder
@NoArgsConstructor /* controller단 테스트 위해 적용*/
@AllArgsConstructor
public class JoinChatRoomRequest {

@NotEmpty(message = "1명 이상의 멤버를 초대해야 합니다.")
Expand Down
Loading
Loading