다크 모드
코딩 컨벤션
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레이어 역할 분리
| 레이어 | 역할 | 의존 대상 |
|---|---|---|
| Handler | HTTP 요청 파싱, 검증, 응답 생성 | 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.go의getEnv,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.Logger를 With("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-tidy | go.mod/go.sum 정리 |
go-test-mod | 테스트 실행 |
go-build-mod | 빌드 확인 |
golangci-lint | 종합 린팅 (5분 타임아웃) |
변경 이력
| 날짜 | 내용 |
|---|---|
| 2026-03-10 | 초기 작성: 소스 코드 기반 컨벤션 문서화 |