> ## Content Index
> Fetch the complete content index at: https://www.popit.kr/llms.txt
> Use this file to discover other available public pages before exploring further.

# 고 모듈을 사용하여 패키지 구성 방법 개선하기
- URL: https://www.popit.kr/improve-package-structure-with-go-modules/
- Published: 2020-02-05T00:30:48.000Z
- Updated: 2020-03-17T06:09:02.000Z
- Author: cp949
- Tags: #소프트웨어, golang, Go, 모듈

회사에서 API 서버를 주로 고 언어로 개발하고 있습니다. 사용해본지 2년 정도 지났는데 간단한 API 서버를 만드는 데에 큰 불편함은 없습니다. 한가지 아쉬운 점이라면 외부 패키지를 사용할 때 발생할 수 있는 몇 가지 문제들이 있다는 점입니다.

- 외부 패키지 버전 일관성 문제빌드 서버에서 매 빌드마다 외부 패키지들을 새로 다운로드 받을 경우 로컬에서 개발을 할 때 사용하던 외부 패키지들과 빌드 서버에서 빌드를 할 때 사용하는 외부 패키지들의 버전이 불일치 할 수 있습니다. 결국 빌드가 실패하거나 배포가 되더라도 API 기능에 예상치 못한 문제가 발생할 수 있습니다.
- 외부 패키지 다운로드 불가 문제네트워크 사정에 따라 외부 패키지를 다운로드 받지 못하는 경우가 생길 수 있습니다.\[1\]

그러던 중 [고 언어 1.11 버전에서 고 모듈이 옵션 기능으로 등장](https://github.com/golang/go/wiki/Modules?ref=popit.kr#go-111-modules)하였고 [1.13 버전에서 보다 본격적으로 지원](https://github.com/golang/go/wiki/Modules?ref=popit.kr#recent-changes)하게 되었습니다. 고 모듈을 사용하면 위 두 가지 문제를 간단하게 해결할 수 있습니다.

이번 글에서는 고 모듈 이전 이러한 문제들을 어떻게 해결했는지 먼저 살펴보고 고 모듈을 사용하여 보다 쉽게 해결하는 방법을 소개하겠습니다.

## 고 모듈 이전 해결 방법

### 벤더링

vendor 디렉토리에 특정 버전의 외부 패키지들을 저장 시킨 뒤 빌드에 참여시킴으로써 버전 일관성 문제를 해결 할 수 있습니다. 기본적으로 다운로드 불가한 외부 패키지를 어떻게든 한번만 구할 수만 있다면 다운로드 불가 문제도 회피 가능합니다.

### 도커 이미지

도커 이미지 상 GOPATH에 해당되는 경로에 미리 외부 패키지들을 저장시키면 벤더링 효과를 보면서 외부 패키지들의 버전을 도커 이미지 한 곳에서 관리할 수 있습니다.

하지만 두 방법 모두 궁극적으로 다운로드 불가 문제를 해결한 것은 아니라는 점과 패키지 구성에 다소 손이 많이 간다는 점이 아쉬웠습니다. 특히 도커 이미지를 활용하는 방법의 경우 로컬 개발 때 사용하는 외부 패키지들과 도커 이미지에 들어있는 외부 패키지들의 버전이 다르다면 버전 일관성 문제는 여전히 발생할 수 있습니다.

## 고 모듈

**이 글에서는 고 언어 1.13 버전을 기준으로 설명합니다.**

[공식 사이트에서 모듈을 다음과 같이 정의](https://blog.golang.org/using-go-modules?ref=popit.kr)하고 있습니다.

> A module is a collection of Go packages stored in a file tree with a go.mod file at its root.

간단히 말해 모듈은 go.mod 파일과 하위 패키지들을 포함하는 디렉토리라 볼 수 있습니다. 최상위 디렉토리에 go.mod 파일을 만들면 모듈이 됩니다.

**모듈은 go.mod 파일이 없는 패키지와도 호환됩니다. 앞으로 지칭되는 '모듈'은 '패키지'도 내포합니다.**

### 외부 모듈 버전 관리

외부 모듈 경로와 버전 정보가 go.mod 파일에 기록됩니다. 이후 외부 모듈들을 다시 구성해야 할 경우가 발생하면 이 go.mod 파일에 명시된 버전으로 모듈들을 다운로드 받게 됩니다. 이를 통해 매 빌드 마다 동일한 버전의 외부 모듈들을 구성할 수 있어 버전 불일치로 인한 문제를 해결할 수 있습니다.

\[go.mod\]  

```
module sample

go 1.13

require (
	github.com/aliyun/aliyun-oss-go-sdk v2.0.5+incompatible
	github.com/artdarek/go-unzip v0.0.0-20180315101617-33dc05190e4b
	github.com/asaskevich/govalidator v0.0.0-20200108200545-475eaeb16496
	github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f // indirect
	github.com/extrame/ole2 v0.0.0-20160812065207-d69429661ad7 // indirect
	github.com/go-sql-driver/mysql v1.5.0
	github.com/go-xorm/xorm v0.7.9
	github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0
	github.com/labstack/echo v3.3.10+incompatible
	github.com/pangpanglabs/echoswagger v1.1.0
	github.com/pangpanglabs/goutils v0.0.0-20200116103626-3f9fcfaa29b0
	github.com/sergeilem/xls v0.0.1
	github.com/tealeg/xlsx v1.0.5
	github.com/urfave/cli v1.22.2
	golang.org/x/image v0.0.0-20200119044424-58c23975cae1
)
```

### [다운로드 할 모듈 버전 지정](https://github.com/golang/go/wiki/Modules?ref=popit.kr#how-to-upgrade-and-downgrade-dependencies)

모듈 경로 맨 끝에 @버전 을 명시하면 됩니다. 생략한다면 @latest 가 됩니다.  

```
go get github.com/user/module@v1.3.0
go get github.com/user/module@latest
```

### [프록시 서버](https://github.com/golang/go/wiki/Modules?ref=popit.kr#are-there-always-on-module-repositories-and-enterprise-proxies)

외부 모듈은 프록시 서버를 통해 다운로드됩니다. 공식 프록시 서버([https://proxy.golang.org](https://proxy.golang.org/?ref=popit.kr))가 기본적으로 사용되며 설정에 따라 사설 프록시 서버 사용도 가능합니다. 다운로드가 불가했던 모듈도 프록시 서버를 통해 다운로드 될 수 있습니다.

### 환경 변수

- GO111MODULE: on | off | auto[1.13 버전 이후로 디렉토리에 go.mod 파일이 있으면 기본적으로 모듈 모드](https://github.com/golang/go/wiki/Modules?ref=popit.kr#recent-changes)로 동작합니다. 1.11, 1.12 버전의 경우 디렉토리가 GOPATH에 설정한 경로 상에 위치하면 go.mod 파일이 있더라도 기본적으로 모듈 모드로 동작하지 않습니다. 이 경우 명시적으로 모듈 모드를 사용하기 위해 해당 환경 변수를 on으로 설정하면 됩니다.
- GOPROXY: proxy server url모듈 모드에서 외부 모듈 다운로드를 할 때 참조됩니다. 따로 설정하지 않으면 공식 프록시 서버([https://proxy.golang.org](https://proxy.golang.org/?ref=popit.kr))로 설정됩니다.

## 적용 방법

1. 프로젝트 위치로 이동
2. 위 과정을 거치면서 생성된 go.mod와 [go.sum](https://github.com/golang/go/wiki/Modules?ref=popit.kr#is-gosum-a-lock-file-why-does-gosum-include-information-for-module-versions-i-am-no-longer-using)(체크섬) 파일을 커밋하여 관리

go get 또는 [go mod tidy](https://github.com/golang/go/wiki/Modules?ref=popit.kr#what-can-i-check-if-i-am-not-seeing-the-expected-version-of-a-dependency)를 통해 사용되는 외부 모듈 목록을 go.mod 파일에 기록  
\[go.mod\]  

```
module new-module

go 1.13

require (
	github.com/labstack/echo v3.3.10+incompatible
)
```

  
github.com/labstack/echo를 외부 모듈로 사용하고 있었다면 위와 같이 기록됩니다.

go mod init \[모듈 경로\]으로 go.mod 파일 생성 **프로젝트가 GOPATH에 설정한 경로 상에 위치하면 모듈 경로 생략 가능** \[go.mod\]  

```
module new-module

go 1.13
```

  
프로젝트가 $GOPATH/src/new-module에 위치했다면 위와 같이 기록됩니다.

## 모듈 적용 후 발생할 수 있는 문제

### 공식 프록시 서버 사용 불가

공식 프록시 서버를 사용할 수 없을 경우\[2\] 사설 [프록시 서버](https://github.com/golang/go/wiki/Modules?ref=popit.kr#are-there-always-on-module-repositories-and-enterprise-proxies)를 사용해 보시길 바랍니다.  

```
GOPROXY=https://goproxy.io go get
```

### 비모듈 모드 때와 버전 불일치

모듈 모드일 때 다운로드할 [외부 모듈 버전이 2 이상이라면 모듈 경로에 버전이 꼭 명시 되어야](https://github.com/golang/go/wiki/Modules?ref=popit.kr#semantic-import-versioning) 합니다.

예) https://github.com/user/module/v2, https://github.com/user/module/v3, ...

만약 외부 모듈 경로에 버전이 명시되지 않으면 다운로드를 할 때 모드별로 상이하게 동작하게 됩니다. 그래서 비모듈 모드 때에 사용하던 모듈과 모듈 모드 때 사용되는 모듈의 버전이 달라질 수 있습니다.

- 모듈 모드
  - 0, 1 버전 혹은 모듈이 적용되기 전 가장 최신 버전을 다운로드 함
- 비모듈 모드
  - 저장소의 기본 브랜치가 가리키는 버전을 다운로드 함

### 모듈 모드가 동작 안 함

1.11, 1.12 버전일 경우 디렉토리가 GOPATH에 설정한 경로상에 위치하면 go.mod 파일이 있어도 기본적으로 비모듈 모드가 됩니다. 이 경우 명시적으로 GO111MODULE을 on으로 설정하여야 모듈 모드로 동작합니다. 그리고 공식 프록시 서버를 기본적으로 사용하지 않기 때문에 GOPROXY에 직접 프록시 서버를 지정하여야 합니다.  

```
GO111MODULE=on GOPROXY=https://proxy.golang.org go get
GO111MODULE=on go build

```

만약 환경 변수를 전역으로 설정하지 않고 커맨드마다 따로 설정하려 한다면 빌드 커맨드 앞에도 GO111MODULE를 설정하여야 합니다.

### 벤더링 안 됨

비모듈 모드와는 다르게 모듈 모드에서는 빌드 옵션을 추가하여야 벤더링된 외부 모듈을 사용합니다. 그리고 [일부분 모듈 벤더링](https://github.com/golang/go/issues/30240?ref=popit.kr)이 아직 지원되지 않습니다. 벤더링을 원한다면 모든 외부 모듈을 벤더링 하여야 합니다.  

```
go mod vendor
go build -mod vendor
```

1. vendor 디렉토리로 모든 외부 모듈을 복사
2. 빌드 할 때 `-mod vendor` 옵션을 붙여 벤더링된 외부 모듈을 사용

### 로컬 모듈 사용

프록시 서버를 통해 다운로드 받은 외부 모듈이 아닌 로컬 모듈을 사용해야 한다면 go.mod 파일 수정이 필요합니다.  

```
colleague
|-- foo
|   `-- go.mod
|   `-- xxx.go
|-- kit
|   `-- go.mod
|   `-- xxx.go
```

예를 들어 위와 같은 디렉토리 구성에서 colleague/foo 모듈이 colleague/kit 로컬 모듈을 사용한다면 colleague/foo 디렉토리 go.mod 파일에 replace 구문을 추가합니다.

\[go.mod\]  

```
module colleague/foo

go 1.13

require (
    colleague/kit v0.0.0
)

replace colleague/kit => ../kit
```

외부 모듈일 때와는 다르게 로컬 모듈은 반드시 go.mod 파일을 가지고 있어야 합니다. 만약 colleague/kit 디렉토리에 go.mod 파일이 없다면 빌드할 때 go.mod 파일을 찾을 수 없다는 오류가 발생합니다. 그리고 require 구문에서 [로컬 모듈 버전을 명시하기 애매하다면 v0.0.0으로 지정](https://github.com/golang/go/wiki/Modules?ref=popit.kr#when-should-i-use-the-replace-directive)하거나 [go mod tidy](https://github.com/golang/go/wiki/Modules?ref=popit.kr#what-can-i-check-if-i-am-not-seeing-the-expected-version-of-a-dependency)를 사용하여 자동으로 버전이 지정되도록 할 수 있습니다.

## 이어보기

- [고 모듈과 함께 비공개 외부 저장소를 사용하는 방법](https://www.popit.kr/go-modules-private-repository/)

---

\[1\] 사무실이 중국에 위치하고 있습니다. 중국의 방화벽으로 인해 네트워크가 차단되는 경우가 많습니다. github.com이 아닌 golang.org를 통해 공개되어 있는 외부 패키지들은 기본적으로 다운로드가 불가합니다.

\[2\] 중국의 경우 공식 프록시 서버를 사용할 수 없습니다.