ข้ามไปยังเนื้อหา

Go Modules

module คือกลุ่มของ Go packages ที่มี version ร่วมกันและแจกจ่ายเป็นหน่วยเดียว โปรเจกต์ Go สมัยใหม่ทุกโปรเจกต์เริ่มต้นด้วย module ระบบ module แทนที่ workflow GOPATH แบบเก่าใน Go 1.11 และกลายเป็นค่าเริ่มต้นใน Go 1.16

Playground note: คำสั่ง go mod ต้องการ go binary และ file system — ไม่สามารถรันใน in-browser runner ได้ ให้ทำตามขั้นตอนด้านล่างใน terminal ของคุณ

Terminal window
mkdir myapp && cd myapp
go mod init github.com/yourname/myapp

คำสั่งนี้สร้างไฟล์ go.mod — manifest ของ module:

module github.com/yourname/myapp
go 1.27.0

module 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 เองให้ใช้:

Terminal window
go get [email protected] # rewrites the go line to 1.27.x

หลังจากเพิ่ม 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 ตัวเป๊ะ binary go ที่เก่ากว่าจะดาวน์โหลดและรันตัวนั้นให้อัตโนมัติ
  • 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

Terminal window
# Add the latest version of a module
go get github.com/go-chi/chi/v5
# Add a specific version
go get github.com/go-chi/chi/[email protected]
# Upgrade a dependency to its latest patch/minor release
go get -u github.com/go-chi/chi/v5

หลังจาก go get source code ของคุณสามารถ import package ได้:

import "github.com/go-chi/chi/v5"

เมื่อเวลาผ่านไป code และ dependencies อาจไม่สอดคล้องกัน — คุณอาจลบ import หรือเพิ่มโดยไม่ได้รัน go get go mod tidy แก้ไขทั้งสองกรณี:

Terminal window
go mod tidy

คำสั่งนี้เพิ่ม require lines ที่ขาดหาย ลบ require lines สำหรับ packages ที่ไม่ได้ import แล้ว อัปเดต go.sum ให้ตรงกัน และ (สำหรับ module go 1.27+) จัด go.mod ให้เป็น layout สองบล็อก direct/indirect รันก่อน commit ทุกครั้ง

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 ล่าสุด

go.mod
require github.com/google/uuid v1.6.0
$ go mod graph
example.com/modw github.com/google/[email protected]

go mod graph พิมพ์ทุก requirement edge ใน build: <module> <requires> <at-version> มัน deterministic และ reproducible บนทุกเครื่องเมื่อ go.mod/go.sum เหมือนกัน — ตรงข้ามกับ resolver ที่อาจเลือก “ล่าสุด” คนละตัวในแต่ละวัน

เมื่อคุณบอกไม่ได้ว่าทำไม dependency ตัวหนึ่งถึงอยู่ใน module ของคุณ go mod why พิมพ์ import path ที่สั้นที่สุดที่ดึงมันเข้ามา ยืนยันแล้วจริง:

Terminal window
go mod why github.com/google/uuid
github.com/google/uuid
example.com/modw
github.com/google/uuid

อ่านเป็นสาย: module ของคุณ import github.com/google/uuid โดยตรง สายของโปรเจกต์จริงมักยาวกว่านี้ — module ของคุณ → framework บางตัว → utility lib บางตัว — และ go mod why คือวิธีที่เร็วที่สุดในการหาว่า direct dependency ตัวไหนลาก dependency ที่ไม่คาดคิดเข้ามา แทนที่จะอ่าน go.sum แล้วเดาเอา

replace แทนที่ source ของ module path ด้วยอย่างอื่น — local directory ระหว่าง development หรือ fork ที่ fix แล้ว:

replace github.com/foo/bar => ../local-bar
replace github.com/foo/bar => github.com/yourfork/bar v1.2.3-patched

replace ใน 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 เขียนทั้งสามได้โดยตรง:

Terminal window
go mod edit -replace github.com/foo/bar=../local-bar
go mod edit -exclude github.com/bad/[email protected]
go mod edit -retract v1.0.1
replace github.com/foo/bar => ../local-bar
exclude github.com/bad/pkg v1.0.0
retract v1.0.1

go work แก้ปัญหา local-development แบบเดียวกับ replace โดยไม่ต้องแก้ go.mod เลย — มีประโยชน์เมื่อพัฒนาหลาย module พร้อมกัน ยืนยันแล้วจริงกับ module จริงสองตัว: app (ที่ require example.com/lib v0.0.0 version ที่ไม่มีอยู่จริง) และ directory lib ข้าง ๆ กัน:

Terminal window
go work init ./app ./lib
go 1.26.5
use (
./app
./lib
)
Terminal window
cd app && go run .
# hello from local lib

app/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 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 ของโปรเจกต์คุณเลย

Terminal window
go install golang.org/x/tools/cmd/stringer@latest # installs a CLI binary — go.mod untouched
go 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 จริง ๆ

ก่อน 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 เอง

Terminal window
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:

Terminal window
go tool stringer -h

ทุกคนในทีมที่รัน go mod tidy/go build ได้ tool version เดียวกันเป๊ะ — ประเด็นทั้งหมดที่ tools.go พยายามแก้ปัญหา แต่ไม่ต้องมี boilerplate แบบ blank-import

โดย default go get/go mod download ดึงมาจาก public Go module proxy และ verify กับ public checksum database:

ตัวแปรDefaultหน้าที่
GOPROXYhttps://proxy.golang.org,directดึง module มาจากไหน; direct fallback ไปที่ VCS ของ module
GOSUMDBsum.golang.orgchecksum database ที่ใช้ verify การ download กันการปลอมแปลง
GOPRIVATE(ว่าง)glob pattern ของ module path ที่จะดึงตรง ข้ามทั้ง proxy และ sumdb — สำหรับ repository ภายใน/private
GONOSUMCHECK / GOFLAGS=-insecure—ทางออกฉุกเฉินแบบเก่า/edge-case; GOPRIVATE (หรือแคบกว่าคือ GONOSUMDB/GONOSUMCHECK) คือวิธีที่รองรับสำหรับ opt-out path หนึ่ง
Terminal window
export GOPRIVATE=github.com/yourcompany/*

การตั้ง GOPRIVATE สำหรับ org ของบริษัทคือวิธีแก้มาตรฐานของ “go get ค้างหรือ fail กับ internal repo ของเรา” — ถ้าไม่ตั้งไว้ public proxy และ sumdb จะถูกเรียกให้ตรวจ module path ที่มันไม่มีทางเห็นได้จริง ๆ

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 version ที่ดาวน์โหลดจะถูก extract ครั้งเดียวลง shared, read-only cache ที่ GOMODCACHE (default $GOPATH/pkg/mod) และถูกใช้ซ้ำโดยทุก module บนเครื่องที่ต้องการ version นั้นเป๊ะ ๆ — ยืนยันแล้วจริง directory นี้มีขนาด 1.8 GB หลังจากทำงานตามปกติในคอร์สนี้

Terminal window
go env GOMODCACHE
go clean -modcache # deletes the entire cache — next build re-downloads everything it needs

ให้ treat มันเป็น cache ล้วน ๆ: ลบได้อย่างปลอดภัยเมื่อ disk เต็ม และ Go จะเติมกลับให้เองจาก GOPROXY (หรือ direct) ครั้งถัดไปที่มีอะไรต้องการ module version ที่หายไป

Go บังคับใช้กฎที่เรียกว่า semantic import versioning: หาก module ออก v2 ขึ้นไป import path ต้องรวม major version suffix ด้วย

Terminal window
# v1 — no suffix needed
go get github.com/foo/[email protected]
# import "github.com/foo/bar"
# v2+ — /v2 suffix is part of the import path
go get github.com/foo/bar/[email protected]
# import "github.com/foo/bar/v2"

ซึ่งหมายความว่า major version สองเวอร์ชันของ module เดียวกันสามารถอยู่ร่วมกันใน binary เดียวได้โดยไม่ขัดแย้ง การเปลี่ยน import path เป็นเจตนา: v2 API อนุญาตให้ไม่เข้ากันกับ v1

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"]
minimal version selection เลือก version สูงสุดที่มีอะไรใน graph ขอจริง
สิ่งที่ได้ประโยชน์ต้นทุน
go.mod (explicit versioning)reproducible build, dependency ชัดเจนต้อง update เมื่อ dependency เปลี่ยน — go mod tidy บ่อยๆ
Minimal version selectionresolution deterministic reproducible — ไม่มี upgrade แปลกใจต้อง go get -u เองเสมอถึงจะเดินหน้า ไม่เกิดเองโดยปริยาย
go work สำหรับ local multi-module devไม่มีบรรทัด replace ให้ต้องจำไปลบก่อน publishไฟล์อีกไฟล์ที่ต้องดูแลให้อยู่นอก version control และ sync กับทีม
Tool directivetool 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 vendorair-gapped build ได้, ไม่ต้องพึ่ง networkvendor/ directory ใหญ่, ต้อง sync ทุก dependency update
  • go get เหมือน npm install — go get update go.mod และ download source ไม่ install binary แยก ต่างจาก npm มาก ใช้ go install pkg@version สำหรับกรณีนั้น
  • สามารถ ignore go.sum — go.sum ต้อง commit ร่วมกับ go.mod เพื่อ verify dependency integrity — ถ้าขาด build อาจไม่ reproducible
  • replace directive ห้ามใช้ใน 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.24 go get -tool และ tool directive ใน go.mod ทำแบบนี้ได้เองในตัว ไม่ต้องใช้ build-tag trick อีกแล้ว

💡 ตัวอย่างจากของจริง

Kubernetes มี go.mod ที่มี dependency หลักร้อยตัว — vendor/ ถูก commit เพื่อให้ build reproducible ในทุก environment

ความผิดพลาดเรื่อง module และ dependency แทบไม่แสดงเป็น compile error — มันแสดงเป็น “ที่เครื่องฉันมันทำงาน” หรือ security surprise เช็กเรื่องพวกนี้:

  • CLI developer tool (linter, code generator) ถูก install ด้วย go get ในขั้นตอน setup ของ application code แทนที่จะเป็น go install pkg@version หรือ tool directive หรือเปล่า? นั่นเพิ่มมันเป็น dependency จริงของ module คุณแบบเงียบ ๆ
  • replace directive ใน 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 ขึ้นไปแล้วและใช้ tool directive แทนได้?
  • go.work ถูก commit ลง version control ใน repository ของ library หรือเปล่า? มันมีไว้สำหรับ local multi-module development ส่วนตัว การ commit มันอาจ redirect build ของทีมไปที่ local path ที่ไม่มีอยู่จริงบนเครื่องพวกเขาแบบเงียบ ๆ

🐛 หา bug ในโค้ดนี้

Terminal window
# setup.sh — generated project bootstrap script
go get golang.org/x/tools/cmd/stringer@latest
go 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 ที่สร้างมาเพื่อกรณีนี้เป๊ะ ๆ

คำสั่งใด initialise Go module ใหม่ใน directory ปัจจุบัน?
minimal version selection ใส่ version ไหนของ github.com/foo/bar ใน build?
$ go mod graph
yourmodule github.com/foo/[email protected]
yourmodule github.com/baz/[email protected]
github.com/baz/[email protected] github.com/foo/[email protected]
library ที่คุณ publish มีสิ่งนี้ใน go.mod: จะเกิดอะไรขึ้นเมื่อคนอื่น import library ของคุณ?
replace github.com/foo/bar => ../local-fork-of-bar
go mod tidy ทำอะไร?
setup script ใช้บรรทัดนี้เพื่อ install tool stringer สำหรับ code generation ผิดตรงไหน และวิธีแก้แบบ Go 1.24+ คืออะไร?
go get golang.org/x/tools/cmd/stringer@latest
module ออก v2.0.0 พร้อม breaking changes import path ต้องเปลี่ยนอย่างไร?