⌨️ 채팅방 생성 및 빌더와 팩토리메서드 패턴에 대한 고민으로 마무리
채팅방 생성을 아직 DB와 연결하지 않았지만 `Entity` 에서 가져다가 쓰는 방식으로 DTO를 만들었고, 그 과정에서 자주 사용하던 팩토리 메서드 방식의 생성 패턴과, 빌더 방식의 생성 패턴에 대한 고민을 하며 마무리하였다.
1. ChatRoom
채팅방 엔티티와 DTO 구현
1.1. ChatRoom
import com.example.chat.constant.MessageType;
import com.example.chat.service.ChatService;
import lombok.*;
import org.springframework.web.socket.WebSocketSession;
import java.util.HashSet;
import java.util.Set;
@AllArgsConstructor
@NoArgsConstructor(access = AccessLevel.PRIVATE)
@Getter
public class ChatRoom {
private String roomId;
private String name;
private Set<WebSocketSession> sessions = new HashSet<>();
public static ChatRoom of (String roomId, String name, Set<WebSocketSession> sessions){
return new ChatRoom(roomId, name, sessions);
}
@Builder
public ChatRoom(String roomId,String name){
this.roomId = roomId;
this.name = name;
}
public void handleActions( WebSocketSession session, ChatMessage chatMessage, ChatService chatService){
if(chatMessage.getType().equals(MessageType.ENTER)){
sessions.add(session);
chatMessage.updateMessage(chatMessage.getSender() + "님이 입장하셨습니다. ");
}
sendMessage(chatMessage, chatService);
}
1.2. ChatRoomDTO
import com.example.chat.entity.ChatRoom;
import org.springframework.web.socket.WebSocketSession;
import java.util.Set;
public record ChatRoomDto(
String roomId,
String name,
Set<WebSocketSession> sessions
) {
public ChatRoomDto from(ChatRoom entity){
return new ChatRoomDto(
entity.getRoomId(),
entity.getName(),
entity.getSessions()
);
}
}
채팅방을 만들 때 세션이 null 이 되는 문제
1) 팩토리메서드 패턴 사용 시 세션이 널리 된다.
public static ChatRoom of (String roomId, String name){
return new ChatRoom(roomId, name, null);
}
이유는 위 코드에서 본 것과 같이 최초 생성 시 `roomId`,`name` 만 받아서 생성하기 때문에 해당 패턴에서는 세션의 값을 null을 넣어주어야 한다.
따로 포스팅하겠지만, 위와 같은 이유로 필드 값이 많은 도메인에 대해 팩토리메서드 패턴으로 생성을 할 시 필요 없는 정보들에 대해 전부 null을 해주어야 하는 문제가 발생한다.
2) 세션 추가가 안 되는 이유??
빌더 패턴에서는 handleActions()의. add(session) 로직이 작동을 하여 세션 값을 넣어준다. 그러나 팩토리메서드에서는 해당 부분이 기능을 하지 않는지 값이 null로 나온다.
public ChatRoomDto from(ChatRoom entity, WebsSocketSession session){
return new ChatRoomDto(
entity.getRoomId(),
entity.getName(),
session.getSessions()
);
}
위와 같은 방식으로 DTO를 생성에서 활용해야 하나 고민 중이다. 혹은 정적팩토리메서드 방식으로 setter를 구현해서 수정
ToDo. 부분은 확실하게 팩토리메서드 방식으로도 세션을 받아오도록 코드를 짜서 수정토록 하겠다.
Solve) 코드를 멍청하게 짰었음..
DTO에서 add를 통해 값을 넣어주는 세션 정보를 초기값에 넣어주니 null 로 생성될 수 밖에 없었다.
즉 roomId,name 을 통해 만들어진 엔티티 ChatRoom 에 add 세션을 가능했지만,
처음부터 null이 들어간DTO를 통해 만들어진 엔티티 ChatRoom은 불변하므로 add가 불가능했던 것
import com.example.chat.entity.ChatRoom;
import org.springframework.web.socket.WebSocketSession;
import java.util.Set;
public record ChatRoomDto(
String roomId,
String name
) {
public ChatRoomDto from(ChatRoom entity){
return new ChatRoomDto(
entity.getRoomId(),
entity.getName()
);
}
}
2. ChatMessage
2.1. ChatMessage
import com.example.chat.constant.MessageType;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
@AllArgsConstructor
@NoArgsConstructor(access = AccessLevel.PRIVATE)
@Getter
public class ChatMessage {
private MessageType type;
private String roomId;
private String sender;
private String message;
public static ChatMessage of (MessageType type, String roomId,String sender,String message){
return new ChatMessage(type, roomId, sender, message);
}
public void updateMessage(String message){
this.message = message;
}
}
`@Setter`를 최대한 지양하기 위해 정적 팩토리메소드 방식으로 메시지 업데이트를 수정
2.2. ChatMessageDto
import com.example.chat.constant.MessageType;
import com.example.chat.entity.ChatMessage;
public record ChatMessageDto(
MessageType messageType,
String roomId,
String sender,
String message
) {
public ChatMessageDto from(ChatMessage entity){
return new ChatMessageDto(
entity.getType(),
entity.getRoomId(),
entity.getSender(),
entity.getMessage()
);
}
}
2.3. MessageType
public enum MessageType {
ENTER,
TALK;
}
추후 퇴장이나 메세지 방식에 따른 코드의 변경이나, 확장을 쉽게 수정하기 위해 별도의 enum으로 관리하기로 하였다.
3. ChatService
import com.example.chat.entity.ChatRoom;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.annotation.PostConstruct;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import java.io.IOException;
import java.util.*;
@Slf4j
@RequiredArgsConstructor
@Service
public class ChatService {
private final ObjectMapper objectMapper;
private Map<String, ChatRoom> chatRooms;
@PostConstruct
private void init(){
chatRooms = new LinkedHashMap<>();
}
public List<ChatRoom> findAllRoom(){
return new ArrayList<>(chatRooms.values());
}
public ChatRoom findRoomById(String roomId){
return chatRooms.get(roomId);
}
public ChatRoom createRoom(String name){
String randomId = UUID.randomUUID().toString();
ChatRoom chatRoom = ChatRoom.builder()
.roomId(randomId)
.name(name)
.build();
chatRooms.put(randomId, chatRoom);
return chatRoom;
}
public <T> void sendMessage(WebSocketSession session, T message){
try{
session.sendMessage(new TextMessage(objectMapper.writeValueAsString(message)));
} catch (JsonProcessingException e) {
throw new RuntimeException(e);
} catch (IOException e) {
log.error(e.getMessage(),e);
}
}
}
4. 흐름
전체적인 흐름은 어렵지 않다
1. 채팅방 생성
2. 채팅방에 입장 -> 이때 handleAction에서 Type = ENTER 일 시 클라이언트의 세션을 저장
3. 채팅방에 메시지 송신 -> Type =TALK 일 때 메세지 발송이 작동
5. 피드백
- 빌더 패턴과 팩토리메서드 패턴에 대한 고민하고 글 작성하기
- 팩토리메서드 패턴에서도 세션을 저장한 객체 만들 수 있도록 확인하기