사용자가 채팅방에 메세지를 보낼 시, 단순히 채팅 방을 구독하고 있는 사용자에게 메세지 전달만 한다면 시간이 지난 후 대화 내용을 확인하지 못할 것이다. 따라서 채팅방을 구독 중인 사용자에게 채팅 내용을 전달하기 전에 DB에 저장하고, 저장에 성공한다면 전달하는 방식으로 구현할 계획이다.
이전 글에서 채팅 내용을 저장하기 위해 MongoDB를 사용한 이유에 대해 설명한 바 있다. 실제 상용화를 하기 위해 클라우드 환경에 구축을 해야한다. 따라서 AWS에 MongoDB를 설치하여 채팅 내용을 저장할 수 있도록 할 것이다.
EC2 인스턴스 생성 관련 글은 해당 링크에서 참고 가능하다. 새로운 EC2 인스턴스를 생성을 완료했다면 접속하여 MongoDB를 설치할 차례이다.
MongoDB의 public key를 다운로드하고 시스템에 추가하기 위해서 다음의 명령어를 입력한다.
sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv 9DA31620334BD75D9DCB49F368818C72E52529D4
다음은 MongoDB List 파일을 생성해야한다. 아래의 명령어를 입력한다.
echo "deb [ arch=amd64 ] https://repo.mongodb.org/apt/ubuntu bionic/mongodb-org/4.0 multiverse" | sudo tee /etc/apt/sources.list.d/mongodb-org-4.0.list
이제 MongoDB 설치 후 실행보자. apt-get update로 로컬 패키지를 업데이트 해준 후 mongodb를 설치한다.
apt-get update
apt-get install -y mongodb-org
설치가 끝났다면 MongoDB를 실행시켜보고, 상태를 확인해보자.
systemctl start mongod # 실행
systemctl status mongod # 실행 상태 확인
정상적으로 동작하는 것을 확인할 수 있다.
위 처럼 단순히 MongoDB를 설치하기만 했다면 채팅 내용을 저장하기 위해 외부에서 접근을 하더라도 원하는 동작을 수행하지 못한다. 따라서 외부 접근을 허용해야 한다.
우선 EC2 인바운드 규칙 수정을 통해 할당한 ip와 port를 외부에서 접근 가능하도록 수정해야한다.
EC2의 보안 그룹 탭에서 인바운드 규칙 편집을 통해 쉽게 변경할 수 있다.
필자는 스프링 부트가 실행되고 있는 서버 ip 주소와 현재 사용 중인 ip에 대해 MongoDB의 기본 포트 번호인 27017번 포트를 개방했다.
다음 명령어를 통해 MongoDB 설정 파일을 편집할 수 있다
vi /etc/mongod.conf
설정 파일을 아래와 같이 수정하여 MongoDB 서버가 외부에서의 접근을 허용하도록 설정할 수 있다.
Spring boot에서 로컬에 설치한 MongoDB에 접근할 때, 접근할 수 있는 계정 정보를 입력하지 않아도 접근할 수 있다. 하지만 EC2에 설치한 MongoDB에 접근하기 위해서 새로운 계정을 생성하고, 계정 정보를 Spring boot 설정 파일에 작성해야 한다.
새로운 계정을 생성하기 위해서, EC2에 접속한 후 mongo
명령어를 입력하여 MongoDB Shell에 접속한다. 아래의 방법을 순차적으로 진행하여 새로운 계정을 생성한다.
# admin 계정 접속
use admin
# 외부 접속용 계정 생성
db.createUser({
user: 'username',
pwd: 'password',
roles: ['userAdminAnyDatabase']
})
db.createUser()는 현재 사용하고 있는 데이터베이스에 새로운 사용자를 추가한다. 필드로 사용자 이름, 비밀번호, 권한을 지정할 수 있다. MongoDB에서 사용자에게 지정 가능한 권한은 MongoDB Built-In Roles에서 확인할 수 있다. 참고로 userAdminAnyDatabase는 어느 데이터베이스든 사용자를 생성하고 제거할 수 있다는 것을 의미한다.
MongoDB Shell에 접속하여 관리자 인증하는 방법은 두 가지가 있다.
db.auth() 사용하기
use admin
명령어로 DB에 접근한 후, db.auth('username', 'password') 명령어로 인증할 수 있다. 인증에 성공한다면 1이 출력될 것이다.
Shell에서 바로 인증하기
mongo -u username -p password --authenticationDatabase admin
명령어를 통해 따로 DB 접근 없이 인증할 수 있다.
# example_db에 접속
use example_db
# 외부 접속용 계정 생성
db.createUser({
user: 'username',
pwd: 'password',
roles: ['dbOwner']
})
관리자 계정 생성 방법과 매우 유사하다. 참고로 dbOwner라는 권한은 해당 데이터베이스에 대한 모든 수정/삭제 권한을 가진다는 것을 의미한다.
Spring boot 프로젝트에서 MongoDB를 사용하기 위해서는 다음과 같은 의존성을 추가해야 한다.
implementation 'org.springframework.boot:spring-boot-starter-data-mongodb'
yml 파일에 아래의 설정을 추가해야 한다. 설정 내용은 위에서 구축한 MongoDB 설정에 맞춰서 기입하면 된다.
spring:
data:
mongodb:
host: # ip 주소
port: # port 번호
authentication-database: # 인증 시 사용되는 데이터베이스를 지정
username: # 사용자 계정
password: # 사용자 비밀번호
database: # 접근할 DB
ChatController.java
@RequiredArgsConstructor
@Controller
@Slf4j
public class ChatController {
private final ChatService chatService;
/**
* 사용자 간 채팅 내용 전달
*/
@CrossOrigin
@MessageMapping("/chat/message") //websocket "/pub/chat/message"로 들어오는 메시징 처리
public ResultResponse<SavedChatMessage> message(ReceivedChatMessage receivedChatMessage, @Header("Authorization") String Authorization) {
SavedChatMessage savedChatMessage = chatService.handleMessage(receivedChatMessage, Authorization);
return new ResultResponse<>(MESSAGE_HANDLING_SUCCESS, savedChatMessage);
}
}
사용자가 메세지를 전송하면 ChatController의 message 메서드에서 이를 처리한다.
ChatService.java
@Service
@Slf4j
@RequiredArgsConstructor
public class ChatService {
private final JwtUtil jwtUtil;
private final ChatRepository chatRepository;
private final SimpMessageSendingOperations messagingTemplate;
...
public SavedChatMessage handleMessage(ReceivedChatMessage receivedChatMessage, String authorization) {
String roomId = receivedChatMessage.getRoomId();
String memberId = findUserId(authorization);
SavedChatMessage savedChatMessage = SavedChatMessage.of(memberId, receivedChatMessage);
try {
saveMessage(roomId, savedChatMessage);
messagingTemplate.convertAndSend("/sub/chat/room/" + roomId, savedChatMessage); // /sub/chat/room/{roomId} - 구독
} catch (Exception e) {
log.error(e.getMessage());
throw new LinkyBusinessException(ErrorCode.MESSAGE_HANDLE_FAIL);
}
return savedChatMessage;
}
}
ChatService의 handleMessage에서 메세지를 처리한다. 처리할 메세지 DTO 객체를 생성 후 이를 DB에 저장하고, 해당 메세지를 구독 중인 채팅방에 전송한다. 만약 채팅 내용 저장을 실패한다면, 메세지 전송 또한 진행되지 않는다.
ChatRepository.class
@Repository
@Slf4j
@RequiredArgsConstructor
public class ChatRepository {
private final MongoTemplate mongoTemplate;
public void save(String messageJson, String roomId) {
try {
mongoTemplate.insert(messageJson, roomId);
} catch (Exception e) {
log.error(e.getMessage());
throw new LinkyBusinessException(ErrorCode.MESSAGE_SAVE_FAIL);
}
}
}
ChatRepository의 save 메서드에서 채팅 내용을 저장한다. 매개변수로 전달 받은 roomId는 MongoDB의 collection 이름으로 지정된다. 만약 collection이 존재하지 않으면 새로 생성된다. 하나의 collection이 하나의 채팅방의 모든 대화 내용을 저장한다. 이렇게 구현한 이유는 채팅 내용이 쌓일 수록 처리할 데이터의 양이 많아질 것이기 때문에, 효율적으로 저장하기 위함이다.
두 개의 브라우저를 열고 테스트를 진행한다. 한 쪽은 채팅을 보내는 쪽이고 다른 한 쪽은 받는 쪽이다. 우선 채팅을 보내보자.
stomp connet 이후 엔드 포인트를 /pub/chat/message로 지정 후, roomId, message, sendingTime 필드를 채워서 메세지를 전송했다.
아래 로그를 통해 성공적으로 전송된 것을 확인할 수 있다.
메세지를 받는 쪽 브라우저 화면이다. 메세지 전송 시 roomId를 "1"로 지정했기 때문에 subscribe destination을 /sub/chat/room/1로 지정했다. 로그를 통해 메세지를 성공적으로 전달 받은 것을 확인할 수 있다.
MongoDB Shell을 통해 채팅 내용이 정상적으로 저장 됐는지 확인해보자. 아래와 같은 명령어를 통해 확인할 수 있다.
저도 공부하는 입장이라 부족할 수 있습니다. 따라서 조언과 피드백은 항상 열려있습니다!
https://be-developer.tistory.com/53
https://planbs.tistory.com/entry/MongoDB-%EC%9D%B8%EC%A6%9Dauthorization-%EC%B6%94%EA%B0%80%ED%95%98%EA%B8%B0