웅글웅글: Nest 게시판 + 채팅 9. 채팅

메밀·2024년 3월 21일
0

웅글웅글: NestJS

목록 보기
9/9
post-thumbnail

1. Chat Application

1) 뭘 만들었냐면

팀 프로젝트를 진행하며 Spring Boot로 아주 단순한 1:1 채팅을 구현했다.
이번에 NestJS로는 1:1, 단체 채팅, 메시지 읽음 처리 등 좀 더 그럴듯한(ㅋㅋㅋ) 채팅을 만들어 보았다.

2) NestJS 승리 🎉

크게 세 가지 부분에서 NestJS가 Socket 통신에 더 적합하다고 느꼈다.

  1. 자동설정: Config 클래스를 작성하지 않아도 된다.
  2. @WebSocketGateway 클래스 구조의 간결성
  3. Gateway DI의 편리함

자세히 살펴보자😆

2. 자동 설정

1) 비교

기본적으로 NestJS는 @WebSocketGateway 클래스로 웹소켓 설정과 이벤트 핸들러를 등록한다.

그러나 Spring Boot에서는 Config 클래스를 작성하고, 또 다른 클래스에서 이벤트 핸들러를 작성해야 한다.

적당한 자동설정을 통해 코드의 양이 획기적으로 줄어든다는 점에서 NestJS의 승리!

2) Spring Boot의 WebSocketConfig 클래스

Spring Boot 사용 시 WebSocketConfig 클래스를 작성해줘야 한다.
설정 값은 코드의 주석으로 대신한다.

@Configuration
@EnableWebSocketMessageBroker // WebSocket, STOMP 활성화
public class StompWebSocketConfig implements WebSocketMessageBrokerConfigurer {
	
    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        registry.addEndpoint("/stomp/chat") // 클라이언트가 WebSocket 연결할 엔드포엔트 등록
                .setAllowedOrigins("...생략")
                .withSockJS();
    }

    // 어플리케이션 내부에서 사용할 path 지정
    @Override
    public void configureMessageBroker(MessageBrokerRegistry registry) {
    	// Client에서 SEND 요청을 처리
        registry.setApplicationDestinationPrefixes("/pub");
        
        // /sub 주제를 구독하는 클라이언트에게 메시지 전송
        registry.enableSimpleBroker("/sub");
    }
}

3) NestJS의 경우

반면 NestJS는 Gateway 클래스에 설정과 이벤트 핸들러를 모두 작성할 수 있다.
필요한 설정값은 @WebSocketGateway 데코레이터의 메타데이터로 넘겨준다.
그리고 이 클래스에 이벤트 핸들러를 적으면 별도의 코드 작성 없이 이벤트 핸들러가 등록된다.

@WebSocketGateway({
  namespace: "chat", // 네임스페이스 설정
  cors: {
    origin: ["http://localhost:3000"]
  }
})
export class ChatGateway implements OnGatewayConnection, OnGatewayDisconnect {

  @WebSocketServer()
  server: Server;

  private chatClients: Set<string> = new Set();

  constructor(private readonly chatService: ChatService) {
  }

  handleConnection(socket: Socket) {
    // 이하 생략

4) 정확히 뭘 자동으로 해주나요🧐?

.Spring BootNest JS
WebSocket 서버 초기화설정 클래스를 작성해야 WebSocket 핸들러, 엔드포인트 등을 정의하고 WebSocket 서버 초기화@WebSocketGateway 데코레이터로 초기화 가능.
즉, NestJS가 내부적으로 소켓 서버 초기화 및 설정
핸들러 등록WebSocket 핸들러 정의하고 config 클래스에 등록해야 함.
혹은 @MessageMapping 어노테이션을 통해 핸들러를 작성하고, config에서 등록
@WebSocketGateway 클래스 내에서 WebSocket 핸들러를 정의, WebSocket 서버에 자동 등록
개발자는 별도의 등록 코드를 작성할 필요X
루트 URL 지정WebSocket 핸들러에 매핑할 루트 URL을 정의해야함.
클라이언트는 이를 통해 WebSocket 연결을 요청
@WebSocketGateway로 루트 URL 지정 가능

2. @WebSocketGateway

앞서 언급했듯, Spring Boot 환경에선 이벤트 핸들러가 (분리된) 클래스에 작성된다.
이때, 이벤트 핸들러를 작성하는 방법엔 크게 두 가지가 있다.

  1. WebSocket 핸들러 클래스 구현하기
  2. @MessageMapping 어노테이션으로 (이미 존재하는) 컨트롤러에 메소드 추가

이 중 나는 2번 방법으로 채팅을 구현했었다.

1) @MessageMapping vs. @SubscribeMessage

Spring Boot는 @MessageMapping을 통해, NestJS는 @SubscribeMessage를 통해 이벤트 핸들러를 작성한다.

2) 무엇이 다른가요?

@SubscribeMessage는 WebSocketGateway 클래스 안에, @MessageMapping은 이미 존재하는 컨트롤러 안에 작성한다.

즉, @MessageMapping은 HTTP 메소드와 함께 있고, @SubscribeMessage는 이벤트 핸들러끼리 모여 있다.

나는 WebSocketGateway 방식의 작성법이 더 명확하다고 생각한다.
컨트롤러 안에 HTTP 메소드와 채팅 이벤트 핸들러가 혼재하는 것 보다는,
이벤트 로직은 이벤트 로직끼리 있는 것이 간결해보인다.

(은근 이벤트 핸들러 보러 컨트롤러 가는 것도 귀찮다 🤣)

3) 반론 가능!

네가 WebSocket 핸들러 클래스를 만들고 이 클래스를 이벤트 핸들러로 등록했으면 되는 일 아니냐?

맞아요🤣

그런데 너무 간단한 채팅 구현이라 핸들러 메소드가 하나밖에 없었다.

그렇다고 치더라도 Config 클래스를 만들고 컨트롤러에 이벤트 핸들러를 두는 것보다,
Gateway 하나에 소켓 관련 코드가 모여있는 게 직관적이라고 느낀다.

3. DI 가능

이 클래스 자체를 DI 할 수 있다는 점에서 Gateway 방식의 편리함을 또 한번 느낄 수 있다.

1) 새 댓글 실시간 알림 기능

private async createComment(commentId: number, options: Partial<Comment>, boardUserId: number): Promise<CommentResponseDto> {
    // 댓글 저장 로직은 생략

    // 댓글 작성 시 글 주인에게 socket 알림 ⭐️⭐️⭐️
    this.alarmGateway.handleCommentCreated(new CommentCreatedAlarm(comment), boardUserId);

    return new CommentResponseDto(comment);
  }

이처럼 유저 A가 B의 게시글에 댓글을 달았을 때, B에게 실시간 알림을 보내는 로직을
AlarmGateway 클래스를 DI해서 재사용할 수 있다.

0개의 댓글