Go Modules
Go module คืออะไร?
หัวข้อที่มีชื่อว่า “Go module คืออะไร?”module คือกลุ่มของ Go packages ที่มี version ร่วมกันและแจกจ่ายเป็นหน่วยเดียว โปรเจกต์ Go สมัยใหม่ทุกโปรเจกต์เริ่มต้นด้วย module ระบบ module แทนที่ workflow GOPATH แบบเก่าใน Go 1.11 และกลายเป็นค่าเริ่มต้นใน Go 1.16
Playground note: คำสั่ง
go modต้องการgobinary และ file system — ไม่สามารถรันใน in-browser runner ได้ ให้ทำตามขั้นตอนด้านล่างใน terminal ของคุณ
การ initialise module
หัวข้อที่มีชื่อว่า “การ initialise module”mkdir myapp && cd myappgo mod init github.com/yourname/myappคำสั่งนี้สร้างไฟล์ go.mod — manifest ของ module:
module github.com/yourname/myapp
go 1.27.0module path (github.com/yourname/myapp) ทำหน้าที่ทั้งเป็น import path prefix สำหรับ packages ของคุณเองและเป็น identifier ที่ modules อื่นใช้เมื่อ depend บน module ของคุณ ไม่จำเป็นต้องเป็น URL จริงระหว่าง development แต่ต้องไม่ซ้ำกันหากคุณเผยแพร่ใน Go module proxy
สังเกตเวอร์ชัน: go mod init เขียนเวอร์ชันจริงของ toolchain ที่รันมัน — ไม่ใช่ต่ำกว่าหนึ่ง minor release ยืนยันในเครื่องแล้ว: รันด้วย Go 1.26.5 จะเขียน go 1.26.5; บน Go 1.27.0 จะเขียน go 1.27.0 หากต้องการตรึง floor เองให้ใช้:
go.mod และ go.sum
หัวข้อที่มีชื่อว่า “go.mod และ go.sum”หลังจากเพิ่ม dependency คุณจะพบสองไฟล์ใน root ของ module:
go.mod — ประกาศ module path, Go version ขั้นต่ำ, และ dependencies ทั้งหมดทั้งทางตรงและทางอ้อม:
module github.com/yourname/myapp
go 1.27
toolchain go1.27.1
ignore ./testdata/fixtures
require ( github.com/go-chi/chi/v5 v5.2.1)
require ( golang.org/x/text v0.28.0 // indirect)สามบรรทัดที่ควรดูให้ละเอียด:
go 1.27คือเวอร์ชันของภาษาและ library ที่ module นี้ยึดเป็นฐาน — compiler บังคับใช้ และตั้งแต่ Go 1.27 การตรวจstdversionของgo testจะปฏิเสธการเรียก API ที่ใหม่กว่าบรรทัดนี้toolchain go1.27.1(ไม่บังคับ) ตรึง toolchain ตัวเป๊ะ binarygoที่เก่ากว่าจะดาวน์โหลดและรันตัวนั้นให้อัตโนมัติignore(Go 1.25) บอกคำสั่งgoให้ข้าม directory tree นั้นไปเลย — มีประโยชน์กับโฟลเดอร์ใหญ่แบบnode_modulesหรือ fixture ที่ไม่ใช่โค้ด Go
บล็อก require สองบล็อกไม่ใช่เรื่องสไตล์: สำหรับ module go 1.27+ go mod tidy บังคับ layout นี้ — dependency ทางตรงในบล็อกแรก ตัว // indirect ในบล็อกที่สอง — และรวมบล็อกซ้ำที่เจอเข้าด้วยกัน
go.sum — ทะเบียน cryptographic checksum ทุก zip archive ของ dependency และ go.mod ถูก hash ที่นี่ Go toolchain ตรวจสอบ hash เหล่านี้ทุก build เพื่อรับประกัน reproducibility และป้องกัน supply-chain attacks ให้ commit ทั้งสองไฟล์ลง version control
การเพิ่ม dependency
หัวข้อที่มีชื่อว่า “การเพิ่ม dependency”# Add the latest version of a modulego get github.com/go-chi/chi/v5
# Add a specific version
# Upgrade a dependency to its latest patch/minor releasego get -u github.com/go-chi/chi/v5หลังจาก go get source code ของคุณสามารถ import package ได้:
import "github.com/go-chi/chi/v5"go mod tidy
หัวข้อที่มีชื่อว่า “go mod tidy”เมื่อเวลาผ่านไป code และ dependencies อาจไม่สอดคล้องกัน — คุณอาจลบ import หรือเพิ่มโดยไม่ได้รัน go get go mod tidy แก้ไขทั้งสองกรณี:
go mod tidyคำสั่งนี้เพิ่ม require lines ที่ขาดหาย ลบ require lines สำหรับ packages ที่ไม่ได้ import แล้ว อัปเดต go.sum ให้ตรงกัน และ (สำหรับ module go 1.27+) จัด go.mod ให้เป็น layout สองบล็อก direct/indirect รันก่อน commit ทุกครั้ง
Minimal version selection
หัวข้อที่มีชื่อว่า “Minimal version selection”Go เลือก version ของแต่ละ dependency ที่จะใช้จริงด้วย algorithm ที่เรียกว่า minimal version selection (MVS): สำหรับทุก module ใน build Go เลือก version ที่ สูงที่สุด ที่ถูกขอโดยอะไรก็ตามใน dependency graph ไม่ใช่ version ล่าสุดที่มีอยู่ ถ้า module ของคุณ require github.com/foo/bar v1.2.0 และ dependency ตัวหนึ่งของคุณ require github.com/foo/bar v1.4.0 build จะใช้ v1.4.0 — ค่าสูงสุดของสิ่งที่ถูกขอจริง — ไม่ใช่ v1.9.0 แค่เพราะมันเพิ่ง release ล่าสุด
require github.com/google/uuid v1.6.0$ go mod graphexample.com/modw github.com/google/[email protected]go mod graph พิมพ์ทุก requirement edge ใน build: <module> <requires> <at-version> มัน deterministic และ reproducible บนทุกเครื่องเมื่อ go.mod/go.sum เหมือนกัน — ตรงข้ามกับ resolver ที่อาจเลือก “ล่าสุด” คนละตัวในแต่ละวัน
go mod why
หัวข้อที่มีชื่อว่า “go mod why”เมื่อคุณบอกไม่ได้ว่าทำไม dependency ตัวหนึ่งถึงอยู่ใน module ของคุณ go mod why พิมพ์ import path ที่สั้นที่สุดที่ดึงมันเข้ามา ยืนยันแล้วจริง:
go mod why github.com/google/uuidexample.com/modwgithub.com/google/uuidอ่านเป็นสาย: module ของคุณ import github.com/google/uuid โดยตรง สายของโปรเจกต์จริงมักยาวกว่านี้ — module ของคุณ → framework บางตัว → utility lib บางตัว — และ go mod why คือวิธีที่เร็วที่สุดในการหาว่า direct dependency ตัวไหนลาก dependency ที่ไม่คาดคิดเข้ามา แทนที่จะอ่าน go.sum แล้วเดาเอา
replace, exclude, retract และ go work
หัวข้อที่มีชื่อว่า “replace, exclude, retract และ go work”replace แทนที่ source ของ module path ด้วยอย่างอื่น — local directory ระหว่าง development หรือ fork ที่ fix แล้ว:
replace github.com/foo/bar => ../local-barreplace github.com/foo/bar => github.com/yourfork/bar v1.2.3-patchedreplace ใน go.mod ของ main module ของคุณเป็นเรื่องปกติมาก — แต่มันมีผลแค่กับ build ของ module คุณเองเท่านั้น ใครก็ตามที่ import module ของคุณจะไม่เห็นบรรทัด replace ของคุณเลย build ของเขาจะ resolve github.com/foo/bar ตามปกติ นี่คือความเข้าใจผิดที่พบบ่อยที่สุดเกี่ยวกับ directive นี้
exclude ลบ version หนึ่งออกจากการพิจารณาทั้งหมด บังคับให้ MVS เลือก version อื่นแทน — ใช้กับ version ที่รู้ว่าพังหรือถูก yank retract ทำตรงข้าม ประกาศจากข้างในgo.modของ module ที่ได้รับผลกระทบเอง: มันบอก tooling ของคนอื่นว่า “อย่าเลือก version นี้ของฉัน” ปรากฏเป็น warning ใน go list -m -u และ go get ยืนยันแล้วจริงกับ go help mod edit ว่า go mod edit เขียนทั้งสามได้โดยตรง:
go mod edit -replace github.com/foo/bar=../local-bargo mod edit -retract v1.0.1replace github.com/foo/bar => ../local-barexclude github.com/bad/pkg v1.0.0retract v1.0.1go work แก้ปัญหา local-development แบบเดียวกับ replace โดยไม่ต้องแก้ go.mod เลย — มีประโยชน์เมื่อพัฒนาหลาย module พร้อมกัน ยืนยันแล้วจริงกับ module จริงสองตัว: app (ที่ require example.com/lib v0.0.0 version ที่ไม่มีอยู่จริง) และ directory lib ข้าง ๆ กัน:
go work init ./app ./libgo 1.26.5
use ( ./app ./lib)cd app && go run .# hello from local libapp/go.mod ไม่ถูกแตะเลย — ไม่มีบรรทัด replace ถูกเพิ่ม — และ build ยัง resolve example.com/lib ไปที่ local directory ได้ เพราะ go.work override module resolution ให้ทุก module ที่อยู่ใน block use ลบหรือ ignore go.work (ตั้งใจให้อยู่นอก version control) แล้ว build จะกลับไปเป็นไปตามที่ go.mod บอกอย่างเดียว
go install pkg@version vs go get
หัวข้อที่มีชื่อว่า “go install pkg@version vs go get”ทั้งคู่ดึงโค้ดมา แต่ตอบคำถามคนละอย่าง go get จัดการ dependency graph ของmodule คุณเอง — มันแก้ go.mod/go.sum และมีไว้สำหรับ package ที่โค้ดคุณ import go install pkg@version build และ install binary เดี่ยว ๆ ลง $GOBIN (default $HOME/go/bin) และตาม go help install มันทำแบบนั้นโดย “ignoring the go.mod file in the current directory or any parent directory” — มันไม่แตะ go.mod ของโปรเจกต์คุณเลย
go install golang.org/x/tools/cmd/stringer@latest # installs a CLI binary — go.mod untouchedgo get golang.org/x/tools/cmd/stringer # WRONG for a tool — adds a require lineใช้ go install สำหรับ developer tool (linter, code generator, stringer, mockgen) ที่คุณรันจาก command line ใช้ go get เฉพาะกับ package ที่ source code ของคุณ import จริง ๆ
Tool directive (Go 1.24)
หัวข้อที่มีชื่อว่า “Tool directive (Go 1.24)”ก่อน Go 1.24 โปรเจกต์ตรึง version ของ tool ด้วยการสร้างไฟล์ tools.go ที่มี blank import คุมด้วย build tag (//go:build tools ตามด้วย import _ "golang.org/x/tools/cmd/stringer") เพื่อให้ go mod tidy track version ของมันเท่านั้น — เป็น workaround ที่รู้กันดีแต่ค่อนข้างอึดอัด tool directive มาแทนที่: go get -tool เพิ่ม entry ของ tool ที่ pin version จริงลงใน go.mod เอง
go get -tool golang.org/x/tools/cmd/stringer@latestยืนยันแล้วจริง — go.mod ที่ได้:
module example.com/tooldir
go 1.26.0
tool golang.org/x/tools/cmd/stringer
require ( golang.org/x/mod v0.41.0 // indirect golang.org/x/sync v0.23.0 // indirect golang.org/x/tools v0.50.0 // indirect)รันตรงจาก module เลย ไม่ต้องหาใน $GOBIN:
go tool stringer -hทุกคนในทีมที่รัน go mod tidy/go build ได้ tool version เดียวกันเป๊ะ — ประเด็นทั้งหมดที่ tools.go พยายามแก้ปัญหา แต่ไม่ต้องมี boilerplate แบบ blank-import
Environment variable ของ proxy และ privacy
หัวข้อที่มีชื่อว่า “Environment variable ของ proxy และ privacy”โดย default go get/go mod download ดึงมาจาก public Go module proxy และ verify กับ public checksum database:
| ตัวแปร | Default | หน้าที่ |
|---|---|---|
GOPROXY | https://proxy.golang.org,direct | ดึง module มาจากไหน; direct fallback ไปที่ VCS ของ module |
GOSUMDB | sum.golang.org | checksum database ที่ใช้ verify การ download กันการปลอมแปลง |
GOPRIVATE | (ว่าง) | glob pattern ของ module path ที่จะดึงตรง ข้ามทั้ง proxy และ sumdb — สำหรับ repository ภายใน/private |
GONOSUMCHECK / GOFLAGS=-insecure | — | ทางออกฉุกเฉินแบบเก่า/edge-case; GOPRIVATE (หรือแคบกว่าคือ GONOSUMDB/GONOSUMCHECK) คือวิธีที่รองรับสำหรับ opt-out path หนึ่ง |
export GOPRIVATE=github.com/yourcompany/*การตั้ง GOPRIVATE สำหรับ org ของบริษัทคือวิธีแก้มาตรฐานของ “go get ค้างหรือ fail กับ internal repo ของเรา” — ถ้าไม่ตั้งไว้ public proxy และ sumdb จะถูกเรียกให้ตรวจ module path ที่มันไม่มีทางเห็นได้จริง ๆ
Toolchain directive และ godebug directive
หัวข้อที่มีชื่อว่า “Toolchain directive และ godebug directive”directive toolchain (toolchain go1.27.1 ใน go.mod) ตรึง Go toolchain version ที่แน่นอน ถ้า go binary ที่เรียก build เก่ากว่า toolchain ที่ pin ไว้ มันจะดาวน์โหลดและรันตัวที่ pin ไว้ให้อัตโนมัติ — ควบคุมด้วย GOTOOLCHAIN (default auto; ตั้งเป็น local เพื่อปิดการดาวน์โหลดอัตโนมัติและใช้ go binary ที่ติดตั้งไว้เสมอ)
directive godebug ตั้งค่า GODEBUG ระดับ module แทนที่จะเป็นระดับ process — มันไปกับ module จึงทำให้ทุก build ได้ runtime behavior เดียวกันสำหรับ setting ที่เปลี่ยน default ข้าม Go version:
godebug http2client=0ยืนยันผ่าน go help mod edit: go mod edit -godebug=key=value เขียนบรรทัดนี้ตรง ๆ นี่คือวิธีที่ toolchain ให้ module opt กลับไปใช้ default เก่า (หรือ opt เข้า default ใหม่ก่อนใคร) โดยไม่ต้องให้ developer ทุกคนตั้ง environment variable เอง
Module cache
หัวข้อที่มีชื่อว่า “Module cache”ทุก module version ที่ดาวน์โหลดจะถูก extract ครั้งเดียวลง shared, read-only cache ที่ GOMODCACHE (default $GOPATH/pkg/mod) และถูกใช้ซ้ำโดยทุก module บนเครื่องที่ต้องการ version นั้นเป๊ะ ๆ — ยืนยันแล้วจริง directory นี้มีขนาด 1.8 GB หลังจากทำงานตามปกติในคอร์สนี้
go env GOMODCACHEgo clean -modcache # deletes the entire cache — next build re-downloads everything it needsให้ treat มันเป็น cache ล้วน ๆ: ลบได้อย่างปลอดภัยเมื่อ disk เต็ม และ Go จะเติมกลับให้เองจาก GOPROXY (หรือ direct) ครั้งถัดไปที่มีอะไรต้องการ module version ที่หายไป
Semantic import versioning
หัวข้อที่มีชื่อว่า “Semantic import versioning”Go บังคับใช้กฎที่เรียกว่า semantic import versioning: หาก module ออก v2 ขึ้นไป import path ต้องรวม major version suffix ด้วย
# v1 — no suffix needed# import "github.com/foo/bar"
# v2+ — /v2 suffix is part of the import path# import "github.com/foo/bar/v2"ซึ่งหมายความว่า major version สองเวอร์ชันของ module เดียวกันสามารถอยู่ร่วมกันใน binary เดียวได้โดยไม่ขัดแย้ง การเปลี่ยน import path เป็นเจตนา: v2 API อนุญาตให้ไม่เข้ากันกับ v1
Version ถูกเลือกอย่างไร
หัวข้อที่มีชื่อว่า “Version ถูกเลือกอย่างไร”flowchart TD
A["your go.mod requires foo v1.2.0"] --> D["minimal version selection"]
B["dependency X requires foo v1.4.0"] --> D
C["dependency Y requires foo v1.1.0"] --> D
D --> E["build uses foo v1.4.0 — the maximum requested, never a newer 'latest'"]
E --> F{"exclude foo v1.4.0?"}
F -->|yes| G["MVS picks the next-highest requested version instead"]
F -->|no| H["go.sum records and verifies v1.4.0's checksum"] ข้อแลกเปลี่ยน
หัวข้อที่มีชื่อว่า “ข้อแลกเปลี่ยน”| สิ่งที่ได้ | ประโยชน์ | ต้นทุน |
|---|---|---|
go.mod (explicit versioning) | reproducible build, dependency ชัดเจน | ต้อง update เมื่อ dependency เปลี่ยน — go mod tidy บ่อยๆ |
| Minimal version selection | resolution deterministic reproducible — ไม่มี upgrade แปลกใจ | ต้อง go get -u เองเสมอถึงจะเดินหน้า ไม่เกิดเองโดยปริยาย |
go work สำหรับ local multi-module dev | ไม่มีบรรทัด replace ให้ต้องจำไปลบก่อน publish | ไฟล์อีกไฟล์ที่ต้องดูแลให้อยู่นอก version control และ sync กับทีม |
| Tool directive | tool version ถูก track ใน go.mod ไม่ต้องมี boilerplate ของ tools.go | ต้องการ Go 1.24+ tooling ที่ contributor รู้จัก |
go.sum (hash verification) | ป้องกัน supply chain attack, verify integrity | ต้อง commit go.sum ด้วย — หลายคนลืม |
go mod vendor | air-gapped build ได้, ไม่ต้องพึ่ง network | vendor/ directory ใหญ่, ต้อง sync ทุก dependency update |
ความเข้าใจผิดที่พบบ่อย
หัวข้อที่มีชื่อว่า “ความเข้าใจผิดที่พบบ่อย”go getเหมือนnpm install—go getupdatego.modและ download source ไม่ install binary แยก ต่างจาก npm มาก ใช้go install pkg@versionสำหรับกรณีนั้น- สามารถ ignore
go.sum—go.sumต้อง commit ร่วมกับgo.modเพื่อ verify dependency integrity — ถ้าขาด build อาจไม่ reproducible replacedirective ห้ามใช้ใน production เด็ดขาด —replaceในgo.modของ main module ใช้ได้ (เช่น fork ที่ดูแลเอง) แต่ไม่มีผลกับผู้ที่ import module ของคุณ —go.modของ dependent มองไม่เห็นreplaceของคุณGOPATH/pkg/modเป็น module cache ถาวร —go clean -modcacheล้าง cache ได้ — ไม่ใช่ permanent storage ควร treat เป็น cache- Go เลือก version “ล่าสุด” ของทุก dependency ให้อัตโนมัติ — minimal version selection เลือก version ต่ำสุด ที่ตอบสนอง requirement ทุกตัวใน graph ไม่ใช่ตัวใหม่สุดที่มีอยู่ — upgrade เกิดขึ้นแบบ explicit เท่านั้น
- ไฟล์
tools.goที่มี blank import ยังเป็นวิธี pin tool version — ตั้งแต่ Go 1.24go get -toolและtooldirective ในgo.modทำแบบนี้ได้เองในตัว ไม่ต้องใช้ build-tag trick อีกแล้ว
💡 ตัวอย่างจากของจริง
Kubernetes มี
go.modที่มี dependency หลักร้อยตัว —vendor/ถูก commit เพื่อให้ build reproducible ในทุก environment
อ่านโค้ดที่ AI เขียน
หัวข้อที่มีชื่อว่า “อ่านโค้ดที่ AI เขียน”ความผิดพลาดเรื่อง module และ dependency แทบไม่แสดงเป็น compile error — มันแสดงเป็น “ที่เครื่องฉันมันทำงาน” หรือ security surprise เช็กเรื่องพวกนี้:
- CLI developer tool (linter, code generator) ถูก install ด้วย
go getในขั้นตอน setup ของ application code แทนที่จะเป็นgo install pkg@versionหรือtooldirective หรือเปล่า? นั่นเพิ่มมันเป็น dependency จริงของ module คุณแบบเงียบ ๆ replacedirective ในgo.modของ library ชี้ไปที่ local path (../something) หรือ fork ที่ไม่ได้ publish หรือเปล่า? นั่นทำให้ build พังสำหรับ consumer ทุกคนทันทีที่พวกเขา build นอก checkout ที่ผู้เขียนใช้พอดี —replaceใน library ที่ publish แล้วคือสัญญาณเตือน ไม่ใช่ความสะดวกส่วนตัวGOFLAGS=-insecureหรือGONOSUMCHECK/GOSUMDB=offถูกตั้งแบบ global ใน script หรือ CI config “เพื่อให้ build ทำงาน” แทนที่จะจำกัด exception ไว้เฉพาะ private module path ด้วยGOPRIVATEหรือเปล่า?- documentation setup ที่ generate มายังอธิบายไฟล์
tools.goที่มี blank import แบบ//go:build toolsอยู่หรือเปล่า ทั้งที่go.modของโปรเจกต์ประกาศgo 1.24ขึ้นไปแล้วและใช้tooldirective แทนได้? go.workถูก commit ลง version control ใน repository ของ library หรือเปล่า? มันมีไว้สำหรับ local multi-module development ส่วนตัว การ commit มันอาจ redirect build ของทีมไปที่ local path ที่ไม่มีอยู่จริงบนเครื่องพวกเขาแบบเงียบ ๆ
🐛 หา bug ในโค้ดนี้
# setup.sh — generated project bootstrap scriptgo get golang.org/x/tools/cmd/stringer@latestgo get github.com/golangci/golangci-lint/v2/cmd/golangci-lint@latestเฉลย
ทั้งสองตัวนี้เป็น developer tool ไม่ใช่ package ที่โค้ดของโปรเจกต์เอง import — แต่ go get เพิ่มแต่ละตัวเป็น dependency จริงใน block require ของ go.mod อยู่ดี เพราะนั่นคือสิ่งที่ go get มีไว้ทำ go mod tidy และ CI build ของทุกคนในทีมตอนนี้ดาวน์โหลดและ track stringer กับ golangci-lint ราวกับว่า application depend บนมันตอน runtime วิธีแก้คือ go install golang.org/x/tools/cmd/stringer@latest และ go install .../golangci-lint@latest (ซึ่ง install binary ลง $GOBIN โดยไม่แตะ go.mod เลย) หรือถ้าทีมอยากให้ version ถูก pin และแชร์ผ่าน git — go get -tool golang.org/x/tools/cmd/stringer@latest ซึ่งใช้ tool directive ที่สร้างมาเพื่อกรณีนี้เป๊ะ ๆ