개요
현재 파일 스토리지로 사용 중인 Cloudflare R2를 제거하고, Google Cloud Storage(GCS) 버킷으로 완전히 교체한다. 기존 StoragePort 인터페이스를 유지하면서 인프라 어댑터만 교체하므로 서비스 레이어 변경은 최소화된다.
배경
현재 구조
StoragePort (interface)
└── R2StorageAdapter ← Cloudflare R2 (aws-sdk-go-v2 + 커스텀 엔드포인트)
목표 구조
StoragePort (interface)
└── GCSStorageAdapter ← Google Cloud Storage (cloud.google.com/go/storage)
구현 범위
In Scope
R2StorageAdapter 제거, GCSStorageAdapter 신규 구현
- GCS 환경변수 추가 (R2 변수 제거)
provider.go 수정 (R2 → GCS 초기화)
env/.env.example 업데이트
cloud.google.com/go/storage 의존성 추가
Out of Scope
- 기존 R2에 저장된 데이터 마이그레이션
- Cloud CDN 연동
- Presigned URL / Signed URL 발급 기능
환경변수
제거
R2_ACCOUNT_ID=
R2_ACCESS_KEY_ID=
R2_SECRET_ACCESS_KEY=
R2_BUCKET_NAME=
R2_PUBLIC_URL=
추가
# Google Cloud Storage
GCS_BUCKET_NAME= # GCS 버킷 이름
GCS_PUBLIC_URL= # 공개 접근 Base URL
# e.g. https://storage.googleapis.com/{bucket-name}
GCS_CREDENTIALS_JSON= # 서비스 계정 JSON 키 전체 내용
# 미설정 시 ADC(Application Default Credentials) 사용
GCS_CREDENTIALS_JSON이 없으면 GKE / Cloud Run 환경의 Workload Identity / ADC로 자동 폴백됩니다.
구현 명세
신규 파일: internal/storage/infra/gcs_storage_adapter.go
type GCSConfig struct {
BucketName string
PublicURL string
CredentialsJSON string // Service Account JSON (선택, 없으면 ADC 사용)
}
type GCSStorageAdapter struct {
client *storage.Client
bucketName string
publicURL string
}
// StoragePort 인터페이스 구현
func (a *GCSStorageAdapter) Upload(ctx, key, body, size, contentType) error
func (a *GCSStorageAdapter) Delete(ctx, key) error
func (a *GCSStorageAdapter) GetPublicURL(key string) string
GCP 서비스 계정 최소 권한
roles/storage.objectCreator → 오브젝트 생성(업로드)
roles/storage.objectViewer → 오브젝트 조회
roles/storage.legacyBucketWriter → 오브젝트 삭제 포함
또는 커스텀 역할로 storage.objects.create, storage.objects.delete 만 부여.
업로드 경로 구조 (기존과 동일 유지)
{UploadType}/{id}/{uuid}{ext}
예시)
contest-banners/42/550e8400-e29b-41d4-a716-446655440000.jpg
user-profiles/7/f47ac10b-58cc-4372-a567-0e02b2c3d479.png
main-banners/3/6ba7b810-9dad-11d1-80b4-00c04fd430c8.webp
변경 파일 목록
| 파일 |
변경 유형 |
내용 |
internal/storage/infra/r2_storage_adapter.go |
삭제 |
R2 어댑터 제거 |
internal/storage/infra/gcs_storage_adapter.go |
신규 |
GCSStorageAdapter 구현 |
internal/storage/provider.go |
수정 |
R2 → GCS 초기화로 교체 |
env/.env.example |
수정 |
R2 변수 제거, GCS 변수 추가 |
go.mod / go.sum |
수정 |
cloud.google.com/go/storage 의존성 추가 |
테스트 전략
Unit Test
GCSStorageAdapter 메서드를 StoragePort mock으로 검증
GCS_BUCKET_NAME 누락 시 초기화 실패 케이스
Integration Test
testcontainers의 fake-gcs-server 컨테이너로 실제 GCS API 호출 검증
Upload → 오브젝트 생성 확인
Delete → 오브젝트 삭제 확인
GetPublicURL → URL 포맷 확인
Edge Cases
GCS_CREDENTIALS_JSON 미설정 시 ADC로 폴백
- 버킷 이름 누락 시 서버 시작 경고 로그 후 storage 엔드포인트 비활성화
완료 조건
개요
현재 파일 스토리지로 사용 중인 Cloudflare R2를 제거하고, Google Cloud Storage(GCS) 버킷으로 완전히 교체한다. 기존
StoragePort인터페이스를 유지하면서 인프라 어댑터만 교체하므로 서비스 레이어 변경은 최소화된다.배경
현재 구조
목표 구조
구현 범위
In Scope
R2StorageAdapter제거,GCSStorageAdapter신규 구현provider.go수정 (R2 → GCS 초기화)env/.env.example업데이트cloud.google.com/go/storage의존성 추가Out of Scope
환경변수
제거
추가
구현 명세
신규 파일:
internal/storage/infra/gcs_storage_adapter.goGCP 서비스 계정 최소 권한
또는 커스텀 역할로
storage.objects.create,storage.objects.delete만 부여.업로드 경로 구조 (기존과 동일 유지)
변경 파일 목록
internal/storage/infra/r2_storage_adapter.gointernal/storage/infra/gcs_storage_adapter.gointernal/storage/provider.goenv/.env.examplego.mod/go.sumcloud.google.com/go/storage의존성 추가테스트 전략
Unit Test
GCSStorageAdapter메서드를StoragePortmock으로 검증GCS_BUCKET_NAME누락 시 초기화 실패 케이스Integration Test
testcontainers의 fake-gcs-server 컨테이너로 실제 GCS API 호출 검증Upload→ 오브젝트 생성 확인Delete→ 오브젝트 삭제 확인GetPublicURL→ URL 포맷 확인Edge Cases
GCS_CREDENTIALS_JSON미설정 시 ADC로 폴백완료 조건
R2StorageAdapter및 R2 관련 환경변수 완전 제거/api/v1/storage/contest-banner,/api/v1/storage/user-profile) 정상 동작env/.env.example에 GCS 환경변수 문서화 및 R2 변수 제거