組織 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,但這只是一個慣例。「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/trace 中的套件 trace 無法在此模組外匯入。建議盡可能將套件保留在 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

如果伺服器儲存庫的套件日後變得對其他專案有用,最好將它們拆分到不同的模組中。