본문 바로가기
프로그래밍/Go

Go 모듈과 패키지 이해하기

by JLearn 2026. 7. 3.
반응형

Go를 처음 공부하다 보면 module, package, go.mod, import path라는 용어를 자주 만나게 됩니다.

처음에는 모두 비슷해 보이지만, 실제 역할은 분명히 다릅니다.
특히 API 서버처럼 외부 패키지를 사용하는 Go 프로젝트에서는 go.mod 파일을 만들고, 프로젝트를 하나의 Go module로 관리하는 것이 일반적인 방식입니다.

이 글에서는 Go 공식 문서를 기준으로 다음 내용을 정리합니다.

  • Go에서 module이 의미하는 것
  • package와 module의 차이
  • go.mod 파일이 필요한 이유
  • go mod init, go mod edit, go get 명령의 역할
  • internal/task가 패키지인지, task가 패키지인지 구분하는 방법

이 글의 예시는 example.com/taskapigo 1.26.1을 기준으로 설명합니다.
실제 프로젝트에서는 본인이 관리하는 도메인, Git 저장소 경로, 회사명 등을 기준으로 module path를 정하는 것이 좋습니다.

[관련 글] Go의 package main과 패키지 구조 이해하기


핵심 요약

Go 프로젝트에서 가장 먼저 구분해야 하는 것은 modulepackage입니다.

구분 의미
module 함께 버전 관리되고 배포되는 Go package들의 묶음
package 같은 디렉터리에서 함께 컴파일되는 Go 파일들의 묶음
go.mod module path, Go 버전, 의존성을 기록하는 파일
import path 다른 코드에서 package를 가져올 때 사용하는 경로

예를 들어 아래 구조가 있다고 가정합니다.

 taskapi/
 ├── go.mod
 ├── cmd/
 │   └── api/
 │       └── main.go
 └── internal/
     └── task/
         ├── handler.go
         ├── service.go
         └── repository.go

go.mod 파일이 다음과 같다면:

module example.com/taskapi

go 1.26.1

internal/task 디렉터리의 Go 파일들이 아래처럼 시작한다고 가정합니다.

package task

이 경우 정리하면 다음과 같습니다.

항목
module path example.com/taskapi
package directory internal/task
import path example.com/taskapi/internal/task
package name task

즉, internal/task는 패키지가 위치한 디렉터리 경로이고, task는 Go 파일에 선언된 package 이름입니다.


1. Go module이란?

Go 공식 문서 기준으로 module은 go.mod 파일에 선언된 module path로 식별됩니다.
그리고 module root directory는 go.mod 파일이 있는 디렉터리입니다.

쉽게 말하면 module은 하나의 Go 프로젝트 단위로 이해할 수 있습니다.

예를 들어 아래 명령을 실행하면:

go mod init example.com/taskapi

현재 디렉터리에 go.mod 파일이 생성됩니다.

module example.com/taskapi

이제 이 디렉터리는 example.com/taskapi라는 module path를 가진 Go module이 됩니다.

Go 공식 문서에서는 module path를 가능하면 소스 코드 저장소 위치로 정하라고 설명합니다.
예를 들어 GitHub에 배포할 프로젝트라면 github.com/사용자명/저장소명 형태가 자연스럽습니다.


2. go.mod 파일은 왜 필요한가?

go.mod는 Go module의 기준 파일입니다.

Go 공식 문서에 따르면, 의존성을 추적하고 관리하려면 코드를 module 안에 두어야 하며, 이 과정에서 소스 트리의 루트에 go.mod 파일이 생성됩니다.

예를 들어 외부 라우터 패키지인 github.com/go-chi/chi/v5를 사용하는 API 프로젝트라면 go.mod 파일이 필요합니다.

예시:

module example.com/taskapi

go 1.26.1

require github.com/go-chi/chi/v5 v5.3.0

각 줄의 의미는 다음과 같습니다.

항목 의미
module example.com/taskapi 현재 module의 module path
go 1.26.1 이 module을 사용하기 위한 최소 Go 버전
require ... 이 module이 의존하는 외부 module

작은 단일 파일 실습에서 표준 라이브러리만 사용하고 go run hello.go 정도만 실행한다면 go.mod 없이도 동작할 수 있습니다.
하지만 실제 프로젝트, API 서버, 외부 패키지를 사용하는 프로젝트라면 go.mod를 만들고 module 단위로 관리하는 것이 맞습니다.


3. go mod init은 무엇을 하는가?

go mod init은 현재 디렉터리를 Go module로 초기화하는 명령입니다.

go mod init example.com/taskapi

이 명령을 실행하면 go.mod 파일이 생성되고, module path가 기록됩니다.

module example.com/taskapi

여기서 example.com/taskapi는 단순한 폴더명이 아니라 module path입니다.

module path는 나중에 package import path의 prefix가 됩니다.

예를 들어 module path가 다음과 같다면:

module example.com/taskapi

internal/task 패키지의 import path는 다음처럼 만들어집니다.

import "example.com/taskapi/internal/task"

즉, Go에서 import할 때 사용하는 경로는 보통 아래 구조로 만들어집니다.

module path + module 내부의 package 디렉터리 경로

4. package란 무엇인가?

Go에서 package는 같은 디렉터리에 있고 함께 컴파일되는 Go 파일들의 묶음입니다.

예를 들어 다음 파일들이 있다고 가정합니다.

internal/task/
├── handler.go
├── service.go
└── repository.go

각 파일의 첫 줄이 모두 다음과 같다면:

package task

이 세 파일은 하나의 task package에 속합니다.

즉, 정확히 구분하면 다음과 같습니다.

표현 의미
internal/task package가 위치한 디렉터리 경로
package task Go 파일에 선언된 package 이름
example.com/taskapi/internal/task 다른 package에서 import할 때 사용하는 import path

다른 코드에서는 보통 import path로 가져옵니다.

import "example.com/taskapi/internal/task"

그리고 코드 안에서는 package 이름으로 접근합니다.

task.NewService()

5. package 이름과 디렉터리 이름은 같아야 할까?

Go에서는 package 이름과 디렉터리 이름이 반드시 같아야 하는 것은 아닙니다.

하지만 Go 공식 문서의 관례에 따르면 package 이름은 보통 소스 디렉터리의 마지막 이름과 같게 짓습니다.

예를 들어:

internal/task/

이 디렉터리 안의 Go 파일은 보통 다음처럼 작성합니다.

package task

이렇게 하면 import하는 쪽에서도 자연스럽게 사용할 수 있습니다.

import "example.com/taskapi/internal/task"

func main() {
    task.NewService()
}

반대로 디렉터리는 task인데 package 이름을 service로 하면 동작은 할 수 있지만, 읽는 사람이 헷갈릴 수 있습니다.

따라서 특별한 이유가 없다면 아래처럼 맞추는 것이 좋습니다.

디렉터리 package 이름
internal/task package task
internal/user package user
internal/order package order

6. internal 디렉터리는 무엇인가?

Go에는 internal이라는 특별한 디렉터리 규칙이 있습니다.

internal 디렉터리 아래에 있는 package는 외부 module에서 마음대로 import할 수 없습니다.

예를 들어 다음 구조가 있다고 가정합니다.

taskapi/
├── go.mod
├── cmd/
│   └── api/
│       └── main.go
└── internal/
    └── task/
        └── service.go

internal/task는 같은 module 내부에서는 import할 수 있습니다.

import "example.com/taskapi/internal/task"

하지만 다른 프로젝트에서는 이 package를 import할 수 없습니다.

import "example.com/taskapi/internal/task" // 외부 module에서는 사용 불가

이 규칙은 내부 구현을 외부에 공개하지 않고, 프로젝트 안에서만 사용하려는 package를 보호하는 데 도움이 됩니다.

API 서버 프로젝트에서는 handler, service, repository 같은 내부 구현 package를 internal 아래에 두는 경우가 많습니다.


7. go get go@1.26.1은 Go를 설치하는 명령인가?

아닙니다.

go get go@1.26.1

이 명령은 Go를 컴퓨터에 설치하는 명령이 아닙니다.

Go 1.21 이후 Go toolchain 관리 방식이 강화되면서, go 명령은 go.modgo line과 toolchain line을 기준으로 필요한 Go toolchain을 선택하거나 다운로드할 수 있습니다.

따라서 go get go@1.26.1은 현재 module의 Go toolchain 요구 버전을 관리하는 명령으로 이해해야 합니다.

예를 들어 현재 프로젝트의 go.mod가 아래처럼 바뀔 수 있습니다.

module example.com/taskapi

go 1.26.1

중요한 점은 이것입니다.

go get go@1.26.1은 Ubuntu에 Go 1.26.1을 설치하는 명령이 아닙니다.
이미 설치된 go 명령이 현재 module의 Go 버전 요구사항을 관리하는 명령입니다.

Go 자체를 설치하려면 운영체제 패키지, 공식 tar.gz, 또는 버전 관리자 같은 별도의 설치 방법을 사용해야 합니다.


8. go mod edit -go=1.26.1은 무엇인가?

go mod edit -go=1.26.1go.mod 파일의 go line을 수정하는 명령입니다.

go mod edit -go=1.26.1

실행 후 go.mod 파일은 다음처럼 될 수 있습니다.

module example.com/taskapi

go 1.26.1

즉, 이 명령은 현재 module이 요구하는 최소 Go 버전을 명시적으로 설정하는 역할을 합니다.

이미 go.mod에 다음 줄이 있다면:

go 1.26.1

다시 실행할 필요는 없습니다.


9. go get과 go mod edit의 차이

go get go@1.26.1go mod edit -go=1.26.1은 결과적으로 go.mod의 Go 버전 관련 내용을 바꿀 수 있지만, 목적은 다릅니다.

명령 역할
go mod edit -go=1.26.1 go.modgo line을 직접 수정
go get go@1.26.1 현재 module의 Go toolchain 요구 버전을 Go 명령을 통해 관리
go get github.com/go-chi/chi/v5@v5.3.0 외부 module 의존성 추가 또는 버전 변경
go mod tidy 실제 import 기준으로 필요한 의존성 정리

일반적인 새 프로젝트에서는 다음 흐름이면 충분합니다.

mkdir -p sources/taskapi
cd sources/taskapi

go mod init example.com/taskapi
go mod edit -go=1.26.1
go get github.com/go-chi/chi/v5@v5.3.0
go mod tidy

go.mod 예시는 다음과 같습니다.

module example.com/taskapi

go 1.26.1

require github.com/go-chi/chi/v5 v5.3.0

10. 예제로 이해하기

아래와 같은 Task API 프로젝트가 있다고 가정합니다.

taskapi/
├── go.mod
├── cmd/
│   └── api/
│       └── main.go
└── internal/
    └── task/
        ├── handler.go
        ├── service.go
        └── repository.go

go.mod:

module example.com/taskapi

go 1.26.1

internal/task/service.go:

package task

type Service struct{}

func NewService() *Service {
    return &Service{}
}

cmd/api/main.go:

package main

import (
    "fmt"

    "example.com/taskapi/internal/task"
)

func main() {
    service := task.NewService()
    fmt.Printf("service = %T\n", service)
}

이 예제에서 각 요소는 다음과 같습니다.

항목
module example.com/taskapi
main package cmd/api 디렉터리의 package main
task package 위치 internal/task
task package 이름 task
task package import path example.com/taskapi/internal/task

즉, cmd/api/main.gointernal/task 디렉터리의 package를 import path로 가져오고, 코드에서는 package name인 task로 사용합니다.


11. 자주 헷갈리는 부분

11-1. internal/task가 패키지인가, task가 패키지인가?

둘 다 패키지를 설명할 때 사용될 수 있지만, 정확히는 역할이 다릅니다.

표현 정확한 의미
internal/task package가 있는 디렉터리 경로
task Go 파일에 선언된 package 이름
example.com/taskapi/internal/task import path

따라서 가장 정확한 표현은 다음입니다.

internal/task 디렉터리에 task package가 있다.

11-2. module 이름은 꼭 실제 도메인이어야 하나?

공개 배포할 module이라면 module path는 실제 저장소 위치와 맞추는 것이 좋습니다.

예를 들어 GitHub에 배포한다면 다음처럼 작성합니다.

go mod init github.com/myname/taskapi

하지만 학습용이나 로컬 실습용이라면 Go 공식 문서에서도 사용하는 example.com 형태를 사용할 수 있습니다.

go mod init example.com/taskapi

다만 실제 회사 프로젝트라면 회사 도메인이나 Git 저장소 경로를 기준으로 정하는 것이 좋습니다.


11-3. go.mod를 나중에 수정해도 되는가?

가능합니다.

go.mod의 module path는 텍스트 파일이므로 수정할 수 있습니다.

다만 module path를 바꾸면 내부 package의 import path도 함께 바뀝니다.

예를 들어 기존에 다음과 같았다면:

module example.com/taskapi

코드에서는 이렇게 import했을 수 있습니다.

import "example.com/taskapi/internal/task"

module path를 다음처럼 바꾸면:

module github.com/myname/taskapi

import도 함께 바뀌어야 합니다.

import "github.com/myname/taskapi/internal/task"

따라서 프로젝트 초기에 module path를 신중하게 정하는 것이 좋습니다.


11-4. package를 너무 많이 나누는 것이 좋은가?

항상 좋은 것은 아닙니다.

Go에서는 package를 기능 단위로 나누되, 너무 이른 시점에 잘게 쪼개면 오히려 코드 이동과 import가 복잡해질 수 있습니다.

작은 프로젝트라면 처음에는 단순하게 시작하고, 코드가 커지면서 역할이 분명해질 때 package를 분리하는 것이 좋습니다.

예를 들어 API 서버라면 처음에는 다음 정도의 구조로 시작할 수 있습니다.

taskapi/
├── go.mod
├── cmd/
│   └── api/
│       └── main.go
└── internal/
    ├── task/
    ├── user/
    └── config/

12. 실무적인 추천 구조

API 서버 프로젝트라면 아래 구조를 기본으로 시작하기 좋습니다.

taskapi/
├── go.mod
├── go.sum
├── cmd/
│   └── api/
│       └── main.go
└── internal/
    ├── config/
    │   └── config.go
    ├── task/
    │   ├── handler.go
    │   ├── service.go
    │   └── repository.go
    └── server/
        └── server.go

각 디렉터리의 역할은 다음과 같습니다.

경로 역할
cmd/api 실행 진입점, package main
internal/config 설정 로딩
internal/task task 도메인 로직
internal/server HTTP 서버 구성
go.mod module과 의존성 정보
go.sum 의존성 검증용 체크섬

cmd/api/main.go는 실행 진입점이므로 package main을 사용합니다.

package main

반면 internal/task는 task 도메인의 내부 package이므로 다음처럼 작성합니다.

package task

결론

Go에서 module과 package는 서로 다른 개념입니다.

module은 go.mod 파일로 관리되는 프로젝트 또는 배포 단위입니다.
하나의 module 안에는 여러 package가 들어갈 수 있습니다.

package는 같은 디렉터리에서 함께 컴파일되는 Go 파일들의 묶음입니다.
예를 들어 internal/task 디렉터리 안의 파일들이 모두 package task로 시작한다면, 그 디렉터리는 task package를 담고 있는 위치입니다.

정리하면 다음과 같습니다.

module  = go.mod로 관리되는 프로젝트 단위
package = 같은 디렉터리에서 함께 컴파일되는 Go 파일 묶음
go.mod  = module path, Go 버전, 의존성을 기록하는 파일

그리고 example.com/taskapi/internal/task처럼 import할 때 사용하는 경로는 다음 조합으로 만들어집니다.

module path + package 디렉터리 경로

Go 프로젝트를 시작할 때는 다음 흐름을 기억하면 됩니다.

go mod init example.com/taskapi
go mod edit -go=1.26.1
go get github.com/go-chi/chi/v5@v5.3.0
go mod tidy

이렇게 하면 프로젝트의 module path, Go 버전, 외부 의존성을 go.mod 기준으로 관리할 수 있습니다.

Go를 처음 공부할 때는 package를 많이 나누는 것보다, 먼저 module과 package의 경계를 정확히 이해하는 것이 중요합니다.
go.mod는 프로젝트의 기준이고, 각 디렉터리는 package의 기준이라고 생각하면 구조를 훨씬 쉽게 이해할 수 있습니다.


참고 자료

반응형

'프로그래밍 > Go' 카테고리의 다른 글

Go package main과 패키지 구조 이해하기  (0) 2026.07.10
Ubuntu 24.04에서 Go 1.26.1과 VS Code 설치하기  (1) 2026.07.03
VS Code Go 환경 세팅  (0) 2026.04.14
우분투 24.04 LTS 서버 Go 1.26.1 설치  (0) 2026.03.17
GO PATH  (0) 2026.03.16

댓글