다크 모드
코딩 컨벤션
truloop-core의 코딩 규칙, 정적 분석 도구 설정, 네이밍 규칙을 설명합니다.
정적 분석 도구
Ktlint (v14.0.1 / CLI v1.8.0)
Kotlin 코드 포맷팅 도구입니다. 일관된 코드 스타일을 자동으로 적용합니다. buildSrc/kotlin-common.gradle.kts에서 전체 모듈에 적용됩니다.
bash
# 포맷 자동 수정
./gradlew ktlintFormat
# 포맷 검증만 수행
./gradlew ktlintCheckDetekt (v1.23.8)
Kotlin 정적 분석 도구입니다. 코드 냄새, 복잡도, 잠재적 버그를 검출합니다. 설정 파일은 gradle/detekt/detekt.yaml에 위치합니다.
bash
# 정적 분석 실행
./gradlew detekt통합 실행
bash
# 포맷 + 정적 분석 한 번에 실행
./gradlew ktlintFormat ktlintCheck detekt네이밍 규칙
패키지
- 기본 패키지:
co.butbeautiful.truloop.* - 도메인 기능별 패키지:
co.butbeautiful.truloop.{layer}.{feature}
클래스 네이밍
| 계층 | 패턴 | 예시 |
|---|---|---|
| Domain Entity | {Name} | Loop, User, Story (Recap의 내부명) |
| Value Object | {Name} | LoopStatus, Location |
| Use Case | {Action}{Entity}UseCase | CreateLoopUseCase, GetUserUseCase |
| Repository Port | {Entity}Repository | LoopRepository, UserRepository |
| Query Port | {Entity}Queries | LoopQueries, UserQueries |
| Persistence Adapter | Exposed{Entity}Adapter | ExposedLoopAdapter |
| External Adapter | {Service}{Purpose}Adapter | FcmNotificationAdapter, S3FileStorageAdapter |
| Controller (Web) | {Feature}Controller | LoopController, UserController |
| Handler (Web) | {Feature}Handler | MediaUploadHandler |
| Route | {feature}Routes() | loopAuthenticatedRoutes(), userPublicRoutes() |
| Exception | {Feature}Exceptions.{Name} | LoopExceptions.LoopNotFound |
| Event Handler | {Feature}{Action}Handler / {Feature}EventHandlersKt | LoopNotificationEventHandler, UserEventHandlersKt |
| Table (Exposed) | {Entity}sTable | UsersTable, LoopsTable |
| DI Module | {scope}Module | infrastructureModule, persistenceModule |
DI 어노테이션 규칙
| 어노테이션 | 용도 | 적용 대상 |
|---|---|---|
@Single | 싱글턴 (앱 수명 동안 1개) | Persistence Adapter, EventHandler |
@Factory | 매 요청마다 새 인스턴스 | UseCase, Controller/Handler |
@InjectedParam | 런타임 파라미터 | parametersOf(...) 전달 값 |
주의
@Provided는 사용하지 않습니다. 모든 의존성은 Koin의 통합 그래프(DSL 모듈 + Annotation Scan)에서 해결됩니다.
ID 규칙
EID (External ID)
- 외부 공개 ID: TSID 기반의
eid문자열 사용 - 내부 DB ID: 내부 FK/관계에만 사용, 절대 외부에 노출하지 않음
- API 응답에는 항상
eid를 포함
kotlin
// 올바름
data class LoopResponse(val eid: String, ...)
// 잘못됨 - 내부 ID 노출
data class LoopResponse(val id: Long, ...)Repository 설계 규칙
CQS (Command/Query Separation)
각 Aggregate마다 2개의 인터페이스를 정의합니다:
| 인터페이스 | 역할 | 예시 메서드 |
|---|---|---|
{Aggregate}Repository | Command (상태 변경) | findByEid, create, update, delete |
{Aggregate}Queries | Query (읽기 전용) | findByLoopId, findCompletedByEid, search |
- 단일 구현 클래스가 두 인터페이스를 모두 구현
- Reader/Writer/QueryRepository/Port 남발 금지 (YAGNI)
Web Layer 규칙
Route 파일 규칙
- Route 파일에서
UseCase.execute()직접 호출 금지 - Route는 HTTP 관심사만 처리 (파싱, 상태 코드, 응답)
- Controller/Handler가 UseCase 호출과 응답 매핑을 담당
인증 경계 규칙
authenticate("auth-jwt"),authenticate("internal-api")등 인증 경계는RouteRegistry에서만 설정- Feature Route 함수는 auth-neutral (인증 불가지론적)
- Public/Authenticated 혼합: 별도 함수로 분리 (
{feature}PublicRoutes(),{feature}AuthenticatedRoutes())
트랜잭션 규칙
- Exposed 작업은
UnitOfWork.transactional/transactionalRaise로 감싸기 - Use Case 레벨에서 트랜잭션 경계 설정
- 중첩 트랜잭션 지양
이벤트 규칙
- Domain Event 발행은
EventPublisherPort를 통해 수행 - Fire-and-forget: 예외를 발생시키지 않으며, try-catch 불필요
- EventBus (Bootstrap) → EventHandler 디스패치 → 에러 시 Sentry 보고
테스트 규칙
| 항목 | 규칙 |
|---|---|
| 프레임워크 | Kotest 6.1.4 FunSpec 중심 |
| 모킹 | MockK 1.14.9 |
| 격리 | 상태 공유 시 IsolationMode.InstancePerTest 사용 |
| 트랜잭션 | TestUnitOfWork 사용 |
| HTTP 테스트 | ktor-server-test-host + client mock |
| 커버리지 | Kover 0.9.7 (./gradlew koverHtmlReport) |
| 통합 테스트 | bootstrap/src/integrationTest/ |
설정 및 환경
AppConfig
bootstrap/src/main/resources/application.yaml에서 YAML 형식으로 설정을 관리합니다. 28개 설정 섹션을 지원하며, 필수 설정이 누락되면 시작 시 fast-fail합니다.
Review Mode
reviewMode.enabled가 true일 때, 허용된 전화번호만 통과합니다. 테스트/샌드박스에서 의도치 않게 활성화하지 않도록 주의합니다.
SQS Consumer
aws.sqsMediaUploadQueueUrl 또는 aws.sqsMediaReadyQueueUrl이 설정되고 테스트 모드가 아닌 경우에만 시작합니다.
변경 이력
| 날짜 | 내용 |
|---|---|
| 2026-03-10 | Ktlint(v14.0.1/CLI v1.8.0), Detekt(v1.23.8), Kotest(6.1.4), MockK(1.14.9), Kover(0.9.7) 버전 정보 갱신. Detekt 설정 파일 경로 명시. Event Handler 클래스 네이밍 패턴 추가. |