[SpringBoot] WebSocket, STOMP - 실시간 채팅⑤, Service

SihoonCho·2023년 3월 26일
0
post-thumbnail

※ 읽기에 앞서


본 시리즈는 작성자의 이해와 경험을 바탕으로 실습 위주의 설명을 기반으로 작성되었습니다.
실습 위주의 이해를 목표로 하기 때문에 다소 과장이 많고 생략된 부분이 많을 수 있습니다.
따라서, 이론적으로 미흡한 부분이 있을 수 있는 점에 대해 유의하시기 바랍니다.

또한, SpringBoot 기반의 Backend 위주의 설명을 포함하고 있으니
Frontend 에 대해서는 별도로 참고자료를 찾아보시기를 권장드립니다.


📌 ChatRoomService


채팅방(ChatRoom)과 관련된 Service를 구현합니다.
ChatRoomRepository를 사용합니다.

기본 기능응용 기능으로 구분합니다.

  • 기본 기능: CRUD(Create, Read, Update, Delete) 기능
  • 응용 기능: CRUD(Create, Read, Update, Delete)를 제외한 기능

📖 기본 기능


@RequiredArgsConstructor
@Service
public class ChatRoomService {

    private final ChatRoomRepository chatRoomRepository;

    /** ChatRoom 조회 */
    @Transactional
    public ChatRoomResponseDto findById(final Long id) {
        ChatRoom entity = this.chatRoomRepository.findById(id).orElseThrow(
                () -> new IllegalArgumentException("해당 ChatRoom이 존재하지 않습니다. id = " + id));
        return new ChatRoomResponseDto(entity);
    }

    /** ChatRoom 생성 */
    @Transactional
    public Long save(final ChatRoomRequestDto requestDto) {
        return this.chatRoomRepository.save(requestDto.toEntity()).getId();
    }

    /** ChatRoom 수정 */
    @Transactional
    public Long update(final Long id, ChatRoomRequestDto requestDto) {
        ChatRoom entity = this.chatRoomRepository.findById(id).orElseThrow(
                () -> new IllegalArgumentException("해당 ChatRoom이 존재하지 않습니다. id = " + id));
        return entity.update(requestDto);
    }

    /** ChatRoom 삭제 */
    public void delete(final Long id) {
        ChatRoom entity = this.chatRoomRepository.findById(id).orElseThrow(
                () -> new IllegalArgumentException("해당 ChatRoom이 존재하지 않습니다. id = " + id));
        this.chatRoomRepository.delete(entity);
    }
}

기본 기능 특징

  • CRUD(CREATE, READ, UPDATE, DELETE): 생성, 조회, 수정, 삭제의 기본 4가지 기능

기본 기능 메소드

  • 조회, findById(): return ChatRoomResponseDto
  • 생성, save(): return Long
  • 수정, update(): return Long
  • 삭제, delete(): return void

📖 응용 기능


@RequiredArgsConstructor
@Service
public class ChatRoomService {

    private final ChatRoomRepository chatRoomRepository;

    /** ChatRoom 목록조회 - 작성순, List */
    @Transactional
    public List<ChatRoomResponseDto> findAllAsc() {
        Sort sort = Sort.by(Sort.Direction.ASC, "id");
        List<ChatRoom> chatRoomList = this.chatRoomRepository.findAll(sort);
        return chatRoomList.stream().map(ChatRoomResponseDto::new).collect(Collectors.toList());
    }

    /** ChatRoom 목록조회 - 최신순, List */
    @Transactional
    public List<ChatRoomResponseDto> findALlDesc() {
        Sort sort = Sort.by(Sort.Direction.DESC, "id");
        List<ChatRoom> chatRoomList = this.chatRoomRepository.findAll(sort);
        return chatRoomList.stream().map(ChatRoomResponseDto::new).collect(Collectors.toList());
    }
    
    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    /** ChatRoom 검색목록조회 - 작성순, List */
    @Transactional
    public List<ChatRoomResponseDto> findAllByRoomNameAsc(String roomName) {
        Sort sort = Sort.by(Sort.Direction.ASC, "id");
        List<ChatRoom> chatRoomList = this.chatRoomRepository.findAllByRoomNameContaining(roomName, sort);
        return chatRoomList.stream().map(ChatRoomResponseDto::new).collect(Collectors.toList());
    }
    
    /** ChatRoom 검색목록조회 - 최신순, List */
    @Transactional
    public List<ChatRoomResponseDto> findAllByRoomNameDesc(String roomName) {
        Sort sort = Sort.by(Sort.Direction.DESC, "id");
        List<ChatRoom> chatRoomList = this.chatRoomRepository.findAllByRoomNameContaining(roomName, sort);
        return chatRoomList.stream().map(ChatRoomResponseDto::new).collect(Collectors.toList());
    }

응용 기능 특징

  • 전체목록 및 검색목록 등 '채팅방 목록 조회'
  • 검색목록은 '채팅방 이름에 검색어를 포함'하는 채팅방 목록
    • this.chatRoomRepository.findAllByRoomNameContaining()

응용 기능 메소드

  • findAllAsc(): return List<ChatRoomResponseDto>
  • findAllDesc(): return List<ChatRoomResponseDto>
  • findAllByRoomNameAsc(): return List<ChatRoomResponseDto>
  • findAllByRoomNameDesc(): return List<ChatRoomResponseDto>

📌 ChatMessageService


채팅 메세지(ChatMessage)와 관련된 Service를 구현합니다.
ChatRoomRepositoryChatMessageRepository 모두 사용합니다.

기본 기능응용 기능으로 구분합니다.

  • 기본 기능: CRUD(Create, Read, Update, Delete) 기능
  • 응용 기능: CRUD(Create, Read, Update, Delete)를 제외한 기능

📖 기본 기능


@RequiredArgsConstructor
@Service
public class ChatMessageService {

    private final ChatRoomRepository chatRoomRepository;
    private final ChatMessageRepository chatMessageRepository;

    /** ChatMessage 조회 */
    @Transactional
    public ChatMessageResponseDto findById(final Long chatMessageId) {
        ChatMessage chatMessageEntity = this.chatMessageRepository.findById(chatMessageId).orElseThrow(
                () -> new IllegalArgumentException("해당 ChatMessage가 존재하지 않습니다. chatMessageId = " + chatMessageId));
        return new ChatMessageResponseDto(chatMessageEntity);
    }

    /** ChatMessage 생성 */
    @Transactional
    public Long save(final Long chatRoomId, final ChatMessageRequestDto requestDto) {
        ChatRoom chatRoomEntity = this.chatRoomRepository.findById(chatRoomId).orElseThrow(
                () -> new IllegalArgumentException("해당 ChatRoom이 존재하지 않습니다. chatRoomId = " + chatRoomId));
        requestDto.setChatRoom(chatRoomEntity);
        return this.chatMessageRepository.save(requestDto.toEntity()).getId();
    }

    /** ChatMessage 삭제 */
    @Transactional
    public void delete(final Long chatMessageId) {
        ChatMessage chatMessageEntity = this.chatMessageRepository.findById(chatMessageId).orElseThrow(
                () -> new IllegalArgumentException("해당 ChatMessage가 존재하지 않습니다. chatMessageId = " + chatMessageId));
        this.chatMessageRepository.delete(chatMessageEntity);
    }
}

기본 기능 특징

  • CRUD(CREATE, READ, UPDATE, DELETE) 중 수정 기능을 제외한 3가지 기능
  • 매개변수(파라미터)로 주어지는 chatRoomId, chatMessageId 구분에 주의
  • 데이터 생성(CREATE) 시 ChatRoomChatMessage1:N의 관계로 매핑되는 것에 주의
    • ChatRoom : ChatMessage = 1 : N
    • requestDto.setChatRoom(chatRoomEntity);

기본 기능 메소드

  • 조회, findById(): return ChatMessageResponseDto
  • 생성, save(): return Long
  • 삭제, delete(): return void

📖 응용 기능


@RequiredArgsConstructor
@Service
public class ChatMessageService {

    private final ChatRoomRepository chatRoomRepository;
    private final ChatMessageRepository chatMessageRepository;

    /** 특정 채팅방 ChatMessage 목록조회 - 작성순, List, ChatRoomId 검색 */
    @Transactional
    public List<ChatMessageResponseDto> findAllByChatRoomIdAsc(final Long chatRoomId) {
        ChatRoom chatRoomEntity = this.chatRoomRepository.findById(chatRoomId).orElseThrow(
                () -> new IllegalArgumentException("해당 ChatRoom이 존재하지 않습니다. chatRoomId = " + chatRoomId));
        Sort sort = Sort.by(Sort.Direction.ASC, "id");
        List<ChatMessage> chatMessageList = this.chatMessageRepository.findAllByChatRoom(chatRoomEntity, sort);
        return chatMessageList.stream().map(ChatMessageResponseDto::new).collect(Collectors.toList());
    }
    
    /** 특정 채팅방 ChatMessage 목록조회 - 최신순, List, ChatRoomId 검색 */
    @Transactional
    public List<ChatMessageResponseDto> findAllByChatRoomIdDesc(final Long chatRoomId) {
        ChatRoom chatRoomEntity = this.chatRoomRepository.findById(chatRoomId).orElseThrow(
                () -> new IllegalArgumentException("해당 ChatRoom이 존재하지 않습니다. chatRoomId = " + chatRoomId));
        Sort sort = Sort.by(Sort.Direction.DESC, "id");
        List<ChatMessage> chatMessageList = this.chatMessageRepository.findAllByChatRoom(chatRoomEntity, sort);
        return chatMessageList.stream().map(ChatMessageResponseDto::new).collect(Collectors.toList());
    }

응용 기능 특징

  • 특정 채팅방의 '채팅 내역 조회'
  • ChatRoomChatMessage처럼 1:N 관계에서,
    N에 해당하는 모든 데이터를 조회하는 방법 2가지 존재
    • 방법1) ChatRoom Entity의 get() 메소드를 통한 접근
    • 방법2) ChatRoom Entity를 가지는 ChatMessage를 조회하는 JPA를 통한 접근
  • ChatMessageService는 '방법2'를 채택하여 사용
    • ChatMessage 테이블에서 ChatRoom Entity를 검색하는 방법
    • JPA Repository에서 findAllByChatRoom_Id()를 통한 접근방법도 있으나
      ChatRoomChatMessage를 강제로 JOIN 한 뒤 id를 찾기 때문에
      불필요한 JOIN 문제가 있음

응용 기능 메소드

  • findAllByChatRoomIdAsc(): return List<ChatMessageResponseDto>
  • findAllByChatRoomIdDesc(): return List<ChatMessageResponseDto>

📌 결론


실시간 채팅과 관련하여 실질적인 기능을 담당하는
ChatRoomService, ChatMessageService 구현이 끝났습니다.

다음 시간에는 이번 시간에 구현한 Service를 사용하여
Client로부터의 WebSocket, STOMP 통신 요청과 응답을 위한
WebSocketConfigController를 구현해보도록 하겠습니다.


본 시리즈는 SpringBoot 기반의 WebSocket, STOMP 실시간 채팅기능 구현을 목표로
최근 트렌드와 함께 WebSocket, STOMP의 정형화된 기초개념을 정립하기 위해 작성되었습니다.

WebSocket을 찾아 헤매는 모든 이들에게 이 글을 바칩니다.

본 시리즈는 작성자의 이해와 경험을 바탕으로 실습 위주의 설명을 기반으로 작성되었습니다.
실습 위주의 이해를 목표로 하기 때문에 다소 과장이 많고 생략된 부분이 많을 수 있습니다.
따라서, 이론적으로 미흡한 부분이 있을 수 있는 점에 대해 유의하시기 바랍니다.

또한, SpringBoot 기반의 Backend 위주의 설명을 포함하고 있으니
Frontend 에 대해서는 별도로 참고자료를 찾아보시기를 권장드립니다.
profile
꾸준히 노력하는 개발자

2개의 댓글

comment-user-thumbnail
2023년 8월 18일

포스트 시리즈 잘 보고있습니다.
이거 다음 포스트 controller부분은 없나요..??

답글 달기
comment-user-thumbnail
2024년 1월 29일

안녕하세요! 질문이 있어 댓글을 작성합니다.
ChatMessageService에서 requestDto.setChatRoom(chatRoomEntity); 이 부분에서
set같은 경우 사용을 지양한다고 공부했습니다.
setter를 만들지 않아서 오류가 발생하고 있습니다..!!
어느 부분이 잘못되었는지 조언을 받을 수 있을까요?

답글 달기