💡개발 환경
Java 11, Srping 2.7.X, MySQL
[TIL] 채팅 기능 ERD 설계 에서 ERD 설계를 진행 하였지만 구독한 채팅방의 정보와 해당 채팅방을 구독하고 있는 사용자의 정보를 List 값으로 저장하게 하면서 멘붕에 빠졌다.
도저히 이 방법으로는 프로젝트 기간 내에 해결할 수 없겠다 싶어서 호다닥 변경하였다.
회원과 채팅방의 관계는 회원(1):채팅방(N)을 가지며, 일대일 채팅인 점을 고려하여 1:N 매핑을 두번 맺게 하였다.
채팅방과 메세지의 관계는 채팅방(1):메세지(N)을 가진다!
[Web] 웹 소켓(Socket)에서 학습한대로 채팅 기능을 구현하기 위해서는 WebSocket을 사용해야 한다.
🚨 WebSocket만 사용하여 구현하더라도 충분히 채팅 기능을 완성할 수 있지만, WebSocket은 단순 통신 구조이기 때문에 채팅 메세지가 어떤 요청인지, 어떻게 처리해야 하는지에 대한 로직을 별도로 구현해야 해야 한다.
이는 가독성 측면에서도, 효율성 면에서도 부적절한 방법이라고 생각하였다.
:Simple Text Oriented Messaging Protocol의 약자로 메세징 전송을 효율적으로 하기 위한 프로토콜이다.
WebSocket은 메세지 브로커에서 클라이언트로 다량의 메세지가 빠른 속도로 전송되는 일반적인 메신저 구조에 매우 적합하여, STOMP와 외부 메세지 브로커를 함께 사용하는 것이 보편적이다.
💡 Spring에서 지원하는 STOMP를 사용하면 외부 메세지 브로커의 사용없이 STOMP의 기능 중 Simple In-Memory Broker를 이용해 SUBSCRIBER 중인 다른 클라이언트에게 메세지를 전송할 수 있다.
implementation 'org.springframework.boot:spring-boot-starter-websocket'
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
// sockJS Fallback을 이용해 노출할 endpoint 설정
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
// 웹소켓이 handshake를 하기 위해 연결하는 endpoint
registry.addEndpoint("/ws")
.setAllowedOriginPatterns("*")
.withSockJS();
}
//메세지 브로커에 관한 설정
@Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
// 서버 -> 클라이언트로 발행하는 메세지에 대한 endpoint 설정 : 구독
registry.enableSimpleBroker("/sub");
// 클라이언트->서버로 발행하는 메세지에 대한 endpoint 설정 : 구독에 대한 메세지
registry.setApplicationDestinationPrefixes("/pub");
}
}
WebSocketMessageBrokerConfigurer
를 구현한 WebSocketConfig
는 STOMP 엔드포인드를 설정하고, 메세지 브로커가 사용할 pub/sub
엔드포인트를 설정한다.
💡 withSockJS()
SockJS
는 WebSocket을 지원하지 않는 브라우저에서 HTTP의 Polling과 같은 방식으로 WebSocket의 요청을 수행하도록 도와준다.
🚨 SockJS를 사용할 경우, 클라이언트에서 WebSocket 요청을 보낼 때 설정한 엔드포인트 뒤에
/webSocket
를 추가해줘야 정상 작동된다.
@Entity
@Getter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class ChatRoom extends Auditable {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long roomId;
@ManyToOne
@JoinColumn(name = "sender_id")
private Member sender;
@ManyToOne
@JoinColumn(name = "receiver_id")
private Member receiver;
public void setSender(Member sender) {
this.sender = sender;
}
public void setReceiver(Member receiver) {
this.receiver = receiver;
}
}
@Entity
@Getter
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class ChatMessage {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long messageId;
@Column(nullable = false)
private String content;
@Column(nullable = false)
private LocalDateTime sendTime;
@ManyToOne
@JoinColumn(name = "sender_id")
private Member sender;
@ManyToOne
@JoinColumn(name = "room_id")
private ChatRoom chatRoom;
public void setMember(Member sender) {
this.sender = sender;
}
public void setChatRoom(ChatRoom chatRoom) {
this.chatRoom = chatRoom;
}
}
안녕하세요! 혤님! 수신인 테이블은 없는건가요? message테이블 = 수신인(상대방)이라고 생각하면 되는 걸까요??!