Bạn đang sửa một thư viện dùng chung lib và ứng dụng app dùng nó — cả hai là module riêng, cùng nằm trên máy bạn, và lib chưa được đẩy lên remote. Làm sao để app thấy bản lib bạn vừa sửa mà không phải publish sau mỗi lần đổi một dòng? Trước Go 1.18, câu trả lời là thêm replace vào go.mod — một hack dễ commit nhầm và làm bẩn lịch sử. Go 1.18 giới thiệu workspace qua file go.work, giải bài toán này sạch sẽ. Bài này đo thật cách workspace hoạt động và vì sao nó hơn hẳn replace.

Vấn đề: module local chưa publish

Xét hai module riêng nằm cạnh nhau:

/work/
├── app/    module example.com/app  (import example.com/lib)
└── lib/    module example.com/lib  (chưa đẩy lên remote)

app/main.go import example.com/lib, nhưng lib mới chỉ ở đĩa local. Đo thật trong go-lab (Go 1.23), build app cho lỗi:

$ go build ./app
main.go:4:2: no required module provides package example.com/lib;
	to add it: go get example.com/lib

Go module cố tải example.com/lib từ mạng — nó không biết bản local ngay bên cạnh. go get cũng vô ích vì module chưa tồn tại trên remote. Đây chính là ma sát mà mọi người phát triển đa module gặp phải.

Ảnh chụp đoạn mã Go nền tối minh hoạ go work làm việc nhiều module cùng lúc không cần replace thủ công, vấn đề app dùng lib local chưa publish thư mục work app module example com app import example com lib lib module example com lib chưa đẩy lên remote app build lỗi không tìm thấy lib vì nó chưa có trên mạng go build app no required module provides package example com lib, giải tạo workspace bằng go work go work init app lib sinh go work ở thư mục cha go work sinh ra go 1.23 use app lib go work use tool thêm module thứ 3 vào sau, điểm mấu chốt không đụng go mod cách cũ trước Go 1.18 sửa go mod của app replace example com lib sang chấm chấm lib làm bẩn go mod dễ commit nhầm cách mới go work giữ ánh xạ local go mod sạch app go mod module example com app 0 dòng replace go work không commit mỗi dev tự dựng không ảnh hưởng người khác, điều khiển bằng biến GOWORK go env GOWORK đường dẫn go work đang dùng GOWORK off go build tắt workspace dùng go mod thuần go work edit json đọc workspace bằng máy tooling

Hình 1: app dùng lib local chưa publish → build lỗi. go work init ./app ./lib sinh file go.work với danh sách use, ánh xạ nhiều module local — khác cách cũ phải thêm replace vào go.mod.

Giải pháp: go work init và use

Workspace được dựng bằng lệnh go work init với danh sách các module tham gia:

go work init ./app ./lib   # sinh go.work ở thư mục cha

Lệnh này tạo file go.work ở thư mục cha, chứa:

go 1.23

use (
	./app
	./lib
)

Mệnh đề use liệt kê các thư mục module thuộc workspace. Khi build từ bất kỳ đâu trong cây workspace, Go ưu tiên các module local này thay vì tải từ mạng. Đo thật, sau khi có go.work:

$ go build .        # từ thư mục app
build OK (dùng lib local qua go.work)
$ go run ./app
Cong(2,3) = 5       # app gọi hàm Cong trong lib local

app giờ thấy lib local ngay, gọi được lib.Cong(2, 3) trả về 5. Thêm module mới vào workspace về sau bằng go work use ./tool.

Điểm mấu chốt: go.mod vẫn sạch

Đây là lý do go.work hơn hẳn replace. Đo thật, sau khi dựng workspace, app/go.mod không có một dòng replace nào:

Ảnh chụp bảng kết quả đo thật nền tối không go work thì lỗi có go work thì build lib local go mod vẫn sạch, go work init cộng go build cộng go env Go 1.23 arm64 10 core, trước và sau go work từ thư mục app trước GOWORK off GOWORK off go build main go dòng 4 2 no required module provides package example com lib, sau workspace bật mặc định go build build OK dùng lib local qua go work go run app Cong 2 3 bằng 5 app gọi hàm trong lib local, go work sinh ra và go mod không bị đụng go work thư mục cha go 1.23 use app lib tool app go mod module example com app go 1.23 0 dòng replace go mod sạch cho người khác clone về vẫn build bình thường go env GOWORK work t080 go work đang ở chế độ workspace, go work use thêm module go work edit json cho tooling go work use tool thêm module thứ 3 go work edit json Go 1.23 Use DiskPath app DiskPath lib DiskPath tool, cốt lõi vấn đề app dùng lib local chưa publish build lỗi go work init cộng use dựng go work ánh xạ nhiều module local sạch go mod không cần replace go work không commit GOWORK off tắt workspace go env GOWORK xem đang dùng file nào thay thế replace thủ công cách cũ dễ commit nhầm vào go mod

Hình 2: Từ thư mục app — GOWORK=off build lỗi (mất lib local), workspace bật mặc định thì Cong(2,3)=5. app/go.mod có 0 dòng replace; go env GOWORK cho thấy file workspace đang dùng. go work use thêm module, go work edit -json cho tooling.

replace (cách cũ) sửa thẳng go.mod — file này được commit, nên đường dẫn local kiểu replace example.com/lib => ../lib dễ lọt vào git và làm hỏng build của người khác (đường dẫn ../lib không tồn tại trên máy họ). go.work cố ý không nên commit (thêm vào .gitignore): mỗi lập trình viên tự dựng workspace của mình, còn go.mod giữ nguyên sạch sẽ cho mọi người. Người khác clone repo về vẫn build bình thường qua module remote thật.

Workspace điều khiển bằng biến GOWORK: go env GOWORK cho biết file đang dùng (/work/t080/go.work), GOWORK=off go build tắt workspace để build như module thuần (kiểm tra xem code có build đúng ngoài workspace không — quan trọng trước khi push). go work edit -json xuất cấu hình dạng JSON cho công cụ đọc.

Đánh đổi cần cân nhắc

go.work cho phát triển local, không cho build sản xuất. Workspace là công cụ lúc phát triển: nó ghi đè việc phân giải module để bạn sửa nhiều module cùng lúc. Nhưng khi build/CI cho sản xuất, bạn muốn dùng đúng phiên bản trong go.mod/go.sum (có kiểm tra checksum) — nên CI thường chạy GOWORK=off hoặc đơn giản không có go.work. Đừng dựa vào workspace cho reproducible build.

Đừng commit go.work trong repo đa người dùng. Vì đường dẫn use là đường dẫn local (./app, ../lib), commit go.work làm build của người khác lệ thuộc vào bố cục thư mục của bạn. Ngoại lệ: một monorepo nơi mọi module nằm cố định trong cùng cây thư mục — khi đó commit go.work có thể hợp lý vì đường dẫn nhất quán với mọi người. Cân theo bố cục dự án.

Vẫn phải cập nhật go.mod khi hoàn tất. Workspace chỉ là cầu tạm. Khi bạn sửa xong lib và publish nó, phải cập nhật require trong app/go.mod sang phiên bản mới thật (go get example.com/lib@v1.2.0), rồi mới bỏ khỏi workspace. go.work không thay thế việc quản lý phụ thuộc thật — nó chỉ giúp giai đoạn phát triển song song mượt hơn.

Ba ý mang về

  1. go.work cho phép làm việc nhiều module local cùng lúc: go work init ./app ./lib sinh file workspace với danh sách use, để app dùng ngay lib local chưa publish — đo thật, không go.work thì build lỗi no required module provides package, có go.work thì Cong(2,3)=5.
  2. go.mod vẫn sạch — hơn hẳn replace thủ công: đo thật app/go.mod có 0 dòng replace; go.work giữ ánh xạ local riêng và không nên commit (mỗi dev tự dựng), khác replace sửa thẳng go.mod dễ commit nhầm làm hỏng build người khác.
  3. Workspace là công cụ phát triển, không cho build sản xuất: điều khiển bằng GOWORK (=off để tắt, go env GOWORK để xem), CI nên build không workspace để dùng đúng phiên bản có checksum — và nhớ cập nhật go.mod thật khi module đã publish.

Phần sau ta chuyển từ công cụ sang kiến trúc phần mềm: Phần sau mổ xẻ Clean Architecture trong Go — cách tổ chức tầng, hướng phụ thuộc, và những chỗ triết lý "đơn giản" của Go va chạm với các lớp trừu tượng.