Files
sodalive-backend-spring-boot/docs/agent-guides/코드스타일.md

5.9 KiB

코드 스타일

코드 스타일 규칙

1) 포맷/기본 규칙

  • .editorconfig 기준을 준수한다.
  • 인덴트: 공백 4칸.
  • 줄바꿈: LF.
  • 최대 라인 길이: 130.
  • 파일 끝 개행 유지, trailing whitespace 제거.

2) import 규칙

  • 와일드카드 import(*)를 사용하지 않는다.
  • 사용하지 않는 import를 남기지 않는다.
  • import alias(as)는 현재 코드베이스에서 사용 사례가 없으므로 지양한다.
  • 기존 파일의 import 정렬/그룹 스타일을 그대로 맞춘다.

3) 네이밍 규칙

  • 클래스/인터페이스/enum: PascalCase.
  • 함수/변수/파라미터: camelCase.
  • 상수: UPPER_SNAKE_CASE (companion object 내부 const val).
  • Request/Response DTO는 ...Request, ...Response 접미사를 유지한다.
  • 서비스/컨트롤러/리포지토리 명명은 역할 접미사(Service, Controller, Repository)를 유지한다.
  • 인터페이스의 기본 구현체는 접미사 Impl을 사용하지 않고 접두사 Default를 사용한다. 예: HomeRecommendationQueryRepository의 기본 구현체는 DefaultHomeRecommendationQueryRepository로 명명한다.

4) 패키지/코드 배치 규칙

  • 기존 로직을 수정하는 경우에는 기존 패키지 구조를 따른다.
  • 기존 로직 수정이 아닌 신규 API나 하위 코드는 kr.co.vividnext.sodalive.v2 패키지 하위에 작성한다.
  • 신규 도메인 또는 신규 기능 패키지 생성 시 kr.co.vividnext.sodalive.v2 바로 아래에 도메인 패키지를 먼저 만들고, 그 아래에 경량 헥사고날 아키텍처를 적용한다.
  • 클라이언트 공개 API의 화면/클라이언트 맞춤 조립 계층은 kr.co.vividnext.sodalive.v2.api.{기능} 하위에 둘 수 있다.
  • 여러 API나 내부 기능에서 재사용될 수 있는 도메인 기능은 kr.co.vividnext.sodalive.v2.{도메인} 하위에 별도 패키지로 둔다.
  • 공개 API 조립 패키지가 재사용 도메인 패키지를 호출하는 방향은 허용하지만, 재사용 도메인 패키지가 공개 API 조립 패키지에 의존하지 않는다.
  • 신규 도메인 또는 신규 기능의 기본 패키지 구조는 application, domain, port, adapter, dto를 사용한다.
  • application에는 use case, orchestration service, 트랜잭션 경계를 둔다.
  • domain에는 순수 정책, 점수 계산, 값 객체, 도메인 모델, 스냅샷 모델처럼 인프라 의존이 없는 코드를 둔다.
  • port에는 application이 필요로 하는 입력/출력 인터페이스를 둔다. 단순 내부 호출까지 억지로 port로 만들지 않는다.
  • adapter에는 web controller, JPA/QueryDSL persistence, cache, scheduler 등 외부 입출력 구현을 둔다.
  • dto에는 API 요청/응답 DTO와 adapter 경계에서 사용하는 DTO를 둔다.
  • 기존 패키지를 수정하는 작업에는 헥사고날 패키지 구조를 강제로 적용하지 않는다.
  • 기존 kr.co.vividnext.sodalive.v2 하위 코드가 이미 다른 구조로 작성되어 있으면 해당 작업의 범위 안에서만 기존 구조를 유지하고, 신규 도메인부터 헥사고날 구조를 적용한다.

5) 타입/널 처리

  • Kotlin 타입 시스템을 활용하고 nullable(?)를 명시한다.
  • 불필요한 Any/약한 타입을 피하고 구체 타입을 우선한다.
  • 기존 코드에서 !! 사용이 많지만, 신규 코드는 가능한 안전 호출/가드절/명시적 예외로 대체를 우선 고려한다.

6) API/응답 규칙

  • API 응답은 ApiResponse.ok(...), ApiResponse.error(...) 패턴을 따른다.
  • 컨트롤러는 도메인 예외를 직접 포맷하지 말고 SodaException을 던진다.
  • 인증 사용자 필요 시 @AuthenticationPrincipal(... ) member: Member? 패턴 + null 가드절을 사용한다.

7) 예외 처리 규칙

  • 비즈니스 예외는 SodaException(messageKey = "...") 우선 사용.
  • 사용자 노출 문구는 하드코딩보다 messageKey 기반 i18n을 우선한다.
  • 공통 예외 변환은 SodaExceptionHandler에서 수행하므로, 개별 컨트롤러에서 중복 처리하지 않는다.
  • 예외를 삼키는 빈 catch 블록을 금지한다.

8) 트랜잭션 규칙

  • 서비스 계층에서 @Transactional을 사용한다.
  • 조회 위주 메서드는 @Transactional(readOnly = true)를 우선한다.
  • 쓰기 로직은 메서드 단위 @Transactional로 경계를 명확히 한다.

9) 비동기/동시성 규칙

  • 비동기 처리는 Kotlin Coroutines 패턴을 따른다.
  • CoroutineScope(Dispatchers.IO) + launch + 예외 처리 패턴을 일관되게 유지한다.
  • 생명주기 종료 시 scope 정리(@PreDestroy) 패턴을 참고한다.
  • 다중 서버 인스턴스에서 같은 @Scheduled 작업이 동시에 실행될 수 있는 스케줄러는 Redisson 기반 분산 lock을 적용해 클러스터 전체에서 한 인스턴스만 작업을 실행하도록 한다.
  • 스케줄러 분산 lock은 기존 RedissonClient bean을 재사용하고, lock key는 작업 목적이 드러나도록 lock:{job-name} 형식으로 고정한다.
  • lock 획득 실패는 다른 인스턴스가 처리 중인 정상 skip으로 보고, 작업 본문은 lock을 획득한 경우에만 실행한다.
  • lock 해제는 finally에서 lock.isHeldByCurrentThread 확인 후 unlock()한다.
  • 스케줄러 작업 시간이 예측 가능하면 무기한 watchdog 의존보다 최악 실행 시간에 여유를 더한 명시적 leaseTime을 우선 검토한다.

10) 의존성 주입

  • 생성자 주입(primary constructor + private val)을 기본으로 사용한다.
  • 필드 주입보다 명시적 생성자 주입을 우선한다.

11) 주석

  • 의미 단위별로 주석을 작성한다.
  • 주석은 한 문장으로 간결하게 작성한다.
  • 주석은 코드의 의도와 구조를 설명한다.
  • 주석은 코드 변경 시 업데이트를 잊지 않는다.