Go 모듈 구성하기

Go 모듈 구성하기 (Organizing a Go module)

Go를 처음 배우는 개발자들이 가장 자주 하는 질문 중 하나가 "Go 프로젝트를 어떻게 구성해야 하죠?"예요 — 파일과 폴더를 어떤 구조로 배치할지 말이죠. 이 문서의 목표는 이 질문에 답하는 데 도움이 될 지침을 몇 가지 제시하는 것입니다. 문서를 가장 잘 활용하려면 튜토리얼과 모듈 소스 관리 문서를 먼저 읽어 Go 모듈의 기본기를 챙겨두는 걸 추천해요.

Go 프로젝트는 패키지, 명령줄 프로그램, 또는 둘의 조합을 담을 수 있어요. 이 가이드는 프로젝트 유형별로 구성되어 있습니다. (본문 전체에서 파일/패키지 이름은 전적으로 임의의 값입니다.)

출처: Go 공식 문서

기본 패키지 (Basic package)

기본적인 Go 패키지는 모든 코드가 프로젝트 루트 디렉터리에 있습니다. 프로젝트는 단일 모듈로 이루어지고, 모듈은 단일 패키지로 이루어지죠. 패키지 이름은 모듈 이름의 마지막 경로 구성 요소와 일치합니다. 단일 Go 파일만 필요로 하는 아주 단순한 패키지의 프로젝트 구조는 다음과 같아요.

project-root-directory/
  go.mod
  modname.go
  modname_test.go

이 디렉터리가 github.com/someuser/modname에 있는 GitHub 저장소로 올라간다고 가정하면, go.mod 파일의 module 줄은 module github.com/someuser/modname이 되어야 합니다. modname.go의 코드는 다음과 같이 패키지를 선언하죠.

package modname

// ... package code here

사용자들은 Go 코드에서 이 패키지를 import 해서 쓸 수 있어요.

import "github.com/someuser/modname"

Go 패키지는 여러 파일로 쪼갤 수 있고, 모든 파일은 같은 디렉터리 안에 있어요.

project-root-directory/
  go.mod
  modname.go
  modname_test.go
  auth.go
  auth_test.go
  hash.go
  hash_test.go

이 디렉터리의 모든 파일이 package modname을 선언합니다.

기본 커맨드 (Basic command)

기본적인 실행 프로그램(또는 명령줄 도구)은 복잡성과 코드 크기에 따라 구조가 달라져요. 가장 단순한 프로그램은 func main이 정의된 단일 Go 파일 하나로 충분합니다. 더 큰 프로그램은 모두 package main을 선언하는 여러 파일로 코드를 나눌 수 있어요.

project-root-directory/
  go.mod
  auth.go
  auth_test.go
  client.go
  main.go

여기서 main.go가 func main을 담고 있지만, 이건 관례일 뿐입니다. "main" 파일은 modname.go(적절한 modname 값에 대해)나 다른 어떤 이름이어도 돼요.

이 디렉터리가 github.com/someuser/modname GitHub 저장소로 올라간다고 가정하면, go.mod 파일의 module 줄은 다음과 같아야 해요.

module github.com/someuser/modname

사용자는 다음 명령으로 자기 머신에 설치할 수 있습니다.

$ go install github.com/someuser/modname@latest

지원 패키지를 둔 패키지 또는 커맨드

더 큰 패키지나 커맨드는 일부 기능을 지원 패키지(supporting package)로 떼어내는 게 유리할 때가 있어요. 처음에는 그런 패키지를 internal이라는 디렉터리에 두는 걸 권장합니다. 이렇게 하면 다른 모듈이 당신이 외부 노출을 원하지 않는 패키지에 의존하는 걸 막아주거든요. 다른 프로젝트는 우리 internal 디렉터리의 코드를 import 할 수 없으니, 외부 사용자를 깨뜨릴 걱정 없이 API를 리팩터링하고 전반적으로 물건을 옮겨 다닐 수 있어요. 패키지의 프로젝트 구조는 다음과 같습니다.

project-root-directory/
  internal/
    auth/
      auth.go
      auth_test.go
    hash/
      hash.go
      hash_test.go
  go.mod
  modname.go
  modname_test.go

modname.go는 package modname을, auth.go는 package auth를 선언하는 식이에요. modname.go는 다음과 같이 auth 패키지를 import 합니다.

import "github.com/someuser/modname/internal/auth"

internal 디렉터리에 지원 패키지를 둔 커맨드의 레이아웃도 거의 비슷한데, 루트 디렉터리의 파일(들)이 package main을 선언한다는 점만 다릅니다.

여러 패키지 (Multiple packages)

모듈은 여러 개의 import 가능한 패키지로 이루어질 수 있고, 각 패키지는 자기 디렉터리를 가지며 계층적으로 구성될 수 있어요. 샘플 프로젝트 구조를 볼까요.

project-root-directory/
  go.mod
  modname.go
  modname_test.go
  auth/
    auth.go
    auth_test.go
    token/
      token.go
      token_test.go
  hash/
    hash.go
  internal/
    trace/
      trace.go

다시 말하지만, go.mod의 module 줄이 다음과 같다고 가정해요.

module github.com/someuser/modname

modname 패키지는 루트 디렉터리에 있으며 package modname을 선언하고, 사용자는 다음과 같이 import 할 수 있습니다.

import "github.com/someuser/modname"

하위 패키지들은 다음과 같이 import 해요.

import "github.com/someuser/modname/auth"
import "github.com/someuser/modname/auth/token"
import "github.com/someuser/modname/hash"

internal/trace에 있는 trace 패키지는 이 모듈 바깥에서는 import 할 수 없습니다. 패키지는 가능한 한 internal에 두는 걸 권장합니다.

여러 커맨드 (Multiple commands)

같은 저장소 안의 여러 프로그램은 보통 각자의 디렉터리를 갖습니다.

project-root-directory/
  go.mod
  internal/
    ... shared internal packages
  prog1/
    main.go
  prog2/
    main.go

각 디렉터리에서 프로그램의 Go 파일들은 package main을 선언해요. 최상위 internal 디렉터리는 저장소의 모든 커맨드가 공유하는 패키지를 담을 수 있습니다. 사용자는 다음과 같이 이 프로그램들을 설치하죠.

$ go install github.com/someuser/modname/prog1@latest
$ go install github.com/someuser/modname/prog2@latest

흔한 관례는 저장소의 모든 커맨드를 cmd 디렉터리에 모으는 것입니다. 커맨드만 있는 저장소에서는 이게 꼭 필요하진 않지만, 커맨드와 import 가능한 패키지가 섞여 있는 혼합 저장소에서는 아주 유용해요. 이건 곧이어 다룹니다.

같은 저장소 안의 패키지와 커맨드

때로는 저장소 하나가 관련 기능을 가진 import 가능한 패키지와 설치 가능한 커맨드를 함께 제공하기도 해요. 그런 저장소의 샘플 프로젝트 구조는 다음과 같습니다.

project-root-directory/
  go.mod
  modname.go
  modname_test.go
  auth/
    auth.go
    auth_test.go
  internal/
    ... internal packages
  cmd/
    prog1/
      main.go
    prog2/
      main.go

이 모듈을 github.com/someuser/modname이라고 부른다고 할 때, 사용자는 여기서 패키지를 import 할 수도 있고

import "github.com/someuser/modname"
import "github.com/someuser/modname/auth"

프로그램을 설치할 수도 있습니다.

$ go install github.com/someuser/modname/cmd/prog1@latest
$ go install github.com/someuser/modname/cmd/prog2@latest

서버 프로젝트 (Server project)

Go는 서버를 구현할 때 흔히 선택되는 언어입니다. 서버 개발에는 프로토콜(REST? gRPC?), 배포, 프론트엔드 파일, 컨테이너화, 스크립트 등 그야말로 다양한 측면이 얽혀 있어서, 그런 프로젝트의 구조는 매우 크게 갈립니다. 여기서는 프로젝트 중 Go로 작성된 부분에만 초점을 맞춰 지침을 드릴게요.

서버 프로젝트는 보통 외부로 노출할 패키지가 없어요. 서버는 대개 자족적인(self-contained) 바이너리(또는 바이너리 그룹)니까요. 그러니 서버 로직을 구현하는 Go 패키지는 internal 디렉터리에 두는 걸 권장합니다. 게다가 프로젝트에는 Go가 아닌 파일을 담은 디렉터리가 많을 가능성이 높으니, 모든 Go 커맨드를 cmd 디렉터리에 모아 두는 것도 좋은 생각이에요.

project-root-directory/
  go.mod
  internal/
    auth/
      ...
    metrics/
      ...
    model/
      ...
  cmd/
    api-server/
      main.go
    metrics-analyzer/
      main.go
    ...
  ... the project's other directories with non-Go code

만약 서버 저장소에 다른 프로젝트와 공유하면 좋을 패키지가 생기면, 그 패키지들을 별도의 모듈로 분리하는 게 가장 좋습니다.

더 알아보기 (Learn more)