一個Go項目的結構設計始終遵循Go語言的簡潔高效理念。一個合理和良好的布局可以提高代碼的可讀性,簡化依賴管理,并優化編譯過程。
像cmd、internal和docs這樣的目錄是標準Go項目的基礎,起著不同的作用。比如,cmd目錄是可執行文件的入口,docs是所有相關文檔的入口,而internal包含項目私有代碼。適當的包劃分可以避免循環依賴,便于單元測試和代碼復用。
然而,這種Go項目布局可能導致過多的目錄層級和包劃分,會給管理工作帶來負擔,并有時讓初學者感到不知所措。
因此,在設計時如何把握什么算是“合理”,就成了關鍵。
這篇文章,讓我們嘗試在目錄結構和包設計中找到簡潔和功能之間的平衡,使項目能夠在變化中健康迭代。
標準布局
像cmd和internal這樣的目錄結構是由一些Go語言社區的開發者在系統總結之前進行了總結,并在標準Go項目布局中得到了進一步的概括,該項目已經獲得了超過4萬個star。盡管其起源是一個提示,但標準布局已經成為Go項目目錄結構的事實標準。
這并不是由核心Go開發團隊定義的官方標準。
my-App/ # Root directory of the project
|── cmd/ # Executables directory
└── myapp/ # MAIn application package
└── main.go # Main application entry point
|── internal/ # Private application and library code
└── package1/ # Internal package 1
|── pkg/ # Library code that can be exported
└── package2/ # External package 2
|── api/ # API protocol definitions directory
|── configs/ # Configuration files directory
|── deployments/ # Deployment configurations and scripts
|── scripts/ # Scripts for build, install, analysis, etc.
|── build/ # Packaging and Continuous Integration
|── tests/ # Additional external test apps and test data
|── docs/ # Design and user documents
|── tools/ # Supporting tools for the project
|── examples/ # Application or public library examples
|── third_party/ # External helper tools, forked code, and other 3rd party utilities
|── githooks/ # Git hooks
|── assets/ # Other assets like images, logos, etc.
|── vendor/ # Dependency package directory (if using vendor mode)
|── go.mod # Module dependency file
|── go.sum # Module checksum file for dependency verification
如果你經常閱讀源代碼,你會輕易地發現,大多數在Github上知名的Go開源項目基本上都遵循上述布局,比如Kube.NETes這個大型Go項目。
讓我們簡單看一下。
- 與Go模塊相關的go.mod和go.sum是必不可少的。
- pkg目錄包含api、apis、kubectl等包,可應用于外部項目,比如基于Kubernetes的開發。
- cmd包含了Kubernetes中各種命令行的main方法入口,比如kubectl.go。
- api目錄存儲與openApiv3相關的json文件。
- test目錄包含所有的e2e和集成測試代碼,根據不同的包進行了分別存儲。
- third_party存儲第三方引用的工具,比如protobuf。
- vendor用于存儲外部依賴,比如k8s.io、etcd等。
- docs目錄目前為空。
當然,Kubernetes項目并不完全遵循標準布局,因為其規模較大。例如,許多Kubernetes腳本存儲在build和cluster目錄中,而不是scripts目錄。還有一些用于特定需求的目錄,比如hacks和staging。
官方布局
2023年發布的文章《組織Go模塊》揭示了Go團隊對布局的不同觀點,提供了根據項目復雜性設計目錄結構的參考,涵蓋了具有少量Go文件、單個cmd命令或簡單包的項目,以及具有多個cmds和多個包的項目。
對它們進行總結如下,并將其作為下一節的官方布局。
my-module/ # Root directory for the module with go.mod
├── go.mod # Module's dependency file
├── go.sum # The module's checksums for dependency validation
├── cmd/ # Directory for commands (each subdirectory here is a main package)
│ └── my-app/ # Main application for this module
│ └── main.go # Main application entry point
├── internal/ # Internal packages meant for use only within this module
│ └── mylib/ # An internal package example
│ └── mylib.go # The package's specific code
├── api/ # API protocol definitions, e.g., protocol buffer files
├── web/ # Web application specific components: static files, server-side templates, SPAs, etc.
├── pkg/ # Library code that's ok to use by external applications (deprecated by some in the community)
│ └── mypkg/ # An example package that could be imported by other applications
│ └── mypkg.go # Package code
├── README.md # Project README file
├── LICENSE # License file
├── .gitignore # Specifies intentionally untracked files to ignore
└── ... <-- Other directories and files as needed
標準布局與官方布局
這兩種布局有一些共同的思想。
- 模塊化。不同的功能會被放入不同的包中,以提高可重用性。
- 提高可見性。根據功能將不同的包存儲在不同的目錄中,以提高可讀性。
基于這些概念,標準布局中有一個通用的cmd目錄來存儲命令行代碼,子包用于保存多個命令,internal目錄用于保存不對外共享的代碼。目錄路徑和包名稱與main.go作為入口文件保持一致。
但是,它們對于像pkg和util這樣的目錄有不同的考慮,例如,Russ Cox反對以pkg和util等模糊方式命名軟件庫。此外,由于社區的貢獻,標準布局比官方建議覆蓋范圍更廣,添加了像scripts和build這樣的目錄。
Go-Clean-Template
標準布局和官方布局都是通用的,將項目分為cmd項目和非cmd項目,因此對于包含entity和dao等多個包的復雜項目(如使用Go開發的Web項目),它們并不是首選。go-clean-template則專為這類Web項目量身定制。
go-clean-template/ <-- Root directory of the project
├── cmd/ <-- Main application entry points
│ └── appname/ <-- Specific startup logic and configuration for 'appname' app
│ └── main.go <-- Main application startup entry
├── internal/ <-- Internal modules, not importable by external applications
│ ├── entity/ <-- Entities (business objects) of the application
│ ├── usecase/ <-- Use case layer containing business logic interfaces
│ ├── repository/ <-- Data storage interfaces
│ ├── handler/ <-- HTTP handlers for receiving requests and calling use cases
│ └── config/ <-- Configuration related code
├── pkg/ <-- Public library code that can be imported by other projects
├── test/ <-- External testing code
├── .Dockerignore <-- Specifies files to ignore in Docker builds
├── .gitignore <-- Specifies intentionally untracked files to ignore in Git
├── Dockerfile <-- Docker configuration file for containerization
├── go.mod <-- Go module dependencies file
├── go.sum <-- Checksums for Go module dependencies
└── Makefile <-- Makefile containing automation commands
作為標準布局的擴展,go-clean-template保留了pkg目錄,以便更好地管理眾多公共庫。它明確定義了像entity、repository、config等子包,省去了我們為它們命名的工作。它還有像test和integration-test這樣的目錄,用于放置相應的測試代碼,提高可讀性。
小結
本文帶大家深入研究了組織Go代碼庫的三種布局:
標準布局提供了一個由社區驅動的、廣泛適用的結構,非常適合需要清晰關注點分離的大型項目。
官方布局由Go的創建者認可,強調簡潔和靈活性,適用于各種項目,特別是那些優先考慮模塊管理的項目。
基于Clean Architecture原則的go-clean-template在需要將業務邏輯、數據訪問和接口層明確分離以提高可維護性和可測試性的項目中表現出色。
這三種范式適應不同的項目需求,每種都提供了一套可自適應和組合的指南。選擇其中之一取決于具體項目的要求、規模和復雜性。