Skip to content

Module

Mỗi ngôn ngữ hiện đại đều có một công cụ quản lý phụ thuộc trưởng thành của riêng mình ví dụ như Gradle của Java Pip của Python Npm của NodeJs v.v. một công cụ quản lý phụ thuộc tốt có thể tiết kiệm không ít thời gian cho nhà phát triển và có thể nâng cao hiệu quả phát triển. Tuy nhiên Go thời kỳ đầu không có một giải pháp quản lý phụ thuộc trưởng thành lúc đó tất cả mã đều được lưu trữ trong thư mục GOPATH đối với dự án công trình rất không thân thiện phiên bản hỗn loạn phụ thuộc khó quản lý để giải quyết vấn đề này các nhà phát triển cộng đồng lớn tranh giành tình hình nhất thời hỗn loạn trong đó cũng không thiếu xuất hiện một số người dẫn đầu như Vendor cho đến Go1.11 chính thức cuối cùng đã ra mắt công cụ quản lý phụ thuộc chính thức Go Mod kết thúc tình hình hỗn loạn trước đó và không ngừng hoàn thiện trong các cập nhật sau này loại bỏ các công cụ cũ trước đây. Cho đến ngày nay khi viết bài này phiên bản phát hành Go đã đến 1.20 ngày nay hầu như tất cả dự án Go đều đang áp dụng Go Mod nên trong bài viết này cũng chỉ giới thiệu Go Mod Go chính thức cũng đã viết tài liệu rất chi tiết về Go module Go Modules Reference.

Viết module

Go Module về bản chất dựa trên VCS (hệ thống kiểm soát phiên bản) khi bạn tải xuống phụ thuộc thực tế là thực hiện lệnh VCS ví dụ như git nên nếu bạn muốn chia sẻ thư viện bạn viết chỉ cần đạt ba điểm sau

  • Kho mã nguồn có thể truy cập công khai và VCS thuộc một trong những loại sau
    • git
    • hg (Mercurial)
    • bzr (Bazaar)
    • svn
    • fossil
  • Là một dự án go mod tuân thủ quy phạm
  • Tuân thủ quy phạm phiên bản ngữ nghĩa

Vì vậy bạn chỉ cần bình thường phát triển VCS và đánh tag cho phiên bản cụ thể của bạn tuân thủ tiêu chuẩn người khác có thể tải xuống thư viện bạn viết thông qua tên module dưới đây sẽ thông qua ví dụ để demo vài bước phát triển module.

Kho ví dụ 246859/hello: say hello (github.com)

Chuẩn bị

Trước khi bắt đầu đảm bảo phiên bản của bạn đủ để hoàn toàn hỗ trợ go mod (go >= 1.17) và đã bật Go Module thông qua lệnh sau để xem có bật không

bash
$ go env GO111MODULE

Nếu chưa bật thông qua lệnh sau để bật Go Module

bash
$ go env -w GO111MODULE=on

Tạo

Trước tiên bạn cần một kho mã nguồn có thể truy cập công khai có nhiều lựa chọn cho việc này tôi khá khuyến nghị Github. Tạo một dự án mới ở trên đặt tên là hello tên kho tuy không có hạn chế đặc biệt nhưng khuyến nghị vẫn không nên sử dụng ký tự đặc biệt vì điều này sẽ ảnh hưởng đến tên module.

Sau khi tạo xong có thể thấy URL của kho là https://github.com/246859/hello tên module go tương ứng chính là github.com/246859/hello.

Sau đó clone nó về local thông qua lệnh go mod init để khởi tạo module.

bash
$ git clone git@github.com:246859/hello.git
Cloning into 'hello'...
remote: Enumerating objects: 5, done.
remote: Counting objects: 100% (5/5), done.
remote: Compressing objects: 100% (4/4), done.
remote: Total 5 (delta 0), reused 0 (delta 0), pack-reused 0
Receiving objects: 100% (5/5), done.

$ cd hello && go mod init github.com/246859/hello
go: creating new go.mod: module github.com/246859/hello

Viết

Sau đó có thể tiến hành công việc phát triển chức năng của nó rất đơn giản chỉ có một hàm

go
// hello.go
package hello

import "fmt"

// Hello returns hello message
func Hello(name string) string {
        if name == "" {
                name = "world"
        }
        return fmt.Sprintf("hello %s!", name)
}

Tiện thể viết một file kiểm tra để kiểm tra đơn vị

go
// hello_test.go
package hello_test

import (
        "testing"
        "fmt"
        "github.com/246859/hello"
)

func TestHello(t *testing.T) {
        data := "jack"
        expected := fmt.Sprintf("hello %s!", data)
        result := hello.Hello(data)

        if result != expected {
                t.Fatalf("expected result %s, but got %s", expected, result)
        }

}

Tiếp theo viết một chương trình dòng lệnh để xuất hello chức năng của nó cũng rất đơn giản. Đối với chương trình dòng lệnh mà nói theo quy phạm là tạo trong cmd/app_name/ của dự án nên file của chương trình dòng lệnh hello được lưu trữ trong thư mục cmd/hello/ sau đó viết mã liên quan trong đó.

go
// cmd/hello/main.go
package main

import (
  "flag"
  "github.com/246859/hello"
  "os"
)

var name string

func init() {
  flag.StringVar(&name, "name", "world", "name to say hello")
}

func main() {
  flag.Parse()
  msg := hello.Hello(name)
  _, err := os.Stdout.WriteString(msg)
  if err != nil {
    os.Stderr.WriteString(err.Error())
  }
}

Kiểm tra

Sau khi viết xong định dạng mã nguồn và kiểm tra

bash
$ go fmt && go vet ./...

$ go test -v .
=== RUN   TestHello
--- PASS: TestHello (0.00s)
PASS
ok      github.com/246859/hello 0.023s

Chạy chương trình dòng lệnh

bash
$ go run ./cmd/hello -name jack
hello jack!

Tài liệu

Cuối cùng cần viết README ngắn gọn rõ ràng cho thư viện này để các nhà phát triển khác nhìn một cái là biết cách sử dụng

markdown
# hello

just say hello

## Install

import code

```bash
go get github.com/246859/hello@latest
```

install cmd

```bash
go install github.com/246859/hello/cmd/hello@latest
```

## Example

Here's a simple example as follows:

```go
package main

import (
  "fmt"
  "github.com/246859/hello"
)

func main() {
  result := hello.Hello("jack")
  fmt.Println(result)
}
```

Đây là một tài liệu README rất đơn giản bạn cũng có thể tự làm phong phú thêm.

Upload

Khi tất cả mã đã viết và kiểm tra xong có thể submit và đẩy sửa đổi vào kho từ xa.

bash
$ git add go.mod hello.go hello_test.go cmd/ example/ README.md

$ git commit -m "chore(mod): mod init" go.mod
[main 5087fa2] chore(mod): mod init
 1 file changed, 3 insertions(+)
 create mode 100644 go.mod

$ git commit -m "feat(hello): complete Hello func" hello.go
[main 099a8bf] feat(hello): complete Hello func
 1 file changed, 11 insertions(+)
 create mode 100644 hello.go

$ git commit -m "test(hello): complete hello testcase" hello_test.go
[main 76e8c1e] test(hello): complete hello testcase
 1 file changed, 17 insertions(+)
 create mode 100644 hello_test.go

$ git commit -m "feat(hello): complete hello cmd" cmd/hello/
[main a62a605] feat(hello): complete hello cmd
 1 file changed, 22 insertions(+)
 create mode 100644 cmd/hello/main.go

$ git commit -m "docs(example): add hello example" example/
[main 5c51ce4] docs(example): add hello example
 1 file changed, 11 insertions(+)
 create mode 100644 example/main.go

$ git commit -m "docs(README): update README" README.md
[main e6fbc62] docs(README): update README
 1 file changed, 27 insertions(+), 1 deletion(-)

Tổng cộng sáu commit không nhiều sau khi submit xong tạo một tag cho commit mới nhất

bash
$ git tag v1.0.0

$ git tag -l
v1.0.0

$ git log --oneline
e6fbc62 (HEAD -> main, tag: v1.0.0, origin/main, origin/HEAD) docs(README): update README
5c51ce4 docs(example): add hello example
a62a605 feat(hello): complete hello cmd
76e8c1e test(hello): complete hello testcase
099a8bf feat(hello): complete Hello func
5087fa2 chore(mod): mod init
1f422d1 Initial commit

Cuối cùng đẩy vào kho từ xa

bash
$ git push --tags
Enumerating objects: 23, done.
Counting objects: 100% (23/23), done.
Delta compression using up to 16 threads
Compressing objects: 100% (17/17), done.
Writing objects: 100% (21/21), 2.43 KiB | 1.22 MiB/s, done.
Total 21 (delta 5), reused 0 (delta 0), pack-reused 0
remote: Resolving deltas: 100% (5/5), done.
To github.com:246859/hello.git
   1f422d1..e6fbc62    main -> main
  * [new tag]         v1.0.0 -> v1.0.0

Sau khi đẩy xong lại tạo một release cho nó (có một tag là đủ release chỉ để phù hợp với quy phạm github)

Như vậy việc viết module đã hoàn thành trên đây là một quy trình cơ bản của phát triển module các nhà phát triển khác có thể import mã hoặc cài đặt công cụ dòng lệnh thông qua tên module.

Import

Thông qua go get để import thư viện

bash
$ go get github.com/246859/hello@latest
go: downloading github.com/246859/hello v1.0.0
go: added github.com/246859/hello v1.0.0

Thông qua go install để cài đặt chương trình dòng lệnh

bash
$ go install github.com/246859/hello/cmd/hello@latest && hello -name jack
hello jack!

Hoặc sử dụng go run để chạy trực tiếp

bash
$ go run -mod=mod github.com/246859/hello/cmd/hello -name jack
hello jack!

Khi một thư viện được import Go Package sẽ tạo một trang cho nó quá trình này được hoàn thành tự động không cần nhà phát triển làm gì ví dụ thư viện hello có một trang tài liệu chuyên dụng như hình dưới.

Về thông tin chi tiết hơn của việc upload module đến Add a package.

Về cách xóa thông tin module đến Removing a package.

Đặt proxy

Go tuy không có kho trung tâm giống như Maven Repo PyPi NPM nhưng có một kho proxy chính thức Go modules services (golang.org) nó sẽ đệm các module mà nhà phát triển đã tải xuống theo phiên bản và tên module. Tuy nhiên do máy chủ của nó được triển khai ở nước ngoài tốc độ truy cập đối với người dùng trong nước không thân thiện cho lắm nên chúng ta cần sửa đổi địa chỉ proxy module mặc định hiện tại trong nước có mấy nhà làm khá tốt

Ở đây chọn proxy của Qiniu Cloud thực hiện lệnh sau để sửa đổi proxy Go trong đó direct biểu thị sau khi proxy tải xuống thất bại sẽ bỏ qua đệm proxy trực tiếp truy cập kho mã nguồn.

sh
$ go env -w GOPROXY=https://goproxy.cn,direct

Sau khi sửa đổi proxy thành công việc tải xuống phụ thuộc trong tương lai sẽ rất nhanh chóng.

Tải xuống phụ thuộc

Sau khi sửa đổi proxy tiếp theo thử cài đặt một phụ thuộc bên thứ ba Go chính thức có trang web truy vấn phụ thuộc chuyên dụng Go Packages.

Import mã

Tìm kiếm framework Web nổi tiếng Gin trong đó.

Ở đây sẽ xuất hiện nhiều kết quả tìm kiếm khi sử dụng phụ thuộc bên thứ ba cần kết hợp số lần import và thời gian cập nhật để quyết định có áp dụng phụ thuộc đó không ở đây trực tiếp chọn cái đầu tiên

Sau khi vào trang tương ứng có thể thấy đây là một trang tài liệu của phụ thuộc đó có rất nhiều thông tin chi tiết về nó sau này khi tra cứu tài liệu cũng có thể đến đây.

Ở đây chỉ cần copy địa chỉ của nó sau đó trong dự án đã tạo trước đó sử dụng lệnh go get lệnh như sau

sh
$ go get github.com/gin-gonic/gin

Trong quá trình sẽ tải xuống rất nhiều phụ thuộc chỉ cần không báo lỗi là tải xuống thành công.

sh
$ go get github.com/gin-gonic/gin
go: added github.com/bytedance/sonic v1.8.0
go: added github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311
go: added github.com/gin-contrib/sse v0.1.0
go: added github.com/gin-gonic/gin v1.9.0
go: added github.com/go-playground/locales v0.14.1
go: added github.com/go-playground/universal-translator v0.18.1
go: added github.com/go-playground/validator/v10 v10.11.2
go: added github.com/goccy/go-json v0.10.0
go: added github.com/json-iterator/go v1.1.12
go: added github.com/klauspost/cpuid/v2 v2.0.9
go: added github.com/leodido/go-urn v1.2.1
go: added github.com/mattn/go-isatty v0.0.17
go: added github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421
go: added github.com/modern-go/reflect2 v1.0.2
go: added github.com/pelletier/go-toml/v2 v2.0.6
go: added github.com/twitchyliquid64/golang-asm v0.15.1
go: added github.com/ugorji/go/codec v1.2.9
go: added golang.org/x/arch v0.0.0-20210923205945-b76863e36670
go: added golang.org/x/crypto v0.5.0
go: added golang.org/x/net v0.7.0
go: added golang.org/x/sys v0.5.0
go: added golang.org/x/text v0.7.0
go: added google.golang.org/protobuf v1.28.1
go: added gopkg.in/yaml.v3 v3.0.1

Sau khi hoàn thành xem file go.mod

sh
$ cat go.mod
module golearn

go 1.20

require github.com/gin-gonic/gin v1.9.0

require (
  github.com/bytedance/sonic v1.8.0 // indirect
  github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
  github.com/gin-contrib/sse v0.1.0 // indirect
  github.com/go-playground/locales v0.14.1 // indirect
  github.com/go-playground/universal-translator v0.18.1 // indirect
  github.com/go-playground/validator/v10 v10.11.2 // indirect
  github.com/goccy/go-json v0.10.0 // indirect
  github.com/json-iterator/go v1.1.12 // indirect
  github.com/klauspost/cpuid/v2 v2.0.9 // indirect
  github.com/leodido/go-urn v1.2.1 // indirect
  github.com/mattn/go-isatty v0.0.17 // indirect
  github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect
  github.com/modern-go/reflect2 v1.0.2 // indirect
  github.com/pelletier/go-toml/v2 v2.0.6 // indirect
  github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
  github.com/ugorji/go/codec v1.2.9 // indirect
  golang.org/x/arch v0.0.0-20210923205945-b76863e36670 // indirect
  golang.org/x/crypto v0.5.0 // indirect
  golang.org/x/net v0.7.0 // indirect
  golang.org/x/sys v0.5.0 // indirect
  golang.org/x/text v0.7.0 // indirect
  google.golang.org/protobuf v1.28.1 // indirect
  gopkg.in/yaml.v3 v3.0.1 // indirect
)

Có thể thấy so với trước đã thêm rất nhiều thứ đồng thời cũng sẽ phát hiện trong thư mục có thêm một file tên là go.sum

sh
$ ls
go.mod  go.sum  main.go

Ở đây tạm thời không bàn sửa đổi file main.go thành mã sau

go
package main

import (
  "github.com/gin-gonic/gin"
)

func main() {
  gin.Default().Run()
}

Chạy lại dự án

sh
$ go run golearn
[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] [WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.
Please check https://pkg.go.dev/github.com/gin-gonic/gin#readme-don-t-trust-all-proxies for details.
[GIN-debug] Environment variable PORT is undefined. Using port :8080 by default
[GIN-debug] Listening and serving HTTP on :8080

Như vậy thông qua một dòng mã đã chạy một máy chủ Web đơn giản nhất. Khi không còn cần một phụ thuộc nào đó cũng có thể sử dụng lệnh go get để xóa phụ thuộc đó ở đây lấy việc xóa Gin làm ví dụ

sh
$ go get github.com/gin-gonic/gin@none
go: removed github.com/gin-gonic/gin v1.9.0

Thêm @none phía sau địa chỉ phụ thuộc là có thể xóa phụ thuộc đó kết quả cũng thông báo xóa thành công lúc này xem lại file go.mod sẽ thấy không còn phụ thuộc Gin.

sh
$ cat go.mod | grep github.com/gin-gonic/gin

Khi cần nâng cấp phiên bản mới nhất có thể thêm hậu tố @latest hoặc có thể tự truy vấn số phiên bản Release khả dụng

sh
$ go get -u github.com/gin-gonic/gin@latest

Cài đặt dòng lệnh

Lệnh go install sẽ tải xuống phụ thuộc bên thứ ba vào local và biên dịch thành file nhị phân nhờ vào tốc độ biên dịch của Go quá trình này thường không tốn quá nhiều thời gian sau đó go sẽ lưu trữ nó trong thư mục $GOPATH/bin hoặc $GOBIN để có thể thực thi file nhị phân đó trên toàn cục (với điều kiện bạn đã thêm các đường dẫn này vào biến môi trường).

TIP

Khi sử dụng lệnh install phải chỉ định số phiên bản.

Ví dụ tải xuống debugger delve được viết bằng ngôn ngữ Go

bash
$ go install github.com/go-delve/delve/cmd/dlv@latest
go: downloading github.com/go-delve/delve v1.22.1
go: downloading github.com/cosiner/argv v0.1.0
go: downloading github.com/derekparker/trie v0.0.0-20230829180723-39f4de51ef7d
go: downloading github.com/go-delve/liner v1.2.3-0.20231231155935-4726ab1d7f62
go: downloading github.com/google/go-dap v0.11.0
go: downloading github.com/hashicorp/golang-lru v1.0.2
go: downloading golang.org/x/arch v0.6.0
go: downloading github.com/cpuguy83/go-md2man/v2 v2.0.2
go: downloading go.starlark.net v0.0.0-20231101134539-556fd59b42f6
go: downloading github.com/cilium/ebpf v0.11.0
go: downloading github.com/mattn/go-runewidth v0.0.13
go: downloading github.com/russross/blackfriday/v2 v2.1.0
go: downloading github.com/rivo/uniseg v0.2.0
go: downloading golang.org/x/exp v0.0.0-20230224173230-c95f2b4c22f2

$ dlv -v
Error: unknown shorthand flag: 'v' in -v
Usage:
  dlv [command]

Available Commands:
  attach      Attach to running process and begin debugging.
  completion  Generate the autocompletion script for the specified shell
  connect     Connect to a headless debug server with a terminal client.
  core        Examine a core dump.
  dap         Starts a headless TCP server communicating via Debug Adaptor Protocol (DAP).
  debug       Compile and begin debugging main package in current directory, or the package specified.
  exec        Execute a precompiled binary, and begin a debug session.
  help        Help about any command
  test        Compile test binary and begin debugging program.
  trace       Compile and begin tracing program.
  version     Prints version.

Additional help topics:
  dlv backend    Help about the --backend flag.
  dlv log        Help about logging flags.
  dlv redirect   Help about file redirection.

Use "dlv [command] --help" for more information about a command.

Quản lý module

Tất cả nội dung trên chỉ đang讲述 việc sử dụng cơ bản của Go Mod nhưng thực tế để học thành thạo Go Mod chỉ có những thứ này là hoàn toàn không đủ. Định nghĩa chính thức về module là một tập hợp các gói được đánh dấu phiên bản. Trong định nghĩa trên gói nên là khái niệm rất quen thuộc còn phiên bản thì phải tuân thủ số phiên bản ngữ nghĩa định nghĩa là định dạng v(major).(minor).(patch) ví dụ như phiên bản của Go v1.20.1 phiên bản chính là 1 phiên bản nhỏ là 20 phiên bản vá là 1 kết hợp lại chính là v1.20.1 dưới đây là giải thích chi tiết hơn

  • major: Khi phiên bản major thay đổi说明 dự án đã xảy ra thay đổi không tương thích dự án phiên bản cũ nâng cấp lên phiên bản mới phần lớn không thể chạy bình thường.
  • minor: Khi phiên bản minor thay đổi说明 dự án đã thêm tính năng mới chỉ là dựa trên phiên bản trước đó chỉ thêm tính năng mới.
  • patch: Khi phiên bản patch thay đổi说明 chỉ có bug được sửa không thêm bất kỳ tính năng mới nào.

Lệnh thông dụng

LệnhGiải thích
go mod downloadTải xuống các gói phụ thuộc của dự án hiện tại
go mod editChỉnh sửa file go.mod
go mod graphXuất biểu đồ phụ thuộc module
go mod initKhởi tạo go mod trong thư mục hiện tại
go mod tidyDọn dẹp module dự án
go mod verifyXác minh tính hợp lệ của phụ thuộc dự án
go mod whyGiải thích những nơi nào trong dự án sử dụng phụ thuộc
go clean -modcacheDùng để xóa đệm phụ thuộc module dự án
go list -mLiệt kê module

Đến go mod cmd để biết thêm thông tin liên quan về lệnh

Lưu trữ module

Khi sử dụng Go Mod để quản lý dự án đệm module mặc định được lưu trữ trong thư mục $GOPATH/pkg/mod cũng có thể sửa đổi $GOMODCACHE để chỉ định lưu trữ ở vị trí khác.

sh
$ go env -w GOMODCACHE=đường dẫn đệm module của bạn

Tất cả dự án Go Module trên cùng một máy chia sẻ đệm trong thư mục này đệm không có giới hạn kích thước và không tự động xóa các file nguồn phụ thuộc đã giải nén trong đệm đều là chỉ đọc muốn xóa sạch đệm có thể thực hiện lệnh sau.

sh
$ go clean -modcache

Trong thư mục $GOMODCACHE/cache/download lưu trữ các file gốc của phụ thuộc bao gồm file hash file nén gốc v.v. ví dụ sau

bash
$ ls $(go env GOMODCACHE)/cache/download/github.com/246859/hello/@v -1
list
v1.0.0.info
v1.0.0.lock
v1.0.0.mod
v1.0.0.zip
v1.0.0.ziphash

Các phụ thuộc đã giải nén được tổ chức dưới đây chính là mã nguồn của module chỉ định.

bash
$ ls $(go env GOMODCACHE)/github.com/246859/hello@v1.0.0 -1
LICENSE
README.md
cmd/
example/
go.mod
hello.go
hello_test.go

Chọn phiên bản

Go khi chọn phiên bản phụ thuộc tuân theo nguyên tắc chọn phiên bản tối thiểu. Dưới đây là một ví dụ từ trang web chính module chính import phiên bản 1.2 của module A và phiên bản 1.2 của module B đồng thời phiên bản 1.2 của module A import phiên bản 1.3 của module C phiên bản 1.2 của module B import phiên bản 1.4 của module C và phiên bản 1.3 và 1.4 của module C đều đồng thời import phiên bản 1.2 của module D theo nguyên tắc phiên bản khả dụng tối thiểu Go cuối cùng sẽ chọn phiên bản là A1.2 B1.2 C1.4 và D1.2. Trong đó màu xanh nhạt biểu thị file go.mod được load khung chọn biểu thị phiên bản được chọn cuối cùng.

Trang web chính thức cũng đưa ra một vài ví dụ khác đại khái ý nghĩa đều tương tự.

go.mod

Mỗi khi tạo một dự án Go Mod đều sẽ sinh ra một file go.mod nên việc quen thuộc với file go.mod là rất cần thiết nhưng trong hầu hết các trường hợp không cần thủ công sửa đổi file go.mod.

module golearn

go 1.20

require github.com/gin-gonic/gin v1.9.0

require (
   github.com/bytedance/sonic v1.8.0 // indirect
   github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
   github.com/gin-contrib/sse v0.1.0 // indirect
   github.com/go-playground/locales v0.14.1 // indirect
   github.com/go-playground/universal-translator v0.18.1 // indirect
   github.com/go-playground/validator/v10 v10.11.2 // indirect
   github.com/goccy/go-json v0.10.0 // indirect
   github.com/json-iterator/go v1.1.12 // indirect
   github.com/klauspost/cpuid/v2 v2.0.9 // indirect
   github.com/leodido/go-urn v1.2.1 // indirect
   github.com/mattn/go-isatty v0.0.17 // indirect
   github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect
   github.com/modern-go/reflect2 v1.0.2 // indirect
   github.com/pelletier/go-toml/v2 v2.0.6 // indirect
   github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
   github.com/ugorji/go/codec v1.2.9 // indirect
   golang.org/x/arch v0.0.0-20210923205945-b76863e36670 // indirect
   golang.org/x/crypto v0.5.0 // indirect
   golang.org/x/net v0.7.0 // indirect
   golang.org/x/sys v0.5.0 // indirect
   golang.org/x/text v0.7.0 // indirect
   google.golang.org/protobuf v1.28.1 // indirect
   gopkg.in/yaml.v3 v3.0.1 // indirect
)

Trong file có thể phát hiện hầu hết các địa chỉ phụ thuộc đều mang những chữ như github v.v. điều này là vì Go không có một kho phụ thuộc công cộng nào phần lớn dự án mã nguồn mở đều được host trên Gitub cũng có một phần là tự xây dựng kho ví dụ như google.golang.org/protobuf golang.org/x/crypto. Thông thường chuỗi网址 này đồng thời cũng là tên module của dự án Go điều này sẽ xuất hiện một vấn đề URL không phân biệt chữ hoa chữ thường nhưng thư mục lưu trữ phụ thuộc lại phân biệt chữ hoa chữ thường nên go get github.com/gin-gonic/gingo get github.com/gin-gonic/Gin hai cái import cùng một phụ thuộc nhưng đường dẫn lưu trữ local khác nhau. Khi xảy ra tình huống này Go không trực tiếp lấy chữ hoa làm đường dẫn lưu trữ mà sẽ chuyển nó thành !chữ thường ví dụ github.com\BurntSushi cuối cùng sẽ chuyển thành github.com\!burnt!sushi.

module

Từ khóa module khai báo tên module của dự án hiện tại trong một file go.mod chỉ có thể xuất hiện một từ khóa module. Ví dụ trong

module golearn

Biểu thị tên module hiện tại là golearn ví dụ mở file go.mod của phụ thuộc Gin có thể phát hiện tên module của nó

module github.com/gin-gonic/gin

Tên module của Gin chính là địa chỉ sử dụng khi tải xuống phụ thuộc đây cũng là định dạng tên module được khuyến nghị thông thường tên miền/người dùng/tên kho.

TIP

Có một điểm cần lưu ý là khi phiên bản chính lớn hơn 1 số phiên bản chính phải được thể hiện trong tên module ví dụ

github.com/my/example

Nếu phiên bản nâng cấp lên v2.0.0 thì tên module cần sửa đổi thành như sau

github.com/my/example/v2

Nếu dự án gốc đã import phiên bản cũ và phiên bản mới không phân biệt khi import phụ thuộc do đường dẫn đều nhất quán nên người sử dụng không thể phân biệt được những thay đổi không tương thích do phiên bản chính thay đổi gây ra như vậy có thể gây ra lỗi chương trình.

Deprecation

Ở dòng đầu tiên phía trên module chú thích Deprecated để biểu thị module đó đã bị loại bỏ ví dụ

// Deprecated: use example.com/mod/v2 instead.
module example.com/mod

go

Từ khóa go biểu thị phiên bản Go sử dụng để viết dự án hiện tại số phiên bản phải tuân thủ quy tắc phiên bản ngữ nghĩa tùy theo phiên bản go khác nhau Go Mod sẽ biểu hiện hành vi khác nhau dưới đây là một ví dụ đơn giản về số phiên bản Go khả dụng tự đến chính thức để tra cứu.

go 1.20

require

Từ khóa require biểu thị import một phụ thuộc bên ngoài ví dụ

require github.com/gin-gonic/gin v1.9.0

Định dạng là require tên module số phiên bản khi có nhiều import có thể dùng ngoặc đơn括 lại

require (
   github.com/bytedance/sonic v1.8.0 // indirect
)

Những phụ thuộc có chú thích // indirect biểu thị phụ thuộc đó không được dự án hiện tại import trực tiếp có thể là phụ thuộc đó được import bởi phụ thuộc mà dự án trực tiếp import nên đối với dự án hiện tại mà nói là import gián tiếp. Trước đó đã đề cập khi phiên bản chính thay đổi phải thể hiện trên tên module nếu không tuân thủ quy tắc này thì module được gọi là module không chuẩn khi require sẽ thêm chú thích incompatible.

require example.com/m v4.1.2+incompatible

Phiên bản giả

Trong file go.mod ở trên có thể phát hiện có một số phiên bản của phụ thuộc không phải là số phiên bản ngữ nghĩa mà là một chuỗi string không biết gì đây thực tế là CommitID của phiên bản tương ứng phiên bản ngữ nghĩa thường chỉ một Release nào đó. Số phiên bản giả thì có thể tinh chỉnh đến chỉ định một Commit nào đó định dạng thông thường là vx.y.z-yyyyMMddHHmmss-CommitId do vx.y.z của nó không nhất định thực sự tồn tại nên gọi là phiên bản giả ví dụ v0.0.0 trong ví dụ dưới không tồn tại thực sự có hiệu lực là 12 vị CommitID sau đó.

// CommitID thường lấy 12 vị đầu
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect

Tương tự khi tải xuống phụ thuộc cũng có thể chỉ định CommitID thay thế số phiên bản ngữ nghĩa

go get github.com/chenzhuoyu/base64x@fe3a3abad311

exclude

Từ khóa exclude biểu thị không load phiên bản phụ thuộc chỉ định nếu đồng thời có require import cùng phiên bản phụ thuộc cũng sẽ bị bỏ qua. Từ khóa này chỉ có hiệu lực trong module chính. Ví dụ

exclude golang.org/x/net v1.2.3

exclude (
    golang.org/x/crypto v1.4.5
    golang.org/x/text v1.6.7
)

replace

replace sẽ thay thế phụ thuộc chỉ định phiên bản có thể sử dụng đường dẫn module và phiên bản để thay thế hoặc đường dẫn file chỉ định của nền tảng khác ví dụ

text
replace golang.org/x/net v1.2.3 => example.com/fork/net v1.4.5

replace (
    golang.org/x/net v1.2.3 => example.com/fork/net v1.4.5
    golang.org/x/net => example.com/fork/net v1.4.5
    golang.org/x/net v1.2.3 => ./fork/net
    golang.org/x/net => ./fork/net
)

Chỉ phiên bản bên trái => được thay thế các phiên bản khác của cùng một phụ thuộc vẫn có thể truy cập bình thường cho dù sử dụng đường dẫn local hay đường dẫn module chỉ định thay thế nếu module thay thế có file go.mod thì chỉ thị module của nó phải khớp với đường dẫn module được thay thế.

retract

Chỉ thị retract biểu thị không nên phụ thuộc vào phiên bản hoặc phạm vi phiên bản của phụ thuộc được retract chỉ định. Ví dụ như sau khi phát hành một phiên bản mới phát hiện một vấn đề nghiêm trọng lúc này có thể sử dụng chỉ thị retract.

Rút lại một số phiên bản

text
retract (
    v1.0.0 // Published accidentally.
    v1.0.1 // Contains retractions only.
)

Rút lại phạm vi phiên bản

text
retract v1.0.0
retract [v1.0.0, v1.9.9]
retract (
    v1.0.0
    [v1.0.0, v1.9.9]
)

go.sum

File go.sum khi mới tạo dự án sẽ không tồn tại chỉ sau khi thực sự import phụ thuộc bên ngoài mới sinh ra file đó file go.sum không phù hợp để con người đọc cũng không khuyến nghị thủ công sửa đổi file đó. Tác dụng của nó chủ yếu là giải quyết vấn đề build nhất quán tức là những người khác nhau trong các môi trường khác nhau sử dụng cùng một dự án build các gói phụ thuộc được import phải hoàn toàn giống nhau chỉ dựa vào một file go.mod là không thể đảm bảo được.

Tiếp theo xem khi tải xuống một phụ thuộc Go từ đầu đến cuối đã làm những gì trước tiên sử dụng lệnh sau để tải xuống một phụ thuộc

go get github.com/bytedance/sonic v1.8.0

Lệnh go get trước tiên sẽ tải gói phụ thuộc xuống thư mục đệm local thông thường thư mục này là $GOMODCACHE/cache/download/ thư mục này được phân chia theo tên miền để phân biệt các phụ thuộc của các trang web khác nhau nên bạn có thể thấy cấu trúc thư mục như sau

sh
$ ls
cloud.google.com/      go.opencensus.io/     gopkg.in/          nhooyr.io/
dmitri.shuralyov.com/  go.opentelemetry.io/  gorm.io/           rsc.io/
github.com/            go.uber.org/          honnef.co/         sumdb/
go.etcd.io/            golang.org/           lukechampine.com/
go.mongodb.org/        google.golang.org/    modernc.org/

Vậy đường dẫn lưu trữ của phụ thuộc tải xuống trong ví dụ trên nằm ở

$GOMODCACHE/cache/download/github.com/bytedance/sonic/@v/

Cấu trúc thư mục có thể như sau sẽ có mấy file được đặt tên theo phiên bản

sh
$ ls
list         v1.8.0.lock  v1.8.0.ziphash  v1.8.3.mod
v1.5.0.mod   v1.8.0.mod   v1.8.3.info     v1.8.3.zip
v1.8.0.info  v1.8.0.zip   v1.8.3.lock     v1.8.3.ziphash

Thông thường trong thư mục này nhất định có một file list dùng để ghi lại số phiên bản đã biết của phụ thuộc đó còn đối với mỗi phiên bản mà nói đều sẽ có các file sau

  • zip: File nén mã nguồn của phụ thuộc
  • ziphash: Giá trị hash được tính toán dựa trên file nén của phụ thuộc
  • info: Siêu dữ liệu phiên bản định dạng json
  • mod: File go.mod của phiên bản đó
  • lock: File tạm thời chính thức cũng không nói dùng để làm gì

Thông thường Go sẽ tính giá trị hash của hai file file nén và go.mod sau đó truy vấn giá trị hash của phụ thuộc đó theo máy chủ do GOSUMDB chỉ định (mặc định là sum.golang.org) nếu giá trị hash tính toán local không khớp với kết quả truy vấn được thì sẽ không tiếp tục thực hiện nữa. Nếu khớp sẽ cập nhật file go.mod và chèn hai bản ghi vào file go.sum đại khái như sau

github.com/bytedance/sonic v1.8.0 h1:ea0Xadu+sHlu7x5O3gKhRpQ1IKiMrSiHttPF0ybECuA=
github.com/bytedance/sonic v1.8.0/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U=

TIP

Giả sử vô hiệu hóa GOSUMDB Go sẽ trực tiếp ghi giá trị hash tính toán local vào file go.sum thông thường không khuyến nghị làm như vậy.

Thông thường mỗi phụ thuộc sẽ có hai bản ghi cái đầu tiên là giá trị hash của file nén cái thứ hai là giá trị hash của file go.mod của phụ thuộc định dạng bản ghi là tên module số phiên bản tên thuật toán:giá trị hash một số phụ thuộc khá cổ có thể không có file go.mod nên sẽ không có bản ghi hash thứ hai. Khi dự án này được build trong môi trường của người khác Go sẽ tính giá trị hash của phụ thuộc local được chỉ định trong go.mod sau đó so sánh với giá trị hash được ghi trong go.sum nếu giá trị hash không khớp说明 phiên bản phụ thuộc khác nhau sẽ từ chối build. Khi xảy ra tình huống này phụ thuộc local và file go.sum đều có thể đã bị sửa đổi nhưng do file go.sum là bản ghi truy vấn GOSUMDB nên sẽ có xu hướng tin tưởng file go.sum hơn.

Module riêng tư

Hầu hết các công cụ của Go Mod đều dành cho dự án mã nguồn mở nhưng Go cũng hỗ trợ module riêng tư. Đối với dự án riêng tư mà nói thông thường cần cấu hình mấy biến môi trường sau để xử lý module riêng tư

  • GOPROXY: Tập hợp máy chủ proxy của phụ thuộc
  • GOPRIVATE: Danh sách các mẫu chung của tiền tố đường dẫn module của module riêng tư nếu tên module phù hợp với quy tắc biểu thị module đó là module riêng tư hành vi cụ thể giống với GONOPROXY và GONOSUMDB.
  • GONOPROXY: Danh sách các mẫu chung của tiền tố đường dẫn module của các module không tải xuống từ proxy nếu phù hợp với quy tắc khi tải xuống module sẽ không đi qua GOPROXY thử trực tiếp tải xuống từ hệ thống kiểm soát phiên bản.
  • GONOSUMDB: Danh sách các mẫu chung của tiền tố đường dẫn module của các module không xác minh GOSUMDB công cộng nếu phù hợp với quy tắc khi tải xuống module sẽ không đi qua cơ sở dữ liệu công cộng checksum.
  • GOINSECURE: Danh sách các mẫu chung của tiền tố đường dẫn module của các module có thể truy vấn thông qua HTTP và các giao thức không an toàn khác.

Workspace

Trước đó đã đề cập file go.mod hỗ trợ chỉ thị replace điều này cho phép chúng ta tạm thời sử dụng một số sửa đổi không kịp phát hành của local như sau

replace (
  github.com/246859/hello v1.0.1 => ./hello
)

Khi biên dịch go sẽ sử dụng module hello của local sau khi phát hành phiên bản mới trong tương lai rồi xóa nó đi.

Nhưng nếu sử dụng chỉ thị replace sẽ sửa đổi nội dung của file go.mod và sửa đổi đó có thể bị submit nhầm vào kho từ xa điều này là điều chúng ta không hy vọng vì target được chỉ thị replace chỉ định là một đường dẫn file chứ không phải URL mạng đường dẫn có thể dùng được trên máy này có thể đến máy khác lại không dùng được đường dẫn file cũng sẽ là một vấn đề lớn về mặt cross-platform. Để giải quyết loại vấn đề này workspace đã ra đời.

Workspace là một giải pháp mới về quản lý nhiều module được Go giới thiệu trong phiên bản 1.18 nhằm thực hiện tốt hơn công việc phát triển nhiều module local dưới đây sẽ thông qua một ví dụ để giải thích.

Kho ví dụ 246859/work: go work example (github.com)

Ví dụ

Trước tiên trong dự án có hai module go độc lập lần lượt là auth user

bash
$ ls -1
LICENSE
README.md
auth
go.work
user

Module auth phụ thuộc vào struct User của module user nội dung như sau

go
package auth

import (
  "errors"
  "github.com/246859/work/user"
)

// Verify user credentials if is ok
func Verify(user user.User) (bool, error) {
  password, err := query(user.Name)
  if err != nil {
    return false, err
  }
  if password != user.Password {
    return false, errors.New("authentication failed")
  }
  return true, nil
}

func query(username string) (string, error) {
  if username == "jack" {
    return "jack123456", nil
  }
  return "", errors.New("user not found")
}

Nội dung module user như sau

go
package user

type User struct {
  Name     string
  Password string
  Age      int
}

Trong dự án này chúng ta có thể viết file go.work như sau

go 1.22

use (
  ./auth
  ./user
)

Nội dung của nó rất dễ hiểu sử dụng chỉ thị use chỉ định những module nào tham gia biên dịch tiếp theo chạy mã trong module auth

go
// auth/example/main.go
package main

import (
  "fmt"
  "github.com/246859/work/auth"
  "github.com/246859/work/user"
)

func main() {
  ok, err := auth.Verify(user.User{Name: "jack", Password: "jack123456"})
  if err != nil {
    panic(err)
  }
  fmt.Printf("%v", ok)
}

Thực hiện lệnh sau thông qua kết quả biết được import module thành công.

bash
$ go run ./auth/example
true

Trong các phiên bản trước đối với hai module độc lập này nếu module auth muốn sử dụng mã của module user chỉ có hai cách

  1. Submit sửa đổi của module user và đẩy vào kho từ xa phát hành phiên bản mới sau đó sửa đổi file go.mod thành phiên bản chỉ định
  2. Sửa đổi file go.mod chuyển hướng phụ thuộc vào file local

Hai cách đều cần sửa đổi file go.mod và sự tồn tại của workspace là để có thể import các module khác mà không cần sửa đổi file go.mod. Nhưng cần hiểu rõ một điểm là file go.work chỉ dùng trong quá trình phát triển sự tồn tại của nó chỉ để thuận tiện hơn cho việc phát triển local chứ không phải để quản lý phụ thuộc nó chỉ tạm thời cho phép bạn bỏ qua quá trình submit đến phát hành này có thể cho phép bạn ngay lập tức sử dụng sửa đổi mới của module user mà không cần chờ đợi nên sau khi module user kiểm tra xong cuối cùng vẫn cần phát hành phiên bản mới và module auth cuối cùng vẫn phải sửa đổi file go.mod để import phiên bản mới nhất (quá trình này có thể hoàn thành bằng lệnh go work sync) nên trong quá trình phát triển go bình thường go.work cũng không nên submit vào VCS (file go.work trong kho ví dụ chỉ để demo) vì nội dung của nó đều phụ thuộc vào file local và chức năng của nó cũng chỉ giới hạn ở phát triển local.

Lệnh

Dưới đây là một số lệnh của workspace

LệnhGiới thiệu
editChỉnh sửa go.work
initKhởi tạo một workspace mới
syncĐồng bộ phụ thuộc module của workspace
useThêm một module mới vào go.work
vendorSao chép phụ thuộc theo định dạng vendor

Đến go work cmd để biết thêm thông tin liên quan về lệnh

Chỉ thị

Nội dung của file go.work rất đơn giản chỉ có ba chỉ thị

  • go chỉ định phiên bản go
  • use chỉ định module sử dụng
  • replace chỉ định module thay thế

Ngoài chỉ thị use ra hai cái còn lại về cơ bản tương đương với chỉ thị trong go.mod chỉ là chỉ thị replace trong go.work sẽ tác động lên tất cả các module một go.work hoàn chỉnh như sau.

tex
go 1.22

use(
  ./auth
  ./user
)

repalce github.com/246859/hello v1.0.0 => /home/jack/code/hello

Golang by www.golangdev.cn edit