Skip to content

코딩 컨벤션

truloop-media-service에서 사용하는 Go 코딩 컨벤션, 아키텍처 패턴, 프로젝트 구조 규칙을 정리합니다.


프로젝트 구조

internal/ 패키지 구조

모든 비즈니스 로직은 internal/ 하위에 위치합니다. Go의 internal 패키지 규칙에 따라 외부 모듈에서 import할 수 없습니다.

internal/
├── auth/          # JWT 인증 및 context 유틸리티
├── bufpool/       # sync.Pool 기반 메모리 버퍼 관리
├── config/        # 환경변수 기반 설정 로드
├── handlers/      # HTTP 핸들러 (요청 파싱, 응답 생성)
├── metrics/       # Prometheus 메트릭 정의
├── middleware/     # HTTP 미들웨어 (로깅, 메트릭, 에러 추적)
├── models/        # 데이터 구조체 및 설정 상수
├── server/        # HTTP 서버 설정, 라우팅, 미들웨어 체인
├── services/      # 외부 시스템 연동 비즈니스 로직
│   ├── idempotency/  # Redis 기반 중복 방지
│   ├── image/        # 이미지 처리 (리사이즈, WebP, EXIF)
│   ├── jobqueue/     # Redis 기반 작업 큐
│   ├── queue/        # SQS 메시지 발행
│   ├── storage/      # S3 업로드/다운로드
│   └── video/        # FFmpeg 비디오 처리
├── utils/         # 공용 유틸리티 함수
└── workers/       # 백그라운드 Worker 및 Worker Pool

레이어 역할 분리

레이어역할의존 대상
HandlerHTTP 요청 파싱, 검증, 응답 생성Service
Service외부 시스템 연동 (S3, SQS, Redis)외부 SDK
Worker백그라운드 비동기 처리Service
Model데이터 구조체, 설정 상수 정의없음

네이밍 컨벤션

파일 네이밍

  • 소문자 snake_case: media_worker.go, base_upload.go
  • 테스트 파일: {source}_test.go (예: processor_test.go)
  • 인터페이스 정의 파일: interface.go
  • Mock 파일: mock.go

타입 네이밍

  • 인터페이스: 행위 기반 이름 (예: Storage, Queue, Service, JobQueue)
  • 구현체: 구현 방식 기반 이름 (예: S3Storage, SQSQueue, RedisService, RedisJobQueue)
  • 핸들러: {역할}Handler (예: UploadHandler, InternalUploadHandler, HealthHandler)
  • 프로세서: Processor (예: image.Processor, video.Processor)
  • 설정 구조체: Config, VariantConfig, VideoVariantConfig

상수 네이밍

  • 에러 타입: ErrorType{이름} (예: ErrorTypeBadRequest, ErrorTypeTokenExpired)
  • 미디어 타입: MediaContentType{타입} (예: MediaContentTypePhoto, MediaContentTypeVideo)
  • 변환 타입: Variant{타입} (예: VariantOptimized, VariantThumbnail)

환경변수 네이밍

  • 대문자 SNAKE_CASE: S3_BUCKET_NAME, MAX_MEDIA_PROCESSING_WORKERS
  • 기본값은 config.gogetEnv, getInt, getBool 등의 헬퍼 함수로 관리

에러 처리 패턴

기본 원칙

  • panic 대신 명시적 error 반환 사용
  • 에러 래핑 시 fmt.Errorf("context: %w", err) 패턴 사용
  • 치명적이지 않은 에러는 로그 후 계속 진행 (graceful degradation)

에러 응답 구조

go
// 구조화된 에러 응답 (ErrorDetail)
type ErrorDetail struct {
    Type        string           `json:"type"`          // 에러 타입 코드
    DisplayType DisplayType      `json:"display_type"`  // 클라이언트 표시 방식
    Message     LocalizedMessage `json:"message"`       // 다국어 메시지
}

Sentry 연동

  • middleware.CaptureError(ctx, err, extra): context에서 Sentry Hub을 추출하여 에러 보고
  • Sentry 미초기화 상태에서는 자동으로 skip (패스스루 미들웨어)
  • Repanic: true 설정으로 Sentry가 에러 캡처 후 panic을 다시 전파, RecoveryMiddleware에서 최종 처리

패턴 예시

go
// 치명적이지 않은 에러 — 로그 후 계속 진행
if err := h.redisQueue.Push(ctx, job); err != nil {
    h.logger.Error("Failed to push job to Redis queue", "error", err)
    middleware.CaptureError(ctx, err, map[string]interface{}{...})
    // Don't fail the upload, just log the error
}

// 치명적 에러 — 정리 후 반환
if err := h.queue.SendMessage(ctx, h.queueURL, msg); err != nil {
    // S3 파일 삭제, idempotency key 삭제 등 정리
    return nil, fmt.Errorf("failed to send to SQS: %w", err)
}

인터페이스 패턴

서비스 인터페이스

모든 외부 시스템 연동은 인터페이스를 통해 추상화합니다. 테스트에서 Mock으로 교체할 수 있습니다.

go
// services/storage/interface.go
type Storage interface {
    Upload(ctx context.Context, bucket, key string, body io.Reader, contentType string) error
    Download(ctx context.Context, bucket, key string) (io.ReadCloser, error)
    // ...
}

// services/queue/interface.go
type Queue interface {
    SendMessage(ctx context.Context, queueURL string, message interface{}) error
}

Mock 구현

각 서비스 패키지에 mock.go 파일로 테스트용 Mock을 제공합니다.


동시성 패턴

Worker Pool

  • DynamicPool: 최소/최대 Worker 수를 설정하고, 큐 깊이에 따라 동적으로 Worker를 추가
  • Task 인터페이스: Execute(ctx context.Context) error 메서드 구현
  • Context 기반 취소: 모든 Worker는 ctx.Done() 채널을 감시

MediaWorker

  • 고정 수 goroutine (maxWorkers)으로 Redis 큐를 blocking pop
  • sync.WaitGroup으로 graceful shutdown 시 현재 처리 중인 작업 완료 대기
  • shutdownChan을 close하여 모든 Worker에 종료 신호 전파

Mutex 사용

  • sync.Mutex/sync.RWMutex: Worker Pool 상태 관리, 변환 결과 수집
  • sync.Once: 서버 shutdown 중복 실행 방지

로깅 컨벤션

구조화된 로깅

log/slog 패키지를 사용하여 JSON 형식의 구조화된 로그를 출력합니다.

go
slog.Info("Processing media",
    "media_type", job.MediaType,
    "media_eid", job.MediaEID,
    "s3_key", job.OriginalS3Key)

로거 주입

각 컴포넌트는 생성 시 slog.LoggerWith("service", "name") 형태로 서비스명을 태깅합니다.

go
logger := slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
    Level: slog.LevelInfo,
})).With("service", "media-worker")

로그 레벨 규칙

레벨용도
Error복구 불가능한 오류, 데이터 손실 위험
Warn비정상적이지만 처리 가능한 상황 (예: 메타데이터 추출 실패)
Info주요 비즈니스 이벤트 (업로드 완료, 변환 성공 등)
Debug상세 디버깅 정보 (Worker 대기 상태, 큐 비어있음 등)

Prometheus 메트릭 컨벤션

네이밍 규칙

  • 네임스페이스: truloop_media
  • 서브시스템별 구분: http, media, s3, sqs, ffmpeg, redis, worker_pool, media_worker
  • Counter는 _total 접미사
  • Histogram은 _seconds 또는 _bytes 접미사
  • Gauge는 설명적 이름 (concurrent_operations, goroutine_count)

메트릭 등록

promauto.New* 함수를 사용하여 init() 시점에 자동 등록합니다 (metrics/metrics.go 파일에 집중 정의).


테스트 컨벤션

파일 구조

  • 유닛 테스트: {source}_test.go (같은 패키지)
  • 통합 테스트: _integration_test.go 접미사, Integration 포함 함수명
  • 에러 케이스 전용 테스트: _error_test.go 접미사

테스트 실행

bash
make test              # 전체 테스트 (race detector 포함)
make test-unit         # 유닛 테스트만 (-short 플래그)
make test-integration  # 통합 테스트만 (-run Integration)
make test-coverage     # 커버리지 리포트 생성

테스트 라이브러리

  • github.com/stretchr/testify: assertion, require, mock
  • Go 표준 testing 패키지
  • -race 플래그 항상 활성화

설정 관리

환경변수 기반 설정

모든 설정은 환경변수로 관리합니다. config.Load() 함수에서 기본값과 함께 로드합니다.

go
cfg := &Config{
    ServerAddr: getEnv("SERVER_ADDR", ":8080"),  // 기본값 제공
    RedisDB:    getInt("REDIS_DB", 0),            // 타입별 헬퍼 함수
    RedisTLS:   getBool("REDIS_TLS", false),
}

검증 규칙

  • 필수 필드 (S3_BUCKET_NAME, MEDIA_UPLOAD_QUEUE_URL, REDIS_ADDR)는 빈 값일 때 에러 반환
  • Redis DB는 0-15 범위 검증
  • 숫자/불리언 파싱 실패 시 기본값 사용 (silent fallback)

Graceful Shutdown

서버 종료 시 다음 순서로 정리합니다:

OS 시그널 수신

SIGINT 또는 SIGTERM 시그널을 수신합니다.

MediaWorker 종료

shutdownChan을 close하고 현재 처리 중인 작업 완료를 30초간 대기합니다.

Worker Pool 종료

S3 업로드 Worker Pool을 중지하고 남은 작업을 drain합니다.

HTTP 서버 종료

httpServer.Shutdown(ctx)으로 30초 타임아웃 내에 graceful shutdown합니다.

Redis 연결 정리

Redis Job Queue 연결을 close합니다.

Sentry 플러시

버퍼링된 에러 이벤트를 2초 내에 전송합니다.


Pre-commit Hooks

.pre-commit-config.yaml에 다음 훅이 설정되어 있습니다:

역할
go-fmt코드 자동 포매팅
go-vet의심스러운 코드 패턴 검사
go-mod-tidygo.mod/go.sum 정리
go-test-mod테스트 실행
go-build-mod빌드 확인
golangci-lint종합 린팅 (5분 타임아웃)

변경 이력

날짜내용
2026-03-10초기 작성: 소스 코드 기반 컨벤션 문서화