위에서 리팩토링 전의 전체 코드를 확인할 수 있습니다.
코드를 보면 아시겠지만 전체적으로 책임의 분리, 예외처리 일관성 부족, 동시성 이슈 등이 부족합니다.
RoomUserEntity개선 - 전
이번에는 RoomUserEntity부터 보겠습니다.
@Document(collection = "chatroom")
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class RoomUserEntity{
@Id
private String id;
private Long roomId;
private String userNickName;
@Enumerated(value = EnumType.STRING)
private RoomUserStatus status;
}
엔티티가 아니라 Document 입니다. 우선 어째서 MongoDB에 이 Document를 저장하기로 했을지 생각해 보겠습니다.
- 빈번한 쓰기 및 읽기 작업
해당 Document는 Chatroom의 사용자를 관리하고 있는 것으로 보입니다. 이는 매우 빈번한 삽입, 수정 이벤트입니다. 이러한 동작은 RDB에서 성능 병목을 일으킬 수 있습니다.
반면 MongoDB는 Document 지향 데이터베이스로, 스키마가 유연하고 인덱스 유지에 대한 오버헤드가 적어 빈번한 쓰기 작업에 유리합니다.
게다가 샤딩을 통해 수평적 확장이 용이하므로, 사용자 수와 채팅방 수가 증가해도 성능 저하 없이 확장할 수 있습니다.
이런 확실한 장점이 있는 것은 알겠습니다. 그런데 과연 도입하는 게 맞는 걸까요?
MongoDB를 도입하는 데서 발생하는 문제 또한 분명 존재합니다.
- 분산된 DB
MySQL과 MongoDB라는 두 개의 DB로 분리돼 관리의 복잡성이 증가합니다. - 실시간성과 데이터 일관성
MySQL의 Chatroom 테이블에는 해당 Chatroom의 최대 인원이 존재합니다. 그렇다면 해당 Chatroom의 최대 인원을 확인하고 MongoDB에서 해당 Chatroom의 유효한 사용자를 카운트한 다음 입장 로직이 수행돼야 합니다. 이를 분산된 두 DB에서 구현하려면 별도의 동시성 제어 로직이 필요해집니다.
RoomUserEntity개선 - 후
@Entity
@Table(name = "chatroom_user")
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class ChatRoomUser extends BaseEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "chatroom_id", nullable = false)
private ChatRoom chatRoom;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "user_id", nullable = false)
private User user;
@Enumerated(value = EnumType.STRING)
@Column(nullable = false)
private ChatRoomUserStatus status;
@Builder
public ChatRoomUser(ChatRoom chatRoom, User user) {
this.chatRoom = Objects.requireNonNull(chatRoom);
this.user = Objects.requireNonNull(user);
this.status = ChatRoomUserStatus.NORMAL;
}
}
@RequiredArgsConstructor
public enum ChatRoomUserStatus {
NORMAL("정상"),
KICKED("추방당한 유저"),
EXIT("퇴장");
private final String description;
}
최종 저의 판단은 해당 데이터를 MongoDB가 아니라 MySQL을 통해 관리하는 게 맞다는 것입니다.
쓰기 작업의 성능을 일부 포기하더라도, 데이터의 일관성과 무결성을 확보하는 것이 더 중요하다고 판단했습니다.
따라서 해당 Document를 제거하고 ChatRoomUser라는 별도의 엔티티를 만들도록 하겠습니다.
별도 엔티티를 만드는 이유는 Chatroom과 User가 ManyToMany의 관계이기에 조인 테이블 역할을 하기 위해서입니다. 또한 추가로 status를 칼럼으로 넣어 추가적인 정보를 관리할 수 있습니다.
여기서 고민인 부분이 새롭게 생깁니다. 바로 ChatRoom 엔티티에 양방향 연관관계를 설정하느냐의 문제입니다.
개인적으로는 양방향 연관관계는 실수의 여지가 늘어난다고 생각합니다.
예를 들어 연관관계의 주인 즉 외래키를 가지고 있지 않은 곳에서 작업을 해도 DB에는 반영이 안 되기 때문입니다.
그래서 보통은 반드시 필요하다고 생각되는 부분만 사용하며 편의 메서드를 만들어 실수의 여지를 줄이고는 합니다. 여기서도 마찬가지로 ChatRoom에서 ChatRoomUser를 컬렉션으로 관리하면 복잡성이 오히려 늘어나는 것 같아 관련 로직은 별도의 비즈니스 로직이나 쿼리를 사용해 처리하도록 하겠습니다.
'PROJECT > Keepham' 카테고리의 다른 글
채팅방 API 서버 - ChatRoomService 개선(2) (2) | 2024.10.07 |
---|---|
채팅방 API 서버 - ChatRoomService 개선(1) (1) | 2024.09.30 |
채팅방 API 서버 - 엔티티 개선(1) (0) | 2024.09.28 |
Keepham - 리팩토링 시작 (0) | 2024.09.27 |