ADR-10: 표준 Go 프로젝트 레이아웃
| 날짜 | 작성자 | 영향 리포지토리 |
|---|---|---|
| 2025-12-23 | @KubrickCode | core |
Context
문제 정의
독립 Go 라이브러리로서(ADR-01: 코어 라이브러리 분리 참조), 코어는 외부 프로젝트에서 go get으로 사용 가능해야 함. 비표준 디렉토리 구조는 소비자와 생태계 도구에 마찰을 일으킴.
기술적 문제
go.mod가 하위 디렉토리에 중첩되어 있으면(예: src/pkg/go.mod), 외부 소비자가 모듈을 직접 import할 수 없음. replace directive를 사용해야 하며, 이는:
- 표준
go get워크플로우를 깨뜨림 - 의존성 업데이트에 수동 조율 필요
- Go 모듈 프록시 및 체크섬 데이터베이스와 호환 불가
- 생태계 도구(goimports, gopls 등) 혼란
Decision
루트 레벨 go.mod와 공개 패키지용 pkg/ 디렉토리로 표준 Go 프로젝트 레이아웃을 채택함.
specvital/core/
├── go.mod # 루트에 모듈 정의
├── go.sum
├── pkg/ # 공개 패키지
│ ├── domain/ # 도메인 모델
│ ├── parser/ # 파서 엔진
│ ├── source/ # Source 추상화
│ └── crypto/ # 암호화 유틸리티
└── ...소비자는 패키지를 직접 import:
go
import "github.com/specvital/core/pkg/parser"Options Considered
Option A: 루트 go.mod + pkg/ 디렉토리 (선택됨)
공개 패키지를 pkg/ 하위에 두는 표준 Go 프로젝트 레이아웃.
장점:
replacedirective 없이 직접 import- Go 모듈 프록시 및 체크섬 데이터베이스 호환
- 생태계 도구와 원활하게 동작
- Go 개발자에게 익숙한 구조
- 공개(
pkg/)와 내부(internal/) 패키지 명확한 분리
단점:
pkg/내 모든 패키지가 공개 API- API 안정성 유지에 규율 필요
internal/사용하지 않으면 내부 패키지 컴파일 타임 강제 없음
Option B: 중첩된 go.mod (예: src/pkg/go.mod)
하위 디렉토리에 모듈 정의.
장점:
- 다른 디렉토리 구성 선호도 허용
- 모노레포에서 다른 언어와 공존 가능
단점:
- 외부 소비자에게
replacedirective 필요 - 표준 Go 도구 워크플로우 깨짐
- 모듈 프록시 캐싱과 호환 불가
- 비표준으로 기여자에게 혼란
Option C: internal/ 전용
모든 패키지를 internal/ 디렉토리 하위에 배치.
장점:
- 컴파일 타임 강제: 외부 패키지가 import 불가
- 소비자 영향 없이 자유로운 리팩토링
단점:
- 라이브러리 목적과 모순: 외부 사용을 위해 설계됨
- 공개 API 노출 없음
- 재사용 가능한 라이브러리에 부적합
Consequences
Positive
마찰 없는 소비
- 외부 프로젝트에서 표준
go get github.com/specvital/core사용 - 수동
replacedirective 불필요 - 표준 도구로 의존성 업데이트 작동 (Dependabot, Renovate)
- 외부 프로젝트에서 표준
생태계 호환성
- Go 모듈 프록시가 모듈 캐시
- 체크섬 데이터베이스가 무결성 검증 제공
- goimports, gopls 정상 동작
개발자 경험
- Go 개발자에게 익숙한 표준 레이아웃
- 기여자 온보딩 시간 단축
pkg/에 명확한 공개 API 표면
Negative
API 안정성 책임
pkg/하위 모든 패키지가 공개 API임- 호환성 깨는 변경에 메이저 버전 범프 필요
- 완화: 노출하지 않을 구현 세부사항은
internal/사용
리팩토링 제약
pkg/내 패키지를 자유롭게 이름 변경/이동 불가- 완화: 보수적인 API 설계, 수정보다 확장 포인트 활용
패키지 가시성
| 디렉토리 | 가시성 | 용도 |
|---|---|---|
pkg/ | 공개 | 외부 프로젝트용 내보낸 API |
internal/ | 비공개 | 구현 세부사항 (필요시 사용) |
