Go module được giới thiệu trong phiên bản 1.11. Đến nay Go module trở nên rất phổ biến, dần thay thế các cách quản lý package trước đây. Go module hoạt động như thế nào, bài viết này sẽ giải thích chi tiết

1 Dẫn nhập

Một ngôn ngữ lập trình muốn được phổ biến rộng rãi, thì cần phải có cơ chế quản lý các thư viện mã nguồn mở một cách hiệu quả, nhanh, tiện lợi:

Golang cũng không thể nằm ngoài xu hướng này. Ban đầu việc quản lý các package trong Golang khá cởi mở và có nhiều phương án khác như go vendor, go dep, go glides. Từ phiên bản 1.11, Go modules được giới thiệu. Có lẽ nó sẽ là xu hướng chủ đạo, hợp nhất để quản lý Go package / module từ nay về sau.

2 Go module có gì mới?

2.1 Go package là gì?

Go package rất giống với Java package. Nó tập hợp một hoặc nhiều file *.go nằm trong cùng một thư mục trùng tên với tên package.

Ví dụ package utils chứa 2 file checkPrime.go và properCase.go có dòng đầu tiên khai báo package utils

├── utils
│   ├── checkPrime.go
│   └── properCase.go
package utils
import "math"
func IsPrime(number int) bool {
}
package utils
import (
    "strings"
)
func ProperCase(input string) string {
}

2.2 Go module là gì ?

Go module là cơ chế mới có từ phiên bản Go 1.11 đùng để đóng gói và phát hành Go package. Một Go module có thể chứa một package hoặc các package phụ thuộc nhau hoặc package theo những phiên bản khác nhau. Hiểu đơn giản package quản lý một hoặc nhiều file cùng thư mục trùng tên với package, cùng phục vụ một số chức năng. Còn module dùng để phát hành, chia sẻ và quản lý phiên bản package.

Go modules có những tính năng mới là:

  1. Thay thế việc lưu mã nguồn của các package trong thư mục $GOPATH hoặc các thư viện quản lý package không phải do Google chuẩn hoá như glide, vendor.
  2. Quản lý các dependencies (thư viện phụ thuộc) theo phiên bản để tránh tình trạng xung đột.
  3. Tối ưu tải thư viện từ Internet sử dụng caching
  4. Hỗ trợ phát hành module dạng mã nguồn mở lên một public git repo hoặc bảo mật mã nguồn của private module.
  5. Hỗ trợ biên dịch module ra dạng file binary gọi là plugin như vậy đóng hoàn toàn mã nguồn để phát hành.

3 Tạo go modules

Để tạo go modules chúng ta sử dụng lệnh

$ go mod init module_name

module_name ở đây có thể là:

  • đường dẫn đến thư mục git repo trên Internet (có thể là github, gitlab) nếu bạn muốn công khai, chia sẻ module của mình
  • bất kỳ tên gì nếu bạn chỉ muốn dùng module này cục bộ, không phát hành, không chia sẻ mã nguồn hoặc đó là executable module chứa package main

Ví dụ go module công khai mã nguồn, module_name phải là đường dẫn chính xác đến git repo. Ví dụ ở đây là https://github.com/TechMaster/mygomodule

$ go mod init github.com/TechMaster/mygomodule

Ví dụ go module không có ý định chia sẻ mã nguồn, chỉ dùng nội bộ hoặc là executable module

$ go mod init techmaster.vn/whatevername

3.1 Ý nghĩa của file go.mod

File go.mod sẽ tương tự như file pom.xml của maven của dự án Java hay file package.json của dự án Node.js. Nó chứa

  • module : tên của module
  • golang: phiên bản golang mà module yêu cầu
  • required(...): danh sách các module thư viện
module techmaster.vn/whatevername
go 1.16
require (
    github.com/TechMaster/mygomodule v0.0.0-20210526162113-c243dee7fcfd
    rsc.io/quote v1.5.2
)

Nếu tác giả của mygomodule không đánh dấu phiên bản commit bằng tag thì lệnh require mygomodule sẽ có dạng như sau

github.com/TechMaster/mygomodule v0.0.0-20210526162113-c243dee7fcfd với 20210526162113-c243dee7fcfd gồm có:

  • 20210526 = yyyy MM dd
  • 162113 = 16:21:13 theo giờ GMT
  • c243dee7fcfd: check sum các file trong mygomodule

Nếu tác giả của mygomodule đánh dấu phiên bản commit bằng tag thì lệnh require mygomodule có dạng

github.com/TechMaster/mygomodule v1.0.1

3.2 Một module có thể chứa một hoặc nhiều package

Hãy tham khảo mã nguồn của mygomodule

├── mathutil
│   └── basicmath.go
├── go.mod
└── mygomodule.go

Module này chứa 2 package mygomodule ở cùng thư mục với file go.mod và file basicmath.go thuộc package mathutil. Khi sử dụng 2 package này chúng ta sẽ khai báo trong file go như sau:

import (
    "github.com/TechMaster/mygomodule"
    "github.com/TechMaster/mygomodule/mathutil"
)

4. Sử dụng lại một module - import module

Có ít nhất là 3 cách để sử dụng lại một module:

4.1 Sử dụng lệnh go get module_name hoặc go mod tidy

Ví dụ

$ go get github.com/TechMaster/mygomodule

hay

$ go get rsc.io/quote

Có hai điểm các bạn cần lưu ý về đường dẫn trong lệnh import

  • github.com/TechMaster/mygomodule/mathutil nhìn rất gọn gàng nhưng thực ra trả về lỗi 404, đường dẫn có thể xem được thư mục mathutil trên trình duyệt phải là https://github.com/TechMaster/mygomodule/tree/main/mathutil. Tuy nhiên lệnh go get hay go mod tidy có cơ chế tự động chuyển đổi đường dẫn github, do đó github.com/TechMaster/mygomodule/mathutil mới là đường dẫn hợp lệ khi import package.
  • rsc.io/quote là tên module, chứ không phải đường dẫn đến git repo chứa mã nguồn https://github.com/rsc/quote . Vậy làm sao lệnh go get hay go mod tidy có thể suy luận từ tên module sang đường dẫn git repo? Câu trả lời ở đây là cơ chế Go Proxy giúp lập trình viên phát hành mã nguồn thư viện go, tôi sẽ nói ở phần sau.

4.2 go get khác gì với go mod tity ?

  • go get -u nâng cấp các thư viện lên phiên bản mới
  • go get -u module_name nâng cấp một thư viện cụ thể
  • go mod tidy bổ xung thư viện cần thiết, loại bỏ thư viện không dùng đến, hoặc nâng cấp thư viện từ phiên bản cũ lên bản mới nhất. go mod tidy đa năng hơn, dễ dùng hơn go get. Nó giúp lập trình viên ít phải bận tâm.

4.3 Sử dụng local module

Trong ứng dụng, tôi cần tạo ra cấu trúc thư mục phân cấp theo chức năng. Ví dụ một module có tên là utils chứa phương thức kiểm tra số nguyên tố. Tôi sẽ làm như sau:

  1. Tạo thư mục con có tên là utils
  2. Tạo file tên bất kỳ nhưng buộc phải khai báo tên package là utils. Ngắn gọn là tên thư mục và tên package phải giống nhau.
  3. Trong file app.go chỉ cần gõ utils.isPrime() khi lưu, tự động go fmt sẽ bổ xung lệnh import phù hợp "techmaster.vn/whatevername/utils"
local module
 package utils
 import "math"
 func IsPrime(number int) bool {
  if number <= 2 {
      return true
  }
  sq_root := int(math.Sqrt(float64(number)))
  for i := 2; i <= sq_root; i++ {
      if number%i == 0 {
          return false
      }
  }
  return true
 }

5. Ý nghĩa của go.sum

Trong ví dụ này, chúng ta thấy khi chạy lệnh go mod tidy sẽ có một file go.sum được tạo ra hoặc cập nhật lại

github.com/TechMaster/mygomodule v1.0.1 h1:FuTs70MhPurvbo2EngnAS97Z6euL8JQGopO+zj7vLHc=
github.com/TechMaster/mygomodule v1.0.1/go.mod h1:dpC9wI/SUDqN8aGiqkb0gYv2b+Dw++z8Np2Q5A3afu8=
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=

go.sum là file lưu chuỗi check sum các module mà ứng dụng phụ thuộc trực tiếp hoặc gián tiếp ( module A, dùng module B, module B dùng module C)

Ý nghĩa của go.sum

6. Những điểm bạn cần tìm hiểu thêm về go module

Bài blog này không thể bao quát mọi vấn đề trong go module. Sau đây là những gợi ý để bạn tìm hiểu thêm về go module

  • Cách quản lý phiên bản của go module
  • Phát hành go module sử dụng go proxy

Nguồn tham khảo