Outsider's Dev Story: Go 카테고리 글 목록https://blog.outsider.ne.kr/Stay Hungry. Stay Foolish. Don't Be Satisfied.2024-03-15T09:50:04+09:00Textcube 1.10.7 : Tempo primo[Book] 개발자를 위한 하룻밤에 읽는 Go 언어 이야기Outsiderhttps://blog.outsider.ne.kr/15442021-04-05T03:13:40+09:002021-04-05T03:13:40+09:00<div>
<fieldset style="padding: 20px 5px 5px 5px;">
<legend><a href="https://www.aladin.co.kr/shop/wproduct.aspx?ItemId=71633668">개발자를 위한 하룻밤에 읽는 Go 언어 이야기</a></legend>
<table>
<tbody>
<tr>
<td>
<a href="https://www.aladin.co.kr/shop/wproduct.aspx?ItemId=71633668"><img src="//blog.outsider.ne.kr/attach/1/2554782516.jpg" alt="개발자를 위한 하룻밤에 읽는 Go 언어 이야기 책 표지" title="" /></a>
</td>
<td style="vertical-align: top">
<a href="https://www.aladin.co.kr/shop/wproduct.aspx?ItemId=71633668">개발자를 위한 하룻밤에 읽는 Go 언어 이야기</a> - ⭐⭐
<br>신제용 지음<br>한빛미디어
</td>
</tr>
</tbody>
</table>
</fieldset>
<br>
</div>
<p>페이스북에서 누군가 가볍게 읽기 좋다는 글(아마도 그랬던 것 같다. 정확한 글은 잘 기억이 나지 않는다.)을 보고 구매한 책이다. 크게 Go 언어에 대해서 배워야지 하는 기대보다는 "Go 언어 공부해야지"하고는 책 초반에서 진도를 나가지 못하고 있던 터라 가볍게 한번 읽어보고 분위기 환기를 하자는 생각이었다.</p>
<p>책은 140페이지 정도로 금방 읽을 수 있는데 읽다가 2015년에 나온 책이란 걸 알게 되었다. Go 언어를 이미 쓰고 있거나 하면 굳이 읽을 필요는 없어보이고 관심이 있다면 분위기나 언어 특징을 이해하는 차원에서 한번 읽어볼 만은 하다고 생각된다. 내용은 저자가 학생과 개발자에게 Go 언어를 설명하는 대화식으로 되어 있는데 나는 약간 오글거리게 느껴졌다. 아주 오래전에는 다른 책에서 이런 대화식으로 구성된 걸 봤을 때 그런 기억이 없는 걸 생각해 보면 지금은 내가 이런 대화식이 거추장스럽게 느껴지는 것 같다.</p>
<p>Go 언어가 만들어진 배경과 언어가 가진 특성, 생태계나 콘퍼런스 분위기 등에 관해서 설명한다. 약간 Go 언어를 너무 찬양하는 느낌은 들었지만, 책의 의도도 그렇고 2015년에 나온 책이라는 걸 생각하면 크게 문제는 안될 것 같다.(중간에 GitHub CEO가 디자이너 출신이라는 이야기가 나오는데 처음 듣는 얘기라 출처가 궁금하기는 했다)</p>
<p>중간부터는 언어의 특징을 간단히 설명한다. 변수 선언을 포함한 간단한 문법 설명과 goroutine을 이용한 동시성 예제를 보여주는데 Go 코드를 몇 번 본적은 있기는 하지만 부담 없이 읽을 수 있게 구성해 놓았다.</p>
<p><strong><a href="https://blog.outsider.ne.kr/1544?commentInput=true#entry1544WriteComment">댓글 쓰기</a></strong></p>Go 환경 설정(with go1.15.7)Outsiderhttps://blog.outsider.ne.kr/15292021-01-26T04:12:30+09:002021-01-26T04:12:30+09:00<p>3년 전에 <a href="https://blog.outsider.ne.kr/1349">Go 환경 설정</a>에 대한 글을 썼다. 당시 <a href="https://golang.org/">Go 언어</a>를 사용해야 했기에 쓴 글이었지만 사실 Go 언어를 그리 많이 쓰진 않았고 언어를 잘 배우는 편은 아니어서 그랬는지 몇 개월 뒤에 나는 <a href="https://blog.outsider.ne.kr/1422">Elctron 데스크톱 앱 개발</a>로 도망쳤다. 그래서 여전히 Go 언어에 대해서는 잘 알지 못한다.</p>
<p>인프라 쪽을 계속하다 보니 Go를 해야겠다는 생각이 계속 들어서 다시 Go 언어를 만지게 됐다. <a href="https://blog.outsider.ne.kr/1349">이전에 글을 썼을 때</a>는 여러 의존성 관리 도구가 있던 시기에 막 go modules가 등장했을 때였던 걸로 기억하는데 지금은 go modules가 평정한 것 같아서 이전과 뭐가 달라졌나(라고 하기엔 이전에 어땠는지도 잘 모르지만...) 확인도 할 겸 앞으로도 사용해야 해서 다시 Go 언어의 개발환경을 설정했다.</p>
<p>나는 주로 사용하는 도구를 바이너리로 받아서 <code>PATH</code>에 넣어서 관리하는 편이라(버전 관리 도구를 사용하지 않고 원할 때 명시적으로 버전을 변경하려고...) <a href="https://golang.org/dl/">다운로드 페이지</a>에서 <code>pkg</code> 대신 <code>tar.gz</code> 파일을 받아서 <code>PATH</code> 경로에 바이너리 파일을 넣었다.</p>
<pre class="line-numbers"><code class="language-bash">$ go version
go version go1.15.7 darwin/amd64
</code></pre>
<p>사실 전에 사용할 때는 자꾸 환경변수 지정하라고 해서 좀 피곤했는데 이젠 환경변수를 지정하지 않고도 크게 무리 없이 사용할 수 있었다.</p>
<p>그사이에 뭐가 달라졌는지 잘 모르기 때문에 <a href="https://golang.org/doc/tutorial/getting-started">Tutorial: Get started with Go</a>를 따라 해봤다.</p>
<p>프로젝트 디렉터리를 만들고 <code>hello.go</code> 파일에 다음 내용을 입력한다.</p>
<pre class="line-numbers"><code class="language-go">package main
import "fmt"
func main() {
fmt.Println("Hello, World!!")
}
</code></pre>
<p>이 코드를 다음과 같이 실행할 수 있다.</p>
<pre class="line-numbers"><code class="language-bash">$ go run .
Hello, World!!
</code></pre>
<p>이제 외부 패키지 <code>rsc.io/quote</code>를 사용하는 예제를 이어서 따라 해 보자. <code>hello.go</code> 파일을 다음과 같이 수정한다.</p>
<pre class="line-numbers"><code class="language-go">package main
import "fmt"
import "rsc.io/quote"
func main() {
fmt.Println(quote.Go())
}
</code></pre>
<p>외부 모듈을 사용하고 있으므로 의존성 정보를 저장하고 있는 파일인 <code>go.mod</code> 파일을 생성하기 위해 <code>go mod init [module]</code>을 실행한다.</p>
<pre class="line-numbers"><code class="language-bash">$ go mod init hello
go: creating new go.mod: module hello
</code></pre>
<p>그리고 실행하면 다음과 같이 외부 모듈을 다운로드받고 실행하는 것을 볼 수 있다.</p>
<pre class="line-numbers"><code class="language-bash">$ go run .
go: finding module for package rsc.io/quote
go: downloading rsc.io/quote v1.5.2
go: found rsc.io/quote in rsc.io/quote v1.5.2
go: downloading rsc.io/sampler v1.3.0
go: downloading golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c
Don't communicate by sharing memory, share memory by communicating.
</code></pre>
<p><code>go.mod</code> 파일이 없는 상태에서 실행하면 다음과 같은 오류가 발생한다.</p>
<pre class="line-numbers"><code class="language-bash">go run .
hello.go:5:8: cannot find package "rsc.io/quote" in any of:
/Users/outsider/bin/lib/go/go1.15.7/src/rsc.io/quote (from $GOROOT)
/Users/outsider/go/src/rsc.io/quote (from $GOPATH)
</code></pre>
<p>여기서 생성된 <code>go.mod</code> 파일은 다음과 같은 내용을 담고 있다.</p>
<pre class="line-numbers"><code class="language-go">module hello
go 1.15
require rsc.io/quote v1.5.2
</code></pre>
<p>그리고 <code>go run .</code> 이후에 생긴 <code>go.sum</code> 파일은 다음과 같이 생겼다.</p>
<pre class="line-numbers"><code class="language-go">golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c h1:qgOY6WgZOaTkIIMiVjBQcw93ERBE4m30iBm00nkL0i8=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
rsc.io/quote v1.5.2 h1:w5fcysjrx7yqtD/aO+QwRjYZOKnaM9Uh2b40tElTs3Y=
rsc.io/quote v1.5.2/go.mod h1:LzX7hefJvL54yjefDEDHNONDjII0t9xZLPXsUe+TKr0=
rsc.io/sampler v1.3.0 h1:7uVkIFmeBqHfdjD+gZwtXXI+RODJ2Wc4O7MPEh/QiW4=
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
</code></pre>
<p>위에서 패키지를 다운로드 받았는데 이는 <code>GOPATH</code> 아래 다운로드가 되는데 별도로 <code>GOPATH</code>를 지정하지 않았기 때문에 <code>~/go</code>가 기본 위치가 된다.(UNIX 기준) <code>~/go</code>에 가보면 <code>pkg</code> 폴더 아래 다운로드받은 모듈이 설치된 것을 볼 수 있다.<br />
<br></p>
<h1><a href="https://www.waypointproject.io/">Waypoint</a> 환경 설정</h1>
<p>좀 더 실전을 위해서 최근에 관심있는 HashiCorp의 <a href="https://www.waypointproject.io/">Waypoint</a>의 개발환경을 설정해 봤다. 아직 개발을 본격적으로 한 것은 아니라서 다양한 케이스나 베스트 프랙티스에 대해서는 잘 모른다.</p>
<p>Waypoint의 <a href="https://github.com/hashicorp/waypoint/blob/main/.github/CONTRIBUTING.md#building-waypoint">기여 문서</a>에 <a href="https://github.com/kevinburke/go-bindata/">go-bindata</a>가 설치되어 있어야 한다고 해서 <code>go get -u github.com/kevinburke/go-bindata/...</code>로 설치한다.</p>
<pre class="line-numbers"><code class="language-bash">$ go get -u github.com/kevinburke/go-bindata/...
go: downloading github.com/kevinburke/go-bindata v3.22.0+incompatible
</code></pre>
<p>이 <code>go-bindata</code>는 <code>$GOPATH/bin</code>에 설치되므로 이 명령어를 사용할 수 있게 하려면 <code>PATH</code>에 <code>$GOPATH/bin</code>을 추가해야 한다. 설치되어 있지 않거나 <code>PATH</code>에 제대로 설정되어 있지 않다면 <code>/bin/sh: go-bindata: command not found</code> 오류가 발생한다. 의존성 설치가 되었으면 <code>make bin</code>을 실행해서 Waypoint를 빌드할 수 있다.</p>
<pre class="line-numbers"><code class="language-bash">$ make bin
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o ./internal/assets/ceb/ceb ./cmd/waypoint-entrypoint
cd internal/assets && go-bindata -pkg assets -o prod.go -tags assetsembedded ./ceb
CGO_ENABLED=0 go build -ldflags "-s -w -X "github.com/hashicorp/waypoint/internal/version".GitCommit=$(git rev-parse --short HEAD)$(test -n "`git status --porcelain`" && echo "+CHANGES" || true) -X "github.com/hashicorp/waypoint/internal/version".GitDescribe=$(git describe --tags --always --match "v*")" -tags assetsembedded -o ./waypoint ./cmd/waypoint
go: downloading github.com/hashicorp/go-getter v1.4.1
go: downloading github.com/dustin/go-humanize v1.0.0
......
go: downloading cloud.google.com/go/storage v1.6.0
</code></pre>
<p>빌드가 끝나면 해당 디렉터리에 빌드된 <code>waypoint</code> 파일이 생성된다.</p>
<pre class="line-numbers"><code class="language-bash">$ ./waypoint version
Waypoint v0.2.0-209-g0f5e7a41 (0f5e7a41)
</code></pre>
<p>이 빌드된 파일이 잘 실행되는 것을 볼 수 있다.</p>
<p><strong><a href="https://blog.outsider.ne.kr/1529?commentInput=true#entry1529WriteComment">댓글 쓰기</a></strong></p>GoLand에서 delve로 원격 디버깅하기Outsiderhttps://blog.outsider.ne.kr/14332019-03-09T21:35:26+09:002019-03-09T21:35:26+09:00<p>작년에 <a href="https://www.etherstudy.net/">이더리움 연구회</a> 스터디에 참가하면서 <a href="https://github.com/ethereum/go-ethereum">이더리움</a>의 소스를 봐야 할 일이 있었다. 코드량이 상당하고 한 번에 다 파악할 수 없으므로 이더리움을 동작해 보면서 필요한 부분을 살펴보려고 <a href="https://tech.etherstudy.net/ethereum/geth/delve/debug/consensus/lifecycle/2018/08/02/geth-consensus-lifecycle-debug.html">Geth와 Delve를 사용한 이더리움 컨센서스 라이프사이클 디버깅</a>을 참고했다.(사실 작년에 했던 건데 미루다가 이제야 정리한다.)</p>
<p>이 글에서는 <a href="https://github.com/go-delve/delve">delve</a>와 연결해서 디버깅하는 과정을 설명하고 있는데 나는 <a href="https://www.jetbrains.com/go/">GoLand</a>를 사용하고 있었으므로 GoLand에서 디버깅을 하고 싶었다. 보통은 GoLand내에서 바로 되는데 <a href="https://golang.org/">Go</a>도 잘 모르고 이더리움은 더욱 몰랐기 때문에 헤매다가 GoLand에서 <a href="https://blog.jetbrains.com/go/2019/02/06/debugging-with-goland-getting-started/#debugging-a-running-application-on-a-remote-machine">원격 디버깅을 하는 방법</a>을 찾아서 <a href="https://tech.etherstudy.net/ethereum/geth/delve/debug/consensus/lifecycle/2018/08/02/geth-consensus-lifecycle-debug.html">윗글</a>의 delve와 연동해서 디버깅을 해봤다.</p>
<p><a href="https://tech.etherstudy.net/ethereum/geth/delve/debug/consensus/lifecycle/2018/08/02/geth-consensus-lifecycle-debug.html">Geth와 Delve를 사용한 이더리움 컨센서스 라이프사이클 디버깅</a>에 나온 대로 이더리움 프로젝트를 설정하고 테스트용 Private Network를 준비한다.</p>
<p>GoLand에서 Run/Debug Configurations를 실행해서 "Go Remote"를 2345 포트로 추가한다.</p>
<p style="text-align: center;"><img src="//blog.outsider.ne.kr/attach/1/1756922887.gif" width="750" height="664" alt="GoLand의 Run/Debug Configurations 설정" title="" /></p>
<p>터미널에서 아래 명령어로 <code>delve</code>로 이더리움을 실행한다.</p>
<pre class="line-numbers"><code class="language-bash">$ cd cmd/geth
dlv debug --headless --listen=:2345 --api-version=2 -- --networkid=1234 --datadir=~/golang/src/github.com/ethereum/go-ethereum/consensus-debug/miner
</code></pre>
<p>이제 GoLand에서 앞에서 Go Remote로 설정한 Debug를 실행한다.</p>
<p style="text-align: center;"><img src="//blog.outsider.ne.kr/attach/1/1068392863.gif" width="336" height="195" alt="GoLand의 Run 메뉴" title="" /></p>
<p>GoLand에서 소스코드에 Breakpoint를 지정해 놓고 필요한 대로 멈추거나 진행하면서 각 변수의 값을 확인할 수 있다.</p>
<p style="text-align: center;"><img src="//blog.outsider.ne.kr/attach/1/3788399065.gif" width="750" height="444" alt="GoLand에서 디버깅하는 화면" title="" /></p>
<p>위 화면은 <a href="https://github.com/ethereum/go-ethereum/blob/master/consensus/ethash/ethash.go">consensus/ethash/ethash.go</a>의 <a href="https://github.com/ethereum/go-ethereum/blob/master/consensus/ethash/ethash.go#L472">New 메서드</a>에서 멈춘 화면이다. delve도 이때 처음 써봤지만 익숙하지 않은 이더리움 등의 코드를 파악할 때는 IDE의 도움을 받는 것이 더 편한 것 같다.</p>
<p><strong><a href="https://blog.outsider.ne.kr/1433?commentInput=true#entry1433WriteComment">댓글 쓰기</a></strong></p>Go 언어의 웹 프레임워크 Gin 사용하기Outsiderhttps://blog.outsider.ne.kr/13712018-04-30T02:53:00+09:002018-04-30T02:53:00+09:00<p>1월부터 하는 <a href="https://kubernetes.io/">Kubernetes</a>에서 실제로 서비스를 구축하기 위해 간단한 마이크로 서비스를 만들어 보고 있다. 주로 Node.js로 하는 게 편하지만 다른 언어도 배워보고 싶어서 Golang을 선택했다. 아주 간단한 API 서버라서 웹 프레임워크가 필요했는데 Go 언어에 대해 아는 게 없어서 좀 찾아봤다.</p>
<p><a href="https://blog.usejournal.com/top-6-web-frameworks-for-go-as-of-2017-23270e059c4b">Top 6 web frameworks for Go as of 2017</a>같은 글을 참고했는데 본격적으로 사용해 보지 않으면 감이 잘 안와서 선택이 쉽지는 않았다. 웹 MVC 프레임워크로는 <a href="https://github.com/astaxie/beego">beego</a>나 <a href="https://github.com/gobuffalo/buffalo">Buffalo</a>가 있었지만 주로 마이크로 웹 프레임워크를 더 선호하는 편이라서 <a href="https://github.com/gin-gonic/gin">Gin</a>이나 <a href="https://github.com/labstack/echo">echo</a>, <a href="https://github.com/kataras/iris">Iris</a>에 관심이 갔다.</p>
<p><a href="https://jaehue.github.io/post/go-my-way-1-webframework/">Go My Way #1 - 웹 프레임워크</a>를 보다 보니 일부러 프로젝트가 활발한 것처럼 보이려고 <a href="https://github.com/avelino/awesome-go/pull/1137">속이던 행동이 발견된</a> Iris는 일찌감치 제외하고 Gin와 echo 중에서 고민하다가 크게 차이는 없는 것 같아서(특히 지금 내 용도에서는...) 좀 더 스타가 많은 Gin을 선택했다.<br />
<br></p>
<h1><a href="https://gin-gonic.github.io/gin/">Gin</a></h1>
<p>저장소의 <a href="https://github.com/gin-gonic/gin">README</a>에 나온 내용대로 <code>main.go</code> 파일을 생성한다.</p>
<pre class="line-numbers"><code class="language-go">// main.go
package main
import "github.com/gin-gonic/gin"
func main() {
r := gin.Default()
r.GET("/ping", func(c *gin.Context) {
c.JSON(200, gin.H{
"message": "pong",
})
})
r.Run() // listen and serve on 0.0.0.0:8080
}
</code></pre>
<p>Go 언어에서 <a href="https://blog.outsider.ne.kr/1354">dep으로 의존성 관리</a>를 하고 있어서 dep을 초기화한다.</p>
<pre class="line-numbers"><code class="language-bash">$ dep init
</code></pre>
<p>초기화 후 만들어진 <code>Gopkg.toml</code> 파일에 <code>gin</code>을 사용하기 위해 다음 내용을 추가한다.</p>
<pre class="line-numbers"><code class="language-clike">[[constraint]]
name = "github.com/gin-gonic/gin"
branch = "master"
</code></pre>
<p>이제 추가한 의존성을 설치한다.</p>
<pre class="line-numbers"><code class="language-bash">$ dep ensure
</code></pre>
<p>의존성 설치도 완료되었으면 Hello World 식 예제 애플리케이션을 실행할 수 있다.</p>
<pre class="line-numbers"><code class="language-bash">$ go run main.go
[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.
[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
- using env: export GIN_MODE=release
- using code: gin.SetMode(gin.ReleaseMode)
[GIN-debug] GET /ping --> main.main.func1 (3 handlers)
[GIN-debug] Environment variable PORT is undefined. Using port :8080 by default
[GIN-debug] Listening and serving HTTP on :8080
</code></pre>
<p>Gin 서버가 <a href="http://localhost:8080">http://localhost:8080</a>에 떴으므로 이 서버에 요청을 보내면 정상적으로 뜬 것을 확인할 수 있다.</p>
<pre class="line-numbers"><code class="language-bash">$ curl -i http://localhost:8080/ping
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Date: Sun, 29 Apr 2018 16:45:36 GMT
Content-Length: 18
{"message":"pong"}
</code></pre>
<p>실제 웹 애플리케이션을 작성하려면 데이터베이스 라이브러리도 필요하고 프로젝트 폴더 구조도 만들어야 하지만 이 글에서는 Gin에 대한 설정만 얘기하고 있으므로 간단히 Gin의 라우팅만 살펴보자.</p>
<pre class="line-numbers"><code class="language-go">// main.go
package main
import (
"net/http"
"github.com/gin-gonic/gin"
)
func main() {
r := gin.Default()
v1 := r.Group("/v1")
{
v1.GET("/health", health)
v1.POST("/signup", signup)
v1.POST("/login", login)
}
r.Run()
}
func health(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"message": "ok",
})
}
func signup(c *gin.Context) {
c.JSON(http.StatusCreated, gin.H{
"message": "signed up",
})
}
func login(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"message": "logged in",
})
}
</code></pre>
<p>보통 API 서버는 URL에 접두사가 붙으므로 <code>r.Group("/v1")</code>을 사용하면 반복해서 사용하지 않아도 된다. 위 설정은 <code>http://localhost:3000/v1/health</code>로 요청을 받게 된다. 상태 코드를 숫자로 적어도 되지만 <a href="https://golang.org/pkg/net/http/#pkg-constants">net/http</a>에서 상수를 가져와서 HTTP 상태 코드로 사용할 수 있다.</p>
<pre class="line-numbers"><code class="language-bash">$ PORT=3000 go run main.go
[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.
[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
- using env: export GIN_MODE=release
- using code: gin.SetMode(gin.ReleaseMode)
[GIN-debug] GET /v1/health --> main.health (3 handlers)
[GIN-debug] POST /v1/signup --> main.signup (3 handlers)
[GIN-debug] POST /v1/login --> main.login (3 handlers)
[GIN-debug] Environment variable PORT="3000"
[GIN-debug] Listening and serving HTTP on :3000
</code></pre>
<p>그리고 gin은 기본 포트로 <code>8080</code>을 사용하지만 <code>PORT</code> 환경변수를 지정하면 해당 포트로 서버를 실행하게 된다.</p>
<p><strong><a href="https://blog.outsider.ne.kr/1371?commentInput=true#entry1371WriteComment">댓글 쓰기</a></strong></p>Go에서 dep으로 의존성 관리하기Outsiderhttps://blog.outsider.ne.kr/13542018-02-21T22:33:46+09:002018-02-21T22:33:46+09:00<p><a href="https://golang.org/">Go</a>로 간단한 프로젝트를 하려고 보니 처음 신경 쓰이는 건 의존성 관리를 어떻게 하는가였다. <a href="https://github.com/golang/go/wiki/PackageManagementTools">PackageManagementTools</a> 문서에 패키지 관리 도구가 정리되어 있다.</p>
<blockquote>
<p>dep, is an official experiment for the dependency tool. It is currently being implemented, in pre-alpha state and should be used with caution as "Lots of functionality is knowingly missing or broken".</p>
</blockquote>
<p>여러 도구가 있지만 <a href="https://github.com/golang/dep">dep</a>을 공식 의존성 관리 도구로 언급하고 있다. 아직 실험적으로 구현 중이지만 공식이라고 하니 그냥 dep을 쓰기로 했다. <a href="https://github.com/golang/dep">dep 저장소</a>를 보면 프로덕션에서 사용해도 문제없다고 하니(dep is safe for production use.)아직 Go로 뭔가 큰 걸 만들 건 아니지만 써도 괜찮을 것 같다.</p>
<h1>dep 설치</h1>
<p>dpe은 go 1.8 이상의 버전이 필요하다고 한다. 현재 최신 버전이 1.10이니 나한테는 영향은 없을 것 같다. <a href="https://golang.org/cmd/go/#hdr-Download_and_install_packages_and_dependencies"><code>go get</code></a>으로 설치해도 되지만(<code>go get -u github.com/golang/dep/cmd/dep</code>) 소스 파일까지는 필요 없을 것 같아서 저장소에 안내된 대로 <a href="https://brew.sh/index_ko.html">Homebrew</a>로 설치했다.</p>
<pre class="line-numbers"><code class="language-bash">$ brew install dep
==> Installing dependencies for dep: go
==> Installing dep dependency: go
==> Downloading https://homebrew.bintray.com/bottles/go-1.10.high_sierra.bottle.tar.gz
######################################################################## 100.0%
==> Pouring go-1.10.high_sierra.bottle.tar.gz
==> Caveats
A valid GOPATH is required to use the `go get` command.
If $GOPATH is not specified, $HOME/go will be used by default:
https://golang.org/doc/code.html#GOPATH
You may wish to add the GOROOT-based install location to your PATH:
export PATH=$PATH:/usr/local/opt/go/libexec/bin
==> Summary
/usr/local/Cellar/go/1.10: 8,150 files, 336.9MB
==> Installing dep
==> Downloading https://homebrew.bintray.com/bottles/dep-0.4.1_1.high_sierra.bottle.tar.gz
######################################################################## 100.0%
==> Pouring dep-0.4.1_1.high_sierra.bottle.tar.gz
/usr/local/Cellar/dep/0.4.1_1: 7 files, 8.8MB
</code></pre>
<p><code>dep</code>으로 버전을 출력해 볼 수 있으면 잘 설치된 것이다.</p>
<pre class="line-numbers"><code class="language-bash">$ dep version
dep:
version : v0.4.1
build date : 2018-01-27
git hash : 37d9ea0
go version : go1.9.3
go compiler : gc
platform : darwin/amd64
</code></pre>
<p>새 버전으로 업그레이드하려면 <code>brew upgrade dep</code> 명령어를 사용하면 된다.<br />
<br></p>
<h1>dep으로 의존성 관리하기</h1>
<p><a href="https://golang.org/cmd/go/#hdr-Download_and_install_packages_and_dependencies"><code>go get</code></a>이 이미 있고 <code>goimports</code> 등을 사용하려고 <a href="https://godoc.org/golang.org/x/tools">tools</a>를 설치할 때는 <code>go get</code>으로 설치했다. <code>go get</code>으로 설치하면 <code>$GOPATH/src</code> 아래 소스코드가 들어가는데 이 아래 있으면 로컬에서 <code>import</code>로 불러서 사용할 수 있으므로 <code>go get</code>으로 의존성을 설치해서 사용할 수도 있다.(Go 개발자들이 정확히 어떻게 쓰는지는 아직 잘 모르지만...)</p>
<p>하지만 소스 코드에 <code>import</code> 구문이 있다고 하더라도 <code>go get</code>으로 설치해서 사용하면 본인은 괜찮지만 다른 사람은 의존성을 일일이 찾아서 설치해야 해서 협업하기만 어려우므로 프로젝트의 의존성은 <code>dep</code>같은 도구가 적합하다고 생각한다.</p>
<p>새 프로젝트에서 dep으로 의존성을 관리하는 방법은 <a href="https://golang.github.io/dep/docs/new-project.html">dep 문서</a>에 잘 나와 잇다. 처음에 <code>dep</code>으로 의존성을 관리하려면 초기화를 해야 한다.</p>
<pre class="line-numbers"><code class="language-bash">$ dep init
</code></pre>
<p>초기화를 하면 아래처럼 2개의 파일과 1개의 디렉터리가 생성된다.</p>
<pre class="line-numbers"><code class="language-bash">├── Gopkg.lock
├── Gopkg.toml
└── vendor/
</code></pre>
<p><br></p>
<h2><a href="https://golang.github.io/dep/docs/Gopkg.toml.html">Gopkg.toml</a></h2>
<p><code>Gopkg.toml</code>은 초기화할 때 생성되는 파일로 <code>dep</code>의 동작을 감시하는 여러 규칙을 담고 있다고 한다.</p>
<ul>
<li>의존성 규칙: <code>constraints</code>, <code>overrides</code>로 사용자가 적용할 의존성의 버전과 어디서 가져와야 하는지를 지정한다.</li>
<li>패키지 그래프 규칙: <code>required</code>, <code>ignored</code>로 임포트 경로를 추가하거나 제외해서 임포트 그래프를 조작할 수 있다.</li>
<li><code>metadata</code>: 사용자가 정의한 키-값의 맵으로 dep은 무시할 것이다.</li>
<li><code>prune</code>: 어떤 파일과 디렉터리가 불필요하다고 판단할지 설정해서 <code>vendor/</code>에서 자동으로 제거한다.</li>
</ul>
<p>아래는 <code>dep init</code>했을 때 생긴 <code>Gopkg.toml</code>의 내용이다.</p>
<pre class="line-numbers"><code class="language-bash"># Gopkg.toml example
#
# Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md
# for detailed Gopkg.toml documentation.
#
# required = ["github.com/user/thing/cmd/thing"]
# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"]
#
# [[constraint]]
# name = "github.com/user/project"
# version = "1.0.0"
#
# [[constraint]]
# name = "github.com/user/project2"
# branch = "dev"
# source = "github.com/myfork/project2"
#
# [[override]]
# name = "github.com/x/y"
# version = "2.4.0"
#
# [prune]
# non-go = false
# go-tests = true
# unused-packages = true
[prune]
go-tests = true
unused-packages = true
</code></pre>
<p><br></p>
<h2><a href="https://golang.github.io/dep/docs/Gopkg.lock.html">Gopkg.lock</a></h2>
<p>프로젝트의 의존성 그래프의 스냅숏을 가진 파일이다. 이 파일은 <code>dep ensure</code> 할 때 변경된다.</p>
<p>아래는 생성된 <code>Gopkg.lock</code> 파일의 내용이다.</p>
<pre class="line-numbers"><code class="language-clike"># This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
[solve-meta]
analyzer-name = "dep"
analyzer-version = 1
inputs-digest = "ab4fef131ee828e96ba67d31a7d690bd5f2f42040c6766b1b12fe856f87e0ff7"
solver-name = "gps-cdcl"
solver-version = 1
</code></pre>
<h2>의존성 추가</h2>
<p>웹 프레임워크로 <a href="https://github.com/gin-gonic/gin">gin</a>을 사용하기 위해 <code>dep ensure</code>로 의존성을 추가해 보자. <code>ensure</code>가 프로젝트에 의존성을 설치하는 명령어고 새로운 의존성을 추가하려면 <code>dep ensure -add</code>를 사용해야 한다.</p>
<pre class="line-numbers"><code class="language-bash">$ dep ensure -add github.com/gin-gonic/gin
no dirs contained any Go code
</code></pre>
<p>현재는 Go 코드를 전혀 작성하지 않아서 오류가 난다. 여기선 Go 코드는 중요하지 않으므로 다음 내용으로 <code>main.go</code> 파일을 만들자.</p>
<pre class="line-numbers"><code class="language-go">package main
</code></pre>
<p>다시 <code>gin</code>을 추가해 보자.</p>
<pre class="line-numbers"><code class="language-bash">$ dep ensure -add github.com/gin-gonic/gin
Fetching sources...
"github.com/gin-gonic/gin" is not imported by your project, and has been temporarily added to Gopkg.lock and vendor/.
If you run "dep ensure" again before actually importing it, it will disappear from Gopkg.lock and vendor/.
</code></pre>
<p>아까와 달리 추가되었다. 안내 문구를 보면 아직 <code>github.com/gin-gonic/gin</code>를 아직 임포트 한 곳이 없어서 <code>Gopkg.lock</code>와 <code>vender/</code>에는 추가되었지만 임포트하지 않고 <code>dep ensure</code>를 실행하면 지워질 것이라고 한다.</p>
<p><code>Gopkg.toml</code> 파일을 보면 하단에 <code>gin</code>이 추가된 것을 볼 수 있다.</p>
<pre class="line-numbers"><code class="language-clike">[[constraint]]
name = "github.com/gin-gonic/gin"
version = "1.2.0"
</code></pre>
<p><code>Gopkg.lock</code> 파일은 아래처럼 의존성의 자세한 정보가 추가되고 <code>[solve-meta]</code>의 <code>inputs-digest</code> 정보가 변경되게 된다.</p>
<pre class="line-numbers"><code class="language-bash">[[projects]]
name = "github.com/gin-gonic/gin"
packages = [
".",
"binding",
"render"
]
revision = "d459835d2b077e44f7c9b453505ee29881d5d12d"
version = "v1.2"
</code></pre>
<p><br></p>
<h2>dep 관련 파일 버전 관리</h2>
<p>위에서 보았듯이 dep이 생성한 파일은 <code>Gopkg.toml</code>, <code>Gopkg.lock</code>와 <code>vender</code> 디렉터리다. 좀 찾아봤는데 <code>Gopkg.toml</code>, <code>Gopkg.lock</code>는 둘 다 커밋하는 것 같다. <code>Gopkg.lock</code>가 꼭 필요한지는 확인 안해 봤지만 <code>npm</code>을 생각해 보면 <code>package-lock.json</code>과 같은 역할을 하는 거로 보인다. 내 취향으로는 프로덕션에서 서비스하기 전까지는 lock 파일을 저장소에서 관리하는 편은 아니지만 요즘 추세는 lock 파일도 보통 넣는 것 같고 해서 dep에서도 두 파일을 모두 Git에 넣기로 했다.</p>
<p><code>vender</code> 디렉터리의 경우 딱 봐도 Git에서 관리할 필요가 없어 보이는데 dep의 <a href="https://golang.github.io/dep/docs/FAQ.html#should-i-commit-my-vendor-directory">FAQ 문서</a>를 보면 이 디렉터리를 커밋해서 관리할 때의 장단점이 나와 있다. 말이 장단점이지 그냥 의존성 파일을 다 저장소에 관리할지 말지의 얘기라서 관례대로 나는 <code>vender</code> 디렉터리는 git에서 제외했다.<br />
<br></p>
<h2>의존성 설치</h2>
<p>프로젝트를 협업하는 경우 <code>Gopkg.toml</code>, <code>Gopkg.lock</code>를 가진 저장소를 받아서 의존성을 설치해서 개발해야 한다.</p>
<pre class="line-numbers"><code class="language-bash">$ dep ensure
</code></pre>
<p>이때는 <code>dep ensure</code>만 실행해야 한다. 참고로 여기서는 <code>dep</code>만 설명하고 있지만, 앞에서 살펴본 대로 go 소스코드에서 <code>import "github.com/gin-gonic/gin"</code> 처럼 임포트하고 있지 않다면 이 의존성이 지워지게 된다.</p>
<p><strong><a href="https://blog.outsider.ne.kr/1354?commentInput=true#entry1354WriteComment">댓글 쓰기</a></strong></p>