AmberAx

Go Standard Layout

· 4 min read
Go Standard Layout

Go 언어(Golang)는 명확하고 단순한 프로젝트 구성을 중시합니다. 특히 디렉터리 구조와 패키지 설계는 코드의 가독성, 확장성, 유지보수성을 좌우하는 중요한 요소입니다.

이번 글에서는 Golang Standards/Project Layout을 중심으로, Go 프로젝트의 디렉터리 구조와 패키지 설계에 대해 자세히 알아봅니다.


디렉터리 구조 #

Go 프로젝트에서 널리 참고되는 디렉터리 구조는 다음과 같습니다. 아래는 이를 트리 형태로 간단히 정리한 예입니다.

.
├── api                     # API 스펙 및 프로토콜 정의 파일 (OpenAPI/Swagger 등)
├── assets                  # 이미지, 로고 등 레포지토리 에셋
├── build                   # 패키징 및 지속적 통합(CI) 관련 스크립트와 설정
│   ├── ci                  # CI 도구 설정 (Travis, CircleCI 등)
│   └── package             # OS, 컨테이너, 클라우드 패키징 설정
├── cmd                     # 메인 애플리케이션 디렉터리 (실행 파일 이름과 동일한 디렉터리 사용)
│   └── myapp               # "myapp" 실행 파일을 위한 코드
├── configs                 # 설정 파일 템플릿과 기본 설정
├── deployments             # 배포 설정과 템플릿 (docker-compose, kubernetes, helm 등)
├── docs                    # 설계 및 사용자 문서
├── examples                # 애플리케이션 및 라이브러리 예제 코드
├── githooks                # Git hooks
├── internal                # 프로젝트 내에서만 사용하는 개인적인 코드
│   ├── app                 # 내부 애플리케이션 코드
│   └── pkg                 # 내부 애플리케이션에서 공유되는 코드
├── pkg                     # 외부 애플리케이션에서 사용할 수 있는 라이브러리 코드
│   └── mypubliclib         # 외부 사용을 위한 공개 라이브러리
├── scripts                 # 빌드, 설치, 분석을 위한 스크립트
├── test                    # 외부 테스트 앱과 테스트 데이터를 위한 디렉터리
│   ├── data                # 테스트 데이터를 위한 서브디렉터리
│   └── testdata            # Go가 무시하는 테스트 데이터 디렉터리
├── third_party             # 외부 도구 및 포크된 코드 (e.g., Swagger UI)
├── tools                   # 프로젝트에서 사용하는 도구 (/pkg, /internal 코드를 사용)
├── vendor                  # 프로젝트의 종속성 관리 디렉터리 (go mod vendor 명령어로 생성됨)
└── website                 # 프로젝트 웹사이트 데이터

이 디렉터리 구조는 프로젝트의 규모와 목적에 맞게 수정할 수 있습니다. 이제 주요 디렉터리의 역할과 활용법을 자세히 살펴보겠습니다.

1. cmd: 실행 파일 관련 디렉터리

cmd는 프로젝트의 실행 파일(entry point)을 포함합니다. 대규모 프로젝트에서는 여러 실행 파일을 포함할 수 있으므로 각각의 실행 파일 이름에 맞는 서브디렉터리를 생성합니다.

예:

cmd/
├── myapp/
│   └── main.go   # myapp 실행 파일 코드
├── admin/
│   └── main.go   # admin 도구 실행 파일 코드

Best Practice:

  • 실행 파일의 코드는 최소화하고, 주요 로직은 internal 디렉터리에 분리합니다.
  • main.go 파일은 초기화 및 종속성 주입(Dependency Injection)과 같은 역할만 담당하도록 설계합니다.

2. internal: 내부 전용 코드

internal 디렉터리는 프로젝트 내에서만 사용되는 코드를 저장합니다. Go는 internal 디렉터리에 있는 코드를 외부에서 임포트하지 못하도록 제한합니다. 이를 통해 모듈화된 코드를 안전하게 보호할 수 있습니다.

예:

internal/
├── app/          # 내부 애플리케이션 로직
└── database/     # 데이터베이스 관련 로직

활용 사례:

  • app/: 비즈니스 로직 구현.
  • database/: DB 연결과 쿼리 관련 코드.

3. pkg: 외부 사용을 위한 코드

pkg는 외부 애플리케이션에서 사용 가능한 라이브러리를 저장합니다. 공개 API를 제공하는 패키지와 내부 코드(internal)를 명확히 분리하여 재사용성을 높입니다.

예:

pkg/
└── mypubliclib  # 외부 사용을 위한 공개 라이브러리

Best Practice:

  • 외부 어플리케이션에서 사용되어도 괜찮은 패키지 모음입니다.
  • 여기에 넣기 전에 두번 고민하세요. 그만큼 신중하게 고민해야 합니다.
  • 이 패턴을 사용하는 유명한 Repository도 많지만, 추천하지 않는 커뮤니티도 많습니다.

패키지 설계 #

Go에서 패키지는 코드의 논리적 단위를 의미하며, 코드의 재사용성과 유지보수성을 결정하는 중요한 요소입니다. 다음은 효과적인 패키지 설계를 위한 가이드입니다.

1. 패키지 이름 규칙

패키지 이름은 짧고 의미가 명확해야 합니다. 일반적으로 단수형 이름을 사용하며, 기능을 직접적으로 나타내는 이름이 바람직합니다.

좋은 예

  • strconv: 문자열 변환을 위한 패키지.
  • logger: 로깅 관련 기능 제공.

피해야 할 예

  • common, utils: 의미가 모호하며, 어떤 기능을 제공하는지 직관적으로 알기 어렵습니다.

2. 패키지 설계 사례

1. 명확하지 않은 패키지 이름

// common/common.go
package common

func ConvertToUpper(input string) string {
    return strings.ToUpper(input)
}

// main.go
package main

import (
    "common"
    "fmt"
)

func main() {
    result := common.ConvertToUpper("hello")
    fmt.Println(result) // HELLO
}

2. 명확한 패키지 이름

// stringutils/stringutils.go
package stringutils

func ConvertToUpper(input string) string {
    return strings.ToUpper(input)
}

// main.go
package main

import (
    "fmt"
    "stringutils"
)

func main() {
    result := stringutils.ConvertToUpper("hello")
    fmt.Println(result) // HELLO
}

비교

  • 불분명한 이름 (common): 패키지가 어떤 역할을 하는지 추측하기 어렵습니다.
  • 명확한 이름 (stringutils): 패키지의 역할을 즉시 이해할 수 있습니다.

결론 #

Go 프로젝트에서 디렉터리 구조와 패키지 설계는 코드 품질의 기본입니다. 명확하고 일관된 구조는 유지보수성을 높이고 팀 협업을 원활하게 합니다.

이 글에서 소개한 가이드를 참고하여 프로젝트를 체계적으로 구성해보세요. 작은 프로젝트라도 올바른 구조를 채택하면 성장 과정에서 발생할 수 있는 복잡성을 줄이고, 대규모 프로젝트로 확장할 때 큰 이점을 가져다줄 것입니다.

지금 바로 적용해 보고, 필요에 따라 팀의 요구사항에 맞게 유연하게 조정하세요. 좋은 프로젝트 구조는 코드베이스를 더 나은 방향으로 이끄는 첫걸음입니다.

Did you find this post helpful?
Share it with others!