본문 바로가기

백엔드/Prisma + GraphQL

[인스타그램 클론코딩] 채팅기능 데이터 모델링

이번에 구현한 실시간 채팅에 포함하고 싶었던 기능은 다음과 같았습니다.

  • 특정 사용자를 선택해서 채팅방을 만들 수 있습니다.
  • 특정 채팅방의 알람을 끄거나 켤 수 있습니다.

 

위의 기능을 구현하기 위한 데이터 모델입니다.

  • Room은 채팅방에 대한 정보를 저장하는 테이블입니다.
  • 특정 채팅방에서 전송된 모든 Message는 해당 채팅방의 Room.messages에 연결됩니다.
  • 특정 채팅방에 참가한 모든 사용자(User)는 해당 채팅방의 Room.participants에 연결됩니다.
  • 특정 채팅방에 전송된 메세지의 알림을 켠 사용자(User)는 해당 채팅방의 Room.subscribers에 연결됩니다.
  • Message는 사용자가 보낸 메세지에 대한 정보를 저장하는 테이블입니다.
  • 사용자가 채팅방에서 메세지를 전송하면, 이것은 사용자에게 직접 전송되는 것이 아니라, Room에 연결되고, Room.participants와 Room.subscribers에 포함된 사용자에게만 Push Notification을 통해서 전송됩니다.
  • 이렇게 구성한 이유는 Message 데이터를 특정 User와 강하게 연결시켜 놓으면, 추후 단체 채팅이 가능하도록 업그레이드할 때 장애가 될 것 같았기 때문입니다.

실시간 채팅 데이터 구조

shema.prisma

위의 모델을 만들기 위해 Prisma 모델링 파일에 추가한 모델과 스키마입니다.

User 모델의 outbox 스키마에는 "outbox"라는 심볼을 정의해서 relation을 명확하게 정의했지만, 사실 필요없는 작업입니다.

다만 나중에 단체 채팅을 구현할 때, User와 Message가 outbox와 from 이외에 다른 스키마를 통해서 연결되어야 하기 때문에 일단 relation을 명확하게 정의했습니다.

model User {
    ...
    rooms               Room[]        @relation("rooms", references: [id])
    subscriptionRooms   Room[]        @relation("subscribers", references: [id])
    outbox              Message[]     @relation("outbox")
    expoPushToken       String        @default(value: "")
    ...
}

model Room {
    id              Int           @default(autoincrement()) @id
    participants    User[]        @relation("rooms", references: [id])
    subscribers    User[]        @relation("subscribers", references: [id])
    messages        Message[]
    createdAt       DateTime      @default(now())
    updatedAt       DateTime      @updatedAt
}

model Message {
    id            Int         @default(autoincrement()) @id
    room          Room        @relation(fields: [roomId], references: [id])
    roomId        Int
    from          User        @relation("outbox", fields: [fromId], references: [id])
    fromId        Int
    text          String      @default("")
    createdAt     DateTime    @default(now())
    updatedAt     DateTime    @updatedAt
}

기존 Prisma 모델: [인스타그램 클론코딩] Prisma2 데이터 모델링

 

위의 Prisma 모델로 부터 자동으로 생성된 MySQL 테이블 생성 코드입니다. FOREIGN KEYREFERENCES 부분을 살펴보면 Prisma 모델에서 정의한 모델과 스키마간의 연결이 테이블과 컬럼에 그대로 반영된 것을 확인할 수 있습니다.

참조: Prisma2 CLI를 사용하여 데이터베이스 테이블 생성

Room 테이블

CREATE TABLE `Room` (
	`id` INT(11) NOT NULL AUTO_INCREMENT,
	`createdAt` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
	`updatedAt` DATETIME(3) NOT NULL,
	PRIMARY KEY (`id`) USING BTREE
)
COLLATE='utf8mb4_unicode_ci'
ENGINE=InnoDB
AUTO_INCREMENT=110
;

Message 테이블

CREATE TABLE `Message` (
	`fromId` INT(11) NOT NULL,
	`id` INT(11) NOT NULL AUTO_INCREMENT,
	`roomId` INT(11) NOT NULL,
	`text` VARCHAR(191) NOT NULL DEFAULT '' COLLATE 'utf8mb4_unicode_ci',
	`createdAt` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
	`updatedAt` DATETIME(3) NOT NULL,
	PRIMARY KEY (`id`) USING BTREE,
	INDEX `roomId` (`roomId`) USING BTREE,
	INDEX `fromId` (`fromId`) USING BTREE,
	CONSTRAINT `Message_ibfk_1` FOREIGN KEY (`roomId`) REFERENCES `instagureng`.`Room` (`id`) ON UPDATE CASCADE ON DELETE CASCADE,
	CONSTRAINT `Message_ibfk_2` FOREIGN KEY (`fromId`) REFERENCES `instagureng`.`User` (`id`) ON UPDATE CASCADE ON DELETE CASCADE
)
COLLATE='utf8mb4_unicode_ci'
ENGINE=InnoDB
AUTO_INCREMENT=284
;

 _room 릴레이션

CREATE TABLE `_rooms` (
	`A` INT(11) NOT NULL,
	`B` INT(11) NOT NULL,
	UNIQUE INDEX `_rooms_AB_unique` (`A`, `B`) USING BTREE,
	INDEX `_rooms_B_index` (`B`) USING BTREE,
	CONSTRAINT `_rooms_ibfk_1` FOREIGN KEY (`A`) REFERENCES `instagureng`.`Room` (`id`) ON UPDATE CASCADE ON DELETE CASCADE,
	CONSTRAINT `_rooms_ibfk_2` FOREIGN KEY (`B`) REFERENCES `instagureng`.`User` (`id`) ON UPDATE CASCADE ON DELETE CASCADE
)
COLLATE='utf8mb4_unicode_ci'
ENGINE=InnoDB
;

_subscribers 릴레이션

CREATE TABLE `_rooms` (
	`A` INT(11) NOT NULL,
	`B` INT(11) NOT NULL,
	UNIQUE INDEX `_rooms_AB_unique` (`A`, `B`) USING BTREE,
	INDEX `_rooms_B_index` (`B`) USING BTREE,
	CONSTRAINT `_rooms_ibfk_1` FOREIGN KEY (`A`) REFERENCES `instagureng`.`Room` (`id`) ON UPDATE CASCADE ON DELETE CASCADE,
	CONSTRAINT `_rooms_ibfk_2` FOREIGN KEY (`B`) REFERENCES `instagureng`.`User` (`id`) ON UPDATE CASCADE ON DELETE CASCADE
)
COLLATE='utf8mb4_unicode_ci'
ENGINE=InnoDB
;