다크 모드
코딩 컨벤션
Kotlin 스타일
kotlin.code.style=official설정 사용allWarningsAsErrors옵션 지원 (프로젝트 속성으로 토글 가능)- JVM Target: 21 (Java 21)
- 컴파일러 옵션:
-opt-in=kotlinx.coroutines.ExperimentalCoroutinesApi-XXLanguage:+PropertyParamAnnotationDefaultTargetMode
정보
ktlint, detekt 등 별도 정적 분석 도구는 사용하지 않습니다. 코드 품질 분석은 Qodana를 통해 수행합니다.
네이밍 컨벤션
파일 네이밍
| 유형 | 패턴 | 예시 |
|---|---|---|
| Composable Screen | {Screen}Route.kt | HomeRoute.kt, LoopRoute.kt |
| ViewModel | {Screen}ViewModel.kt | HomeViewModel.kt |
| UiState | {Screen}UiState.kt | HomeUiState.kt |
| UiEvent | {Screen}UiEvent.kt | HomeUiEvent.kt |
| SideEffect | {Screen}SideEffect.kt | HomeSideEffect.kt |
| Env + EnvModule | {Screen}Env.kt | HomeEnv.kt |
| Navigation | {Screen}Navigation.kt | HomeNavigation.kt |
| EventTracker | {Screen}EventTracker.kt | HomeEventTracker.kt |
| API Interface | {Domain}Api.kt | LoopApi.kt, AuthApi.kt |
| DataSource | {Domain}RemoteDataSource.kt | LoopRemoteDataSource.kt |
| Local DataSource | {Domain}LocalDataSource.kt | LoopLocalDataSource.kt |
| Repository Interface | {Domain}Repository.kt | FeedRepository.kt |
| Repository Impl | {Domain}RepositoryImpl.kt | FeedRepositoryImpl.kt |
| Request DTO | {Action}RequestDto.kt | CreateLoopRequestDto.kt |
| Response DTO | {Action}ResponseDto.kt | GetLoopResponseDto.kt |
| Hilt Module | {Domain}Module.kt | NetworkModule.kt, ApiModule.kt |
| UseCase (fun interface) | {Action}UseCase | ObserveUserUseCase, JoinLoopUseCase |
| UseCase (구현) | {Action}UseCase.kt | ObserveUserUseCase.kt |
| Domain API typealias | ApiTypes.kt | domain/loop/ApiTypes.kt |
| Domain Model | {ModelName}.kt | CurrentUser.kt, Loop.kt |
클래스/인터페이스 네이밍
| 유형 | 패턴 | 예시 |
|---|---|---|
| ViewModel | {Screen}ViewModel | HomeViewModel |
| Sealed Interface (Event) | {Screen}UiEvent | HomeUiEvent |
| Sealed Interface (Effect) | {Screen}SideEffect | HomeSideEffect |
| Data Class (State) | {Screen}UiState | HomeUiState |
| Hilt Module | {Domain}Module | NetworkModule |
| Qualifier | @{Name}Identifier | @RefreshCurrentUserIdentifier, @JoinLoopIdentifier |
Compose 컨벤션
Composable 함수
Stability 최적화
@Immutable: UiState data class에 적용하여 Compose 안정성 보장@Stable: ViewModel, 변경 가능한 객체에 적용stability_config.conf: 프로젝트 루트에 Compose Stability 설정 파일 관리 (현재co.butbeautiful.truloop.core.data.model.*등록)
kotlin
@Immutable
internal data class HomeUiState(
val me: CurrentUser = CurrentUser.EMPTY,
val selectedTabIndex: Int = 0,
val error: TruloopError? = null,
)
@HiltViewModel
@Stable
internal class HomeViewModel @Inject constructor(/* ... */) : ViewModel()접근 제한자
- Feature 모듈의 UiState, UiEvent, SideEffect, ViewModel, Route는
internal로 선언 - Navigation Entry 함수 (
fun EntryProviderScope<Any>.{name}Entries(...))만public으로 외부에 노출 - API Interface, DataSource는
internal로 선언
아키텍처 컨벤션
MVI 패턴 규칙
| 규칙 | 설명 |
|---|---|
| 단방향 데이터 흐름 | View -> UiEvent -> ViewModel -> UiState -> View |
| 단일 UiEvent 핸들러 | onUiEvent(event: UiEvent) 함수 하나로 모든 이벤트 처리 |
| Immutable State | UiState는 data class로 정의하고 copy()로만 변경 |
| SideEffect는 Channel | 일회성 이벤트는 Channel로 전달 |
Repository 패턴 규칙
core:data모듈에 Repository 인터페이스와 구현체 (RepositoryImpl) 모두 위치- DataSource를 통해 API 호출 (Remote) 또는 DB 접근 (Local)
- DTO -> Domain Model 변환은 Repository에서 수행
Domain 모듈 규칙
- Domain 모듈은
fun interface로 UseCase를 정의 - API 타입은
typealias로 정의 (ApiTypes.kt)하고, Queries/Commandsdata class로 그룹화 - UseCase DI에는
@Qualifierannotation (@{Name}Identifier)을 사용 - Domain 모듈 간 의존성은 허용 (예:
domain:loop->domain:place,domain:user)
Env 패턴 규칙
kotlin
// Env: Feature에 필요한 UseCase를 data class로 캡슐화
data class HomeEnv(
val refreshUser: RefreshCurrentUserUseCase,
val observeUser: ObserveUserUseCase,
val joinLoop: JoinLoopUseCase
)
// EnvModule: Hilt Module에서 Env를 생성하여 제공
@Module
@InstallIn(ViewModelComponent::class)
class HomeEnvModule {
@Provides
fun providesEnv(
@RefreshCurrentUserIdentifier refreshCurrentUserUseCase: RefreshCurrentUserUseCase,
@ObserveUserIdentifier observeCurrentUser: ObserveUserUseCase,
@JoinLoopIdentifier joinLoop: JoinLoopUseCase,
): HomeEnv {
return HomeEnv(
refreshUser = refreshCurrentUserUseCase,
observeUser = observeCurrentUser,
joinLoop = joinLoop
)
}
}- Feature별 하나의 Env
data class+ EnvModule 쌍을 사용 - Env는
data class로 정의하고, Hilt@Module+@Provides를 통해 DI - ViewModel의 생성자 파라미터를 간결하게 유지
- UseCase 호출을 Env에 위임
패키지 구조
Feature 모듈
feature/{name}/src/main/java/co/butbeautiful/truloop/feature/{name}/
{Name}Route.kt -- Composable 진입점 (internal)
{Name}ViewModel.kt -- ViewModel (internal)
{Name}UiState.kt -- UI State (internal)
{Name}UiEvent.kt -- UI Event (internal)
{Name}SideEffect.kt -- Side Effect (internal)
{Name}Env.kt -- Env data class + EnvModule
{Name}Navigation.kt -- Navigation 3 entry 정의 (public)
{Name}EventTracker.kt -- Analytics 이벤트 트래커Core Data 모듈
core/data/src/main/java/co/butbeautiful/truloop/core/data/
api/ -- Retrofit API 인터페이스
ApiModule.kt -- API DI 모듈
LoopApi.kt
AuthApi.kt
...
datasource/ -- DataSource 구현
local/ -- 로컬 DataSource
LoopRemoteDataSource.kt
...
repository/ -- Repository 인터페이스 + 구현
RepositoryModule.kt
FeedRepository.kt
FeedRepositoryImpl.kt
...
model/
domain/ -- Domain 매핑 모델
request/ -- Request DTO
response/ -- Response DTO
mapper/ -- DTO <-> Domain 변환 매퍼
di/ -- 추가 DI 모듈Domain 모듈
domain/{name}/src/main/kotlin/co/butbeautiful/truloop/domain/{name}/
UseCases.kt -- fun interface UseCase 정의
ApiTypes.kt -- typealias API 타입 정의
api/ -- Queries/Commands data class (API 그룹)
core/
...
usecase/ -- UseCase 구현체
model/ -- Domain Model
di/ -- UseCase DI 모듈에러 처리 컨벤션
truloopExceptionHandler를 사용하여 CoroutineScope에서 에러를 UiState에 반영TruloopError인터페이스로 통일된 에러 표현 (core:ktx모듈)GeneralTruloopErrorsealed interface가 구체적인 에러 유형 정의:SnackBar— Snackbar로 표시할 에러Dialog— Dialog로 표시할 에러Specified— 서버의display_type에 따른 에러InternalError— 네트워크/서비스 이용 불가 등 내부 에러
kotlin
// ViewModel에서의 사용 패턴
private val exceptionHandler = truloopExceptionHandler { error ->
_uiState.update { it.copy(error = error) }
}
// exceptionHandler를 CoroutineScope에 적용
viewModelScope.launch(exceptionHandler) {
env.refreshUser()
}변경 이력
| 날짜 | 변경 내용 |
|---|---|
| 2026-03-10 | Navigation 3 패턴 반영, Env 패턴 수정 (data class + Module), Qualifier 네이밍 수정 (@{Name}Identifier), Repository/Domain 모듈 구조 정정, TruloopError 상세화, 접근 제한자 규칙 수정 |