組織 Go 模組

Go 新手開發者常問的一個問題是:“我該如何組織我的 Go 專案?”,這涉及到檔案和資料夾的佈局。本文件旨在提供一些指導方針,以幫助回答這個問題。要充分利用本文件,請務必透過閱讀教程管理模組源來熟悉 Go 模組的基礎知識。

Go 專案可以包含包、命令列程式或兩者的組合。本指南按專案型別組織。

基本包

一個基本的 Go 包將其所有程式碼放在專案的根目錄中。專案由一個模組組成,該模組由一個包組成。包名與模組名的最後一個路徑元件匹配。對於一個只需要一個 Go 檔案的非常簡單的包,專案結構如下:

project-root-directory/
  go.mod
  modname.go
  modname_test.go

[在本文件中,檔案/包名是完全任意的]

假設此目錄已上傳到 GitHub 倉庫 github.com/someuser/modname,則 go.mod 檔案中的 module 行應為 module github.com/someuser/modname

modname.go 中的程式碼使用以下方式宣告包:

package modname

// ... package code here

使用者可以透過在他們的 Go 程式碼中 import 它來依賴此包,如下所示:

import "github.com/someuser/modname"

一個 Go 包可以拆分成多個檔案,所有檔案都位於同一個目錄中,例如:

project-root-directory/
  go.mod
  modname.go
  modname_test.go
  auth.go
  auth_test.go
  hash.go
  hash_test.go

目錄中的所有檔案都宣告 package modname

基本命令

一個基本的執行程式(或命令列工具)根據其複雜性和程式碼大小進行結構化。最簡單的程式可以由一個定義了 func main 的單個 Go 檔案組成。較大的程式可以將程式碼拆分到多個檔案中,所有檔案都宣告 package main

project-root-directory/
  go.mod
  auth.go
  auth_test.go
  client.go
  main.go

這裡 main.go 檔案包含 func main,但這只是一個約定。“主”檔案也可以命名為 modname.go(對於適當的 modname 值)或任何其他名稱。

假設此目錄已上傳到 GitHub 倉庫 github.com/someuser/modname,則 go.mod 檔案中的 module 行應為:

module github.com/someuser/modname

使用者應該能夠透過以下方式將其安裝到他們的機器上:

$ go install github.com/someuser/modname@latest

帶有支援包的包或命令

較大的包或命令可能會受益於將某些功能拆分到支援包中。最初,建議將此類包放入名為 internal 的目錄中;這可以防止其他模組依賴於我們不一定希望暴露並支援外部使用的包。由於其他專案無法從我們的 internal 目錄匯入程式碼,我們可以自由地重構其 API 並通常移動事物而不會破壞外部使用者。因此,包的專案結構如下:

project-root-directory/
  internal/
    auth/
      auth.go
      auth_test.go
    hash/
      hash.go
      hash_test.go
  go.mod
  modname.go
  modname_test.go

modname.go 檔案宣告 package modnameauth.go 宣告 package auth,依此類推。modname.go 可以按如下方式匯入 auth 包:

import "github.com/someuser/modname/internal/auth"

internal 目錄中包含支援包的命令的佈局非常相似,只是根目錄中的檔案宣告 package main

多個包

一個模組可以由多個可匯入的包組成;每個包都有自己的目錄,並且可以分層結構。這是一個示例專案結構:

project-root-directory/
  go.mod
  modname.go
  modname_test.go
  auth/
    auth.go
    auth_test.go
    token/
      token.go
      token_test.go
  hash/
    hash.go
  internal/
    trace/
      trace.go

提醒一下,我們假設 go.mod 中的 module 行是:

module github.com/someuser/modname

modname 包位於根目錄中,宣告 package modname,使用者可以透過以下方式匯入:

import "github.com/someuser/modname"

使用者可以按如下方式匯入子包:

import "github.com/someuser/modname/auth"
import "github.com/someuser/modname/auth/token"
import "github.com/someuser/modname/hash"

位於 internal/tracetrace 包無法在此模組之外匯入。建議儘可能將包保留在 internal 中。

多個命令

同一倉庫中的多個程式通常會有單獨的目錄:

project-root-directory/
  go.mod
  internal/
    ... shared internal packages
  prog1/
    main.go
  prog2/
    main.go

在每個目錄中,程式的 Go 檔案宣告 package main。頂級 internal 目錄可以包含倉庫中所有命令使用的共享包。

使用者可以按如下方式安裝這些程式:

$ go install github.com/someuser/modname/prog1@latest
$ go install github.com/someuser/modname/prog2@latest

一個常見的約定是將倉庫中的所有命令放入 cmd 目錄;雖然在僅由命令組成的倉庫中這並非嚴格必要,但在包含命令和可匯入包的混合倉庫中,這非常有用,我們將在下一節討論。

同一倉庫中的包和命令

有時,一個倉庫會提供具有相關功能的可匯入包和可安裝命令。這是一個此類倉庫的示例專案結構:

project-root-directory/
  go.mod
  modname.go
  modname_test.go
  auth/
    auth.go
    auth_test.go
  internal/
    ... internal packages
  cmd/
    prog1/
      main.go
    prog2/
      main.go

假設此模組名為 github.com/someuser/modname,使用者現在可以從中匯入包:

import "github.com/someuser/modname"
import "github.com/someuser/modname/auth"

並從中安裝程式:

$ go install github.com/someuser/modname/cmd/prog1@latest
$ go install github.com/someuser/modname/cmd/prog2@latest

伺服器專案

Go 是實現伺服器的常用語言選擇。鑑於伺服器開發的許多方面:協議(REST?gRPC?)、部署、前端檔案、容器化、指令碼等,此類專案的結構差異很大。我們在此將重點放在專案中使用 Go 編寫的部分。

伺服器專案通常不會有用於匯出的包,因為伺服器通常是一個自包含的二進位制檔案(或一組二進位制檔案)。因此,建議將實現伺服器邏輯的 Go 包儲存在 internal 目錄中。此外,由於專案可能有很多其他非 Go 檔案的目錄,因此將所有 Go 命令儲存在 cmd 目錄中是一個好主意:

project-root-directory/
  go.mod
  internal/
    auth/
      ...
    metrics/
      ...
    model/
      ...
  cmd/
    api-server/
      main.go
    metrics-analyzer/
      main.go
    ...
  ... the project's other directories with non-Go code

如果伺服器倉庫中包含的包變得對與其他專案共享有用,最好將這些包拆分到單獨的模組中。