다크 모드
DB 스키마 관리
truloop-core의 데이터베이스 스키마 관리 전략, Exposed ORM 테이블 정의, 마이그레이션 방식을 설명합니다.
스키마 관리 도구: Atlas
정보
스키마 변경은 별도의 database_schema Repository에서 관리합니다. truloop-core 코드에서 직접 DDL을 실행하지 않습니다.
선언적 스키마 관리
truloop은 전통적인 마이그레이션 파일 방식이 아닌, Atlas를 사용한 선언적(Declarative) 스키마 관리를 채택합니다.
| 항목 | 설명 |
|---|---|
| 도구 | Atlas |
| 스키마 파일 | ../database_schema/schema.pg.hcl |
| 방식 | 선언적 (원하는 상태 정의 → 자동 마이그레이션 SQL 생성) |
| Repository | database_schema (별도 저장소) |
작업 흐름
스키마 수정
database_schema/schema.pg.hcl 파일에서 원하는 테이블/컬럼 변경을 작성합니다.
차이 확인
Atlas가 현재 DB 상태와 HCL 파일을 비교하여 마이그레이션 SQL을 자동 생성합니다.
적용
생성된 SQL을 검토 후 적용합니다.
위험
절대 규칙: DB에 직접 DDL을 실행하지 않습니다. 모든 변경은 schema.pg.hcl을 통해야 합니다.
Exposed ORM 테이블
truloop-core는 Exposed ORM (JetBrains)을 사용하여 DB에 접근합니다. 테이블 정의는 :adapter:persistence 모듈에 위치합니다.
테이블 구조 예시
kotlin
// adapter/persistence/user/UsersTable.kt
object UsersTable : LongIdTable("users") {
val eid = varchar("eid", 32).uniqueIndex()
val username = varchar("username", 50).uniqueIndex()
val name = varchar("name", 100)
val phoneNumber = varchar("phone_number", 50).uniqueIndex()
val profileImageUrl = varchar("profile_image_url", 2048).nullable()
val socialLinks = jsonb<Map<String, String>>("social_links", Json).default(emptyMap())
val email = varchar("email", 255).nullable()
val emailVerifiedAt = timestampWithTimeZone("email_verified_at").nullable()
val locale = varchar("locale", 35).default("en-US")
val birthDate = date("birth_date").nullable()
val isProfilePrivate = bool("is_profile_private").default(false)
val isActive = bool("is_active").default(true)
val createdAt = timestampWithTimeZone("created_at").defaultExpression(CurrentTimestampWithTimeZone)
val updatedAt = timestampWithTimeZone("updated_at").defaultExpression(CurrentTimestampWithTimeZone)
val deletedAt = timestampWithTimeZone("deleted_at").nullable()
}테이블 타입
Exposed ORM 테이블 정의 시 세 가지 베이스 클래스를 사용합니다:
| 베이스 클래스 | 용도 | PK 방식 |
|---|---|---|
LongIdTable | 대부분의 엔티티 테이블 | Auto-increment id: Long |
CompositeIdTable | 복합 PK가 필요한 테이블 | 복합 키 (.entityId()) |
Table | Junction 테이블 (좋아요, 북마크 등) | 수동 PrimaryKey 지정 |
FK 분리 원칙
정보
테이블 간 FK 참조에 Exposed의 reference()를 사용하지 않고 long() primitive를 사용합니다. FK 무결성은 Atlas 스키마에서 DB 레벨로 관리합니다. 이를 통해 persistence 어댑터 간 의존성을 제거하고 독립적인 컴파일을 가능하게 합니다.
전체 테이블 목록
| 테이블 | Exposed 클래스 | 베이스 | 설명 |
|---|---|---|---|
users | UsersTable | LongIdTable | 사용자 정보 |
loops | LoopsTable | LongIdTable | 룹 (핵심 비즈니스 엔티티). availability_deadline nullable timestamptz 컬럼 포함 (가용시간 응답 마감 시각, null이면 전원 응답 시에만 COLLECTED) |
loop_participants | LoopParticipantsTable | CompositeIdTable | 룹 참여자 (PK: loopId + userId) |
loop_media | LoopMediaTable | LongIdTable | 룹에 첨부된 미디어. uq_loop_media_loop_eid 복합 유니크 인덱스 (loop_id + eid) |
loop_invite_tokens | LoopInviteTokensTable | LongIdTable | 룹 초대 토큰 |
loop_guest_participants | GuestParticipantsTable | LongIdTable | 비회원 참여자 |
loop_comments | LoopCommentsTable | LongIdTable | 룹 댓글 |
stories | StoriesTable | LongIdTable | 리캡 (내부명: Story) |
DB 연결
Connection Pool
HikariCP 7.0.2를 사용하여 커넥션 풀을 관리합니다. 설정은 AppConfig의 DB 섹션에서 로드됩니다. Pool 이름은 truloop-hikari-pool이며, Micrometer를 통한 메트릭 모니터링이 활성화되어 있습니다.
SSM 터널을 통한 로컬 접속
bash
# Dev DB 터널 시작 (localhost:54320)
make db-start-dev
# Dev DB 터널 종료
make db-stop-devbash
# 터널 상태 확인
make db-status필수 도구: AWS CLI, Session Manager Plugin, Pulumi, jq
정보
SSM Jump Service는 truloop-infra에서 환경별로 상시 관리됩니다.
트랜잭션 관리
UnitOfWork 패턴
Exposed의 트랜잭션을 UnitOfWork Port로 추상화합니다. Use Case 레벨에서 트랜잭션 경계를 설정합니다.
kotlin
// application/shared/port/UnitOfWork.kt
interface UnitOfWork {
suspend fun <T> transactional(
readOnly: Boolean = false,
block: suspend () -> T,
): T
}readOnly = true를 지정하면 읽기 전용 트랜잭션으로 실행됩니다- 기존 트랜잭션이 진행 중이면 재사용합니다
롤백 규칙:
- 블록이 정상 완료되면 커밋 (sealed result 실패 반환도 커밋)
- 예외가 throw되면 롤백
- 비즈니스 실패 시 롤백이 필요하면 sealed result 대신 예외를 throw
주의
규칙:
- Exposed 작업은 반드시
UnitOfWork.transactional로 감싸야 합니다 - 오류는 예외를 throw하여 처리합니다 (Either 패턴 미사용)
- Use Case 레벨에서 트랜잭션을 관리합니다
- 중첩 트랜잭션은 지양합니다
DB 공유
truloop-ai-server는 동일한 Aurora PostgreSQL 인스턴스를 읽기 전용으로 공유합니다. 주요 공유 테이블:
loops- 룹 정보loop_media- 미디어 정보content_templates- 콘텐츠 템플릿stories- 리캡 정보story_creation_history- 리캡 생성 이력
변경 이력
| 날짜 | 내용 |
|---|---|
| 2026-03-12 | 이미지 그룹핑 테이블 추가: loop_grouping_results (그룹핑 결과 캐시), loop_group_representative_overrides (대표 사진 오버라이드). loop_media에 uq_loop_media_loop_eid 복합 유니크 인덱스 추가 |
| 2026-03-11 | loops 테이블에 availability_deadline 컬럼 추가 (nullable timestamptz, 가용시간 응답 마감 시각) |
| 2026-03-10 | 소스 코드 기준으로 전면 검증: UsersTable 예시를 실제 스키마로 교체, 전체 테이블 목록을 탭별로 재구성 (누락 테이블 20개 이상 추가), 테이블 타입/FK 분리 원칙 섹션 추가, UnitOfWork readOnly 파라미터 반영, HikariCP 버전 7.0.2로 수정, DB 공유 테이블 목록 수정 |