如何優(yōu)雅的組織Golang項(xiàng)目結(jié)構(gòu)
一個(gè)Go項(xiàng)目的結(jié)構(gòu)設(shè)計(jì)始終遵循Go語言的簡潔高效理念。一個(gè)合理和良好的布局可以提高代碼的可讀性,簡化依賴管理,并優(yōu)化編譯過程。
像cmd、internal和docs這樣的目錄是標(biāo)準(zhǔn)Go項(xiàng)目的基礎(chǔ),起著不同的作用。比如,cmd目錄是可執(zhí)行文件的入口,docs是所有相關(guān)文檔的入口,而internal包含項(xiàng)目私有代碼。適當(dāng)?shù)陌鼊澐挚梢员苊庋h(huán)依賴,便于單元測試和代碼復(fù)用。
然而,這種Go項(xiàng)目布局可能導(dǎo)致過多的目錄層級和包劃分,會(huì)給管理工作帶來負(fù)擔(dān),并有時(shí)讓初學(xué)者感到不知所措。
因此,在設(shè)計(jì)時(shí)如何把握什么算是“合理”,就成了關(guān)鍵。
這篇文章,讓我們嘗試在目錄結(jié)構(gòu)和包設(shè)計(jì)中找到簡潔和功能之間的平衡,使項(xiàng)目能夠在變化中健康迭代。
標(biāo)準(zhǔn)布局
像cmd和internal這樣的目錄結(jié)構(gòu)是由一些Go語言社區(qū)的開發(fā)者在系統(tǒng)總結(jié)之前進(jìn)行了總結(jié),并在標(biāo)準(zhǔn)Go項(xiàng)目布局中得到了進(jìn)一步的概括,該項(xiàng)目已經(jīng)獲得了超過4萬個(gè)star。盡管其起源是一個(gè)提示,但標(biāo)準(zhǔn)布局已經(jīng)成為Go項(xiàng)目目錄結(jié)構(gòu)的事實(shí)標(biāo)準(zhǔn)。
這并不是由核心Go開發(fā)團(tuán)隊(duì)定義的官方標(biāo)準(zhǔn)。
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
如果你經(jīng)常閱讀源代碼,你會(huì)輕易地發(fā)現(xiàn),大多數(shù)在GitHub上知名的Go開源項(xiàng)目基本上都遵循上述布局,比如Kubernetes這個(gè)大型Go項(xiàng)目。
圖片
讓我們簡單看一下。
- 與Go模塊相關(guān)的go.mod和go.sum是必不可少的。
- pkg目錄包含api、apis、kubectl等包,可應(yīng)用于外部項(xiàng)目,比如基于Kubernetes的開發(fā)。
- cmd包含了Kubernetes中各種命令行的main方法入口,比如kubectl.go。
- api目錄存儲(chǔ)與openApiv3相關(guān)的json文件。
- test目錄包含所有的e2e和集成測試代碼,根據(jù)不同的包進(jìn)行了分別存儲(chǔ)。
- third_party存儲(chǔ)第三方引用的工具,比如protobuf。
- vendor用于存儲(chǔ)外部依賴,比如k8s.io、etcd等。
- docs目錄目前為空。
當(dāng)然,Kubernetes項(xiàng)目并不完全遵循標(biāo)準(zhǔn)布局,因?yàn)槠湟?guī)模較大。例如,許多Kubernetes腳本存儲(chǔ)在build和cluster目錄中,而不是scripts目錄。還有一些用于特定需求的目錄,比如hacks和staging。
官方布局
2023年發(fā)布的文章《組織Go模塊》揭示了Go團(tuán)隊(duì)對布局的不同觀點(diǎn),提供了根據(jù)項(xiàng)目復(fù)雜性設(shè)計(jì)目錄結(jié)構(gòu)的參考,涵蓋了具有少量Go文件、單個(gè)cmd命令或簡單包的項(xiàng)目,以及具有多個(gè)cmds和多個(gè)包的項(xiàng)目。
對它們進(jìn)行總結(jié)如下,并將其作為下一節(jié)的官方布局。
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
標(biāo)準(zhǔn)布局與官方布局
這兩種布局有一些共同的思想。
- 模塊化。不同的功能會(huì)被放入不同的包中,以提高可重用性。
- 提高可見性。根據(jù)功能將不同的包存儲(chǔ)在不同的目錄中,以提高可讀性。
基于這些概念,標(biāo)準(zhǔn)布局中有一個(gè)通用的cmd目錄來存儲(chǔ)命令行代碼,子包用于保存多個(gè)命令,internal目錄用于保存不對外共享的代碼。目錄路徑和包名稱與main.go作為入口文件保持一致。
但是,它們對于像pkg和util這樣的目錄有不同的考慮,例如,Russ Cox反對以pkg和util等模糊方式命名軟件庫。此外,由于社區(qū)的貢獻(xiàn),標(biāo)準(zhǔn)布局比官方建議覆蓋范圍更廣,添加了像scripts和build這樣的目錄。
Go-Clean-Template
標(biāo)準(zhǔn)布局和官方布局都是通用的,將項(xiàng)目分為cmd項(xiàng)目和非cmd項(xiàng)目,因此對于包含entity和dao等多個(gè)包的復(fù)雜項(xiàng)目(如使用Go開發(fā)的Web項(xiàng)目),它們并不是首選。go-clean-template則專為這類Web項(xiàng)目量身定制。
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
作為標(biāo)準(zhǔn)布局的擴(kuò)展,go-clean-template保留了pkg目錄,以便更好地管理眾多公共庫。它明確定義了像entity、repository、config等子包,省去了我們?yōu)樗鼈兠墓ぷ鳌K€有像test和integration-test這樣的目錄,用于放置相應(yīng)的測試代碼,提高可讀性。
小結(jié)
本文帶大家深入研究了組織Go代碼庫的三種布局:
標(biāo)準(zhǔn)布局提供了一個(gè)由社區(qū)驅(qū)動(dòng)的、廣泛適用的結(jié)構(gòu),非常適合需要清晰關(guān)注點(diǎn)分離的大型項(xiàng)目。
官方布局由Go的創(chuàng)建者認(rèn)可,強(qiáng)調(diào)簡潔和靈活性,適用于各種項(xiàng)目,特別是那些優(yōu)先考慮模塊管理的項(xiàng)目。
基于Clean Architecture原則的go-clean-template在需要將業(yè)務(wù)邏輯、數(shù)據(jù)訪問和接口層明確分離以提高可維護(hù)性和可測試性的項(xiàng)目中表現(xiàn)出色。
這三種范式適應(yīng)不同的項(xiàng)目需求,每種都提供了一套可自適應(yīng)和組合的指南。選擇其中之一取決于具體項(xiàng)目的要求、規(guī)模和復(fù)雜性。