準(zhǔn)項(xiàng)目布局:目錄規(guī)范、適用場(chǎng)景與源碼級(jí)實(shí)踐指南)
基于 project-layout 的 Go 標(biāo)準(zhǔn)項(xiàng)目布局目錄規(guī)范、適用場(chǎng)景與源碼級(jí)實(shí)踐指南【免費(fèi)下載鏈接】project-layoutStandard Go Project Layout項(xiàng)目地址: https://gitcode.com/GitHub_Trending/pr/project-layout本文以本倉(cāng)庫(kù)的意大利語(yǔ)文檔 README_it.md 為主體系統(tǒng)講解 Go 社區(qū)通用的標(biāo)準(zhǔn)項(xiàng)目布局Standard Go Project Layout每個(gè)核心目錄cmd、internal、pkg、api、web、configs、scripts、deployments等的職責(zé)、命名規(guī)則與取舍依據(jù)。讀完之后你將能夠?yàn)樽约旱?Go 應(yīng)用挑選合適的目錄骨架、理解internal包的編譯器級(jí)強(qiáng)制機(jī)制并基于本倉(cāng)庫(kù)模板快速搭建一個(gè)結(jié)構(gòu)清晰的 Go 項(xiàng)目。一、這套布局的定位社區(qū)模式集合而非官方標(biāo)準(zhǔn)文檔開(kāi)宗明義地強(qiáng)調(diào)這套布局并不是 Go 核心團(tuán)隊(duì)定義的官方標(biāo)準(zhǔn)。它是 Go 生態(tài)中長(zhǎng)期沉淀下來(lái)的一組常見(jiàn)歷史與新興項(xiàng)目布局模式其中一些模式比其他模式更流行同時(shí)它也附帶了一些小改進(jìn)以及幾乎所有足夠大的真實(shí)世界應(yīng)用都會(huì)用到的若干支撐目錄。文檔還給出三個(gè)關(guān)鍵定位刻意保持通用性這套結(jié)構(gòu)不試圖強(qiáng)加某種特定的 Go 包結(jié)構(gòu)例如 Clean Architecture 之類的分層方式就不在此討論范圍內(nèi)社區(qū)協(xié)作成果如果你發(fā)現(xiàn)新的模式或者認(rèn)為某個(gè)現(xiàn)有模式需要更新應(yīng)當(dāng)通過(guò) issue 提出按需裁剪目錄存在不代表必須使用——連vendor模式也不是萬(wàn)能的。什么時(shí)候該用、什么時(shí)候不該用文檔用加粗語(yǔ)氣給出了一條最重要的建議如果你正在學(xué)習(xí) Go或者只是在開(kāi)發(fā) PoC概念驗(yàn)證或個(gè)人簡(jiǎn)單項(xiàng)目這套布局是不必要的復(fù)雜化。請(qǐng)從真正簡(jiǎn)單的結(jié)構(gòu)開(kāi)始——一個(gè)main.go文件和go.mod就足夠了。隨著項(xiàng)目演進(jìn)布局的重要性分階段上升項(xiàng)目增長(zhǎng)期要時(shí)刻保證代碼結(jié)構(gòu)良好否則會(huì)演變成一堆隱藏依賴 全局狀態(tài)的混亂代碼多人協(xié)作期需要更有結(jié)構(gòu)的布局并引入管理包/庫(kù)的通用方式開(kāi)源或被依賴期當(dāng)你的倉(cāng)庫(kù)是開(kāi)源項(xiàng)目、或有其他項(xiàng)目會(huì) import 你的代碼時(shí)就必須明確私有包與代碼即internal目錄的邊界。文檔給出的落地建議是克隆本倉(cāng)庫(kù)保留你需要的部分刪除其余一切??寺∶钊缦逻@是全文唯一涉及倉(cāng)庫(kù)地址的場(chǎng)景git clone https://gitcode.com/GitHub_Trending/pr/project-layoutGo Modules 前提從 Go 1.14 起不再被 $GOPATH 束縛從 Go 1.14 開(kāi)始Go Modules 正式達(dá)到生產(chǎn)可用。文檔建議除非你有特定理由不用否則一律使用 Go Modules——這樣你就不必再操心$GOPATH和項(xiàng)目放置位置的問(wèn)題。關(guān)于模塊路徑module path有一條容易被忽視的細(xì)節(jié)倉(cāng)庫(kù)自帶的 go.mod 文件內(nèi)容非常簡(jiǎn)潔// go.mod (倉(cāng)庫(kù)根目錄) module github.com/YOUR-USER-OR-ORG-NAME/YOUR-REPO-NAME go 1.19文檔說(shuō)明這份go.mod默認(rèn)假設(shè)你的項(xiàng)目托管在 GitHub 上但這并非強(qiáng)制要求——模塊路徑可以是任意值不過(guò)模塊路徑的第一段應(yīng)當(dāng)包含一個(gè)點(diǎn)域名形式。當(dāng)前版本的 Go 已不再?gòu)?qiáng)制這一點(diǎn)但如果你使用稍舊版本的 Go構(gòu)建失敗時(shí)不妨先懷疑這里文檔同時(shí)引用了 golang/go 的 issue #37554 與 #32819 供進(jìn)一步閱讀見(jiàn)官方倉(cāng)庫(kù)。命名、格式與風(fēng)格先跑工具再讀指南文檔建議遇到命名、格式、風(fēng)格問(wèn)題時(shí)先從運(yùn)行g(shù)ofmt開(kāi)始。需要說(shuō)明的是意大利語(yǔ)文檔中提到的標(biāo)準(zhǔn) linter 是golint而倉(cāng)庫(kù)中最新的英文主文檔 README.md 已更新為推薦staticcheck——因?yàn)?golint 現(xiàn)已棄停維護(hù)若以當(dāng)前倉(cāng)庫(kù)狀態(tài)為準(zhǔn)優(yōu)先使用 staticcheck 這類仍在維護(hù)的 linter。此外文檔列出了一批必讀的命名與風(fēng)格指南此處僅保留條目名稱不再附外部鏈接Go Naming Conventions 演講talks.golang.org2014Effective Go 的 Naming 章節(jié)《Package names in Go》官方博客CodeReviewComments wikirakyll/JBD 的《Go 包風(fēng)格指南》Package-Oriented Style以及關(guān)于包命名、包組織與代碼結(jié)構(gòu)建議的四場(chǎng)經(jīng)典演講GopherCon EU 2018 Peter Bourgon 的工業(yè)級(jí)編程最佳實(shí)踐、GopherCon Russia 2018 的 Go best practices、GopherCon 2017 Edward Muller 的 Go 反模式、GopherCon 2018 Kat Zien 的 Go 應(yīng)用結(jié)構(gòu)和一篇關(guān)于面向包的設(shè)計(jì)與架構(gòu)分層的中文文章。二、Go 目錄cmd、internal、pkg、vendor這是文檔中權(quán)重最高的四個(gè) Go 專屬目錄也是整套布局的核心骨架。/cmd本項(xiàng)目的主應(yīng)用程序/cmd存放本項(xiàng)目的主應(yīng)用可執(zhí)行程序。規(guī)則有四點(diǎn)每個(gè)應(yīng)用的目錄名應(yīng)與期望的可執(zhí)行文件名一致例如/cmd/myapp不要在應(yīng)用目錄里堆大量代碼如果代碼可能被其他項(xiàng)目 import 復(fù)用就放進(jìn)/pkg如果不可復(fù)用、或你明確不希望別人復(fù)用就放進(jìn)/internal——你無(wú)法預(yù)測(cè)別人會(huì)怎么 import 你的代碼所以要把意圖表達(dá)得足夠顯式最常見(jiàn)、也最推薦的做法是寫一個(gè)很小的main函數(shù)只做 import 并調(diào)用/internal與/pkg中的代碼別無(wú)其他子目錄說(shuō)明文檔 cmd/README.md 列出了采用該模式的主流項(xiàng)目包括 velero、moby、prometheus、influxdb、kubernetes、dapr、go-ethereum 等。本倉(cāng)庫(kù)中cmd/_your_app_/就是一個(gè)空占位目錄提示你按可執(zhí)行文件名創(chuàng)建子目錄。/internal編譯器強(qiáng)制的私有代碼/internal存放不希望他人 import的私有應(yīng)用與庫(kù)代碼。文檔強(qiáng)調(diào)兩個(gè)機(jī)制性事實(shí)該模式由 Go 編譯器本身強(qiáng)制只要包位于internal目錄下其他包就無(wú)法 import 它除非共享公共祖先路徑。這是自 Go 1.4 起的行為見(jiàn) Go 1.4 發(fā)布說(shuō)明中的 internal packages 一節(jié)internal不限于頂層你可以在項(xiàng)目樹(shù)的任意層級(jí)放置多個(gè)internal目錄。文檔還推薦了一個(gè)可選的二級(jí)結(jié)構(gòu)用于區(qū)分共享的內(nèi)部代碼與非共享的內(nèi)部代碼小項(xiàng)目不必做但能提供更清晰的包用途暗示實(shí)際應(yīng)用代碼放在/internal/app例如/internal/app/myapp這些應(yīng)用共享的內(nèi)部代碼放在/internal/pkg例如/internal/pkg/myprivlib。這一推薦結(jié)構(gòu)在本倉(cāng)庫(kù)中被真實(shí)落地為占位目錄internal/ ├── app/ │ └── _your_app_/ # 應(yīng)用私有代碼不共享 └── pkg/ └── _your_private_lib_/ # 應(yīng)用間共享的私有庫(kù)子目錄文檔 internal/README.md 進(jìn)一步列出了使用internal的知名項(xiàng)目hashicorp/terraform、influxdb、perkeep、jaeger、moby、minio 等其中 hashicorp/waypoint 展示了/internal/pkg的用法。/pkg明確可供外部使用的公開(kāi)庫(kù)/pkg存放允許外部應(yīng)用使用的庫(kù)代碼例如/pkg/mypubliclib。文檔對(duì)此目錄的態(tài)度可以概括為三層謹(jǐn)慎放入其他項(xiàng)目會(huì) import 這些庫(kù)并默認(rèn)它們能正常工作因此放任何東西進(jìn)去之前要想清楚internal才是硬保證真正從機(jī)制上阻止 import 的是internal目錄Go 編譯器強(qiáng)制而/pkg的價(jià)值在于**顯式地對(duì)外傳達(dá)這里的代碼可以被安全使用**這一契約。社區(qū)作者 Travis Jeffery 的博文《Ill take pkg over internal》對(duì)pkg與internal的取舍給出了很好的綜述見(jiàn)子目錄文檔 pkg/README.md 的引用它也是工程組織手段當(dāng)項(xiàng)目根目錄混雜大量非 Go 組件時(shí)把 Go 代碼統(tǒng)一收攏到/pkg下可以讓各種 Go 工具更好用——這一點(diǎn)在 GopherCon EU 2018Peter Bourgon、GopherCon 2018Kat Zien與 GoLab 2018Massimiliano Pippi三場(chǎng)演講中均有論述。文檔同時(shí)坦誠(chéng)了社區(qū)爭(zhēng)議pkg是一個(gè)常見(jiàn)但并非普遍接受的模式Go 社區(qū)中有人并不推薦它如果你的項(xiàng)目很小、多一層嵌套沒(méi)有價(jià)值完全可以不用。pkg/README.md 附有一長(zhǎng)串使用pkg布局的知名倉(cāng)庫(kù)清單containerd、istio、helm、k3s、kubernetes、moby、grafana、cockroach、etcd、linkerd、spire 等可作為采用與否的參考樣本。最后文檔交代了pkg目錄的歷史起源早期 Go 官方源碼樹(shù)曾用pkg目錄存放其包社區(qū)項(xiàng)目隨之模仿了這一模式Brad Fitzpatrick 的相關(guān)推文提供了更多背景。/vendor應(yīng)用依賴/vendor存放應(yīng)用依賴——可以手工管理也可以用你偏好的依賴管理工具比如內(nèi)置的 Go Modules執(zhí)行g(shù)o mod vendor命令即可自動(dòng)生成/vendor目錄注意如果你沒(méi)有使用 Go 1.14該版本起默認(rèn)啟用 vendor 模式可能需要在go build命令上追加-modvendor標(biāo)志構(gòu)建庫(kù)時(shí)不要提交你的應(yīng)用依賴自 Go 1.13 起Go 啟用了 module proxy 特性默認(rèn)使用官方代理服務(wù)器 proxy.golang.org。如果你的需求與約束能被它滿足就完全不需要vendor目錄。本倉(cāng)庫(kù)當(dāng)前并未包含vendor目錄與依賴交給模塊代理/go mod vendor生成的定位一致。三、服務(wù)應(yīng)用目錄/api/api存放 OpenAPI/Swagger 規(guī)范、JSON schema 文件與協(xié)議定義文件。這是一個(gè)面向服務(wù)化應(yīng)用的目錄接口契約與實(shí)現(xiàn)代碼分離便于生成客戶端或文檔。子目錄文檔 api/README.md 給出的參考項(xiàng)目是 kubernetes 與 moby 的api目錄。四、Web 應(yīng)用目錄/web/web存放 Web 應(yīng)用專屬組件靜態(tài) Web 資源、服務(wù)端模板與 SPA單頁(yè)應(yīng)用。從本倉(cāng)庫(kù)的實(shí)際結(jié)構(gòu)看該目錄已被細(xì)分為三個(gè)占位子目錄給出了推薦的組織方式web/ ├── app/ # 前端應(yīng)用如 SPA 源碼 ├── static/ # 靜態(tài)資源 └── template/ # 服務(wù)端模板對(duì)應(yīng)的目錄說(shuō)明見(jiàn) web/README.md。五、通用應(yīng)用目錄configs、init、scripts、build、deployments、test/configs配置模板與默認(rèn)配置存放配置文件模板或默認(rèn)配置例如confd或consul-template的模板文件也應(yīng)放在這里。說(shuō)明見(jiàn) configs/README.md。/init系統(tǒng)初始化與進(jìn)程管理存放系統(tǒng)初始化systemd、upstart、sysv與進(jìn)程管理器/守護(hù)器runit、supervisord的配置。說(shuō)明見(jiàn) init/README.md。/scripts構(gòu)建與運(yùn)維腳本存放執(zhí)行各類構(gòu)建、安裝、分析等操作的腳本。文檔指出這類腳本的核心作用是讓根級(jí) Makefile 保持小巧、直接以 hashicorp/terraform 的 Makefile 為范例。這一點(diǎn)在本倉(cāng)庫(kù)中被貫徹到了極致——倉(cāng)庫(kù)根目錄的 Makefile 全文只有一行注釋# note: call scripts from /scripts即所有實(shí)際邏輯都被推給了/scripts下的腳本根 Makefile 僅作為入口提示。這正示范了文檔所說(shuō)的根 Makefile 小而簡(jiǎn)單原則。更多示例見(jiàn) scripts/README.md。/build打包與持續(xù)集成/build承擔(dān)打包Packaging與 CI 兩類職責(zé)文檔建議拆成兩個(gè)子目錄/build/package放置云鏡像AMI、容器Docker、操作系統(tǒng)包deb、rpm、pkg的打包配置與腳本/build/ci放置 CItravis、circle、drone的配置與腳本。文檔特別提醒某些 CI 工具如 Travis CI對(duì)配置文件位置要求非常嚴(yán)格可以把配置放在/build/ci下再通過(guò)鏈接link指到 CI 工具期望的路徑在可行的情況下。/deployments部署配置與模板存放 IaaS、PaaS、系統(tǒng)級(jí)以及容器編排的部署配置與模板docker-compose、kubernetes/helm、mesos、terraform、bosh 等。文檔注意到一個(gè)命名差異在部分倉(cāng)庫(kù)中尤其是用 kubernetes 部署的應(yīng)用這個(gè)目錄叫/deploy。本倉(cāng)庫(kù)采用了deployments/命名說(shuō)明見(jiàn) deployments/README.md。/test外部測(cè)試應(yīng)用與測(cè)試數(shù)據(jù)/test存放額外的外部測(cè)試應(yīng)用與測(cè)試數(shù)據(jù)內(nèi)部結(jié)構(gòu)可自由組織。文檔給出兩條與 Go 工具鏈直接相關(guān)的實(shí)用規(guī)則大型項(xiàng)目建議設(shè)一個(gè)數(shù)據(jù)子目錄如/test/data若需要 Go 工具鏈忽略目錄內(nèi)容應(yīng)命名為/test/testdataGo 的構(gòu)建/測(cè)試工具會(huì)自動(dòng)跳過(guò)testdata目錄由于Go 同樣會(huì)忽略以.或_開(kāi)頭的目錄和文件測(cè)試數(shù)據(jù)目錄的命名還有更大自由度——這也解釋了本倉(cāng)庫(kù)為何大量使用_your_app_、_your_private_lib_這類下劃線前綴占位目錄它們既是模板占位符又天然被 Go 工具忽略。示例見(jiàn) test/README.md。六、其他目錄docs、tools、examples、third_party、githooks、assets、website目錄職責(zé)補(bǔ)充說(shuō)明/docs用戶文檔與設(shè)計(jì)文檔補(bǔ)充在自動(dòng)生成的 godoc 文檔之外docs/README.md/tools項(xiàng)目的支撐工具注意這些工具可以 import/pkg與/internal中的代碼tools/README.md/examples應(yīng)用與/或公開(kāi)庫(kù)的使用示例examples/README.md/third_party外部輔助工具、fork 的代碼、其他第三方工具如 Swagger UIthird_party/README.md/githooksGit hooksgithooks/README.md/assets倉(cāng)庫(kù)附帶的其他資源圖片、logo 等assets/README.md/website如果不使用 GitHub pages項(xiàng)目網(wǎng)站數(shù)據(jù)放在這里website/README.md值得注意的是/tools的 import 規(guī)則工具既依賴項(xiàng)目代碼可 importpkg/internal又不應(yīng)被主程序反向依賴——這為腳手架/代碼生成器/內(nèi)部 CLI類工具劃定了一個(gè)安全的存放位置。七、不該出現(xiàn)的目錄/src文檔專設(shè)一節(jié)警告不要在 Go 項(xiàng)目中使用/src目錄。理由分兩層來(lái)源判斷Go 項(xiàng)目出現(xiàn)src目錄通常是因?yàn)殚_(kāi)發(fā)者來(lái)自 Java 世界Java 中src是常見(jiàn)模式。文檔直接建議盡量別把 Go 項(xiàng)目做得像 Java 項(xiàng)目避免與 Go workspace 的/src混淆$GOPATH環(huán)境變量指向當(dāng)前的工作區(qū)非 Windows 系統(tǒng)上默認(rèn)是$HOME/go該工作區(qū)包含頂層的/pkg、/bin與/src三個(gè)目錄你的實(shí)際項(xiàng)目會(huì)落在工作區(qū)的/src之下。如果項(xiàng)目自身再嵌套一個(gè)/src路徑就會(huì)變成/some/path/to/workspace/src/your_project/src/your_code.go這種雙層src。文檔最后補(bǔ)充雖然自 Go 1.11 起項(xiàng)目可以放在GOPATH之外但這并不意味著/src布局就是好主意。八、倉(cāng)庫(kù)骨架總覽一個(gè)可裁剪的純模板結(jié)合上文對(duì)文檔各目錄的解讀再看本倉(cāng)庫(kù)的實(shí)際骨架可以完整對(duì)照出這套布局的全貌倉(cāng)庫(kù)內(nèi)不含任何.go源碼文件是一個(gè)純目錄模板配合 LICENSE.md 與上文所示的 go.mod、Makefile. ├── api/ # OpenAPI/Swagger/JSON schema/協(xié)議定義 ├── assets/ # 圖片、logo 等倉(cāng)庫(kù)資源 ├── cmd/ │ └── _your_app_/ # 主應(yīng)用目錄名可執(zhí)行文件名 ├── configs/ # 配置模板/默認(rèn)配置含 confd、consul-template ├── deployments/ # IaaS/PaaS/編排部署docker-compose、k8s/helm、terraform… ├── docs/ # 用戶與設(shè)計(jì)文檔 ├── examples/ # 應(yīng)用/公開(kāi)庫(kù)示例 ├── githooks/ # Git hooks ├── init/ # systemd/upstart/sysv、runit、supervisord ├── internal/ │ ├── app/_your_app_/ # 應(yīng)用私有代碼 │ └── pkg/_your_private_lib_/ # 應(yīng)用間共享私有庫(kù) ├── pkg/ │ └── _your_public_lib_/ # 可供外部 import 的公開(kāi)庫(kù) ├── scripts/ # 構(gòu)建/安裝/分析腳本根 Makefile 保持精簡(jiǎn) ├── test/ # 外部測(cè)試應(yīng)用與測(cè)試數(shù)據(jù)testdata 可被 Go 忽略 ├── third_party/ # 外部工具、fork 代碼 ├── tools/ # 支撐工具可 import pkg 與 internal ├── web/ │ ├── app/ # SPA/前端應(yīng)用 │ ├── static/ # 靜態(tài)資源 │ └── template/ # 服務(wù)端模板 ├── website/ # 項(xiàng)目網(wǎng)站數(shù)據(jù)非 GitHub pages 時(shí) ├── go.mod # module 路徑為占位符需替換為你自己的 ├── Makefile # 僅一行call scripts from /scripts └── LICENSE.md使用流程與文檔的結(jié)論一致克隆倉(cāng)庫(kù) → 保留所需目錄、刪除其余 → 替換 go.mod 中的模塊路徑占位符github.com/YOUR-USER-OR-ORG-NAME/YOUR-REPO-NAME→ 把cmd、internal、pkg下帶下劃線前綴的占位目錄重命名為真實(shí)名稱。下劃線前綴目錄同時(shí)享受 Go 工具鏈自動(dòng)忽略的便利。九、Badges倉(cāng)庫(kù)文檔面板文檔最后給出了 README badge 的推薦用法此處按當(dāng)前倉(cāng)庫(kù)內(nèi)容說(shuō)明用途不附外鏈Go Report Card會(huì)用gofmt、go vet、gocyclo、golint、ineffassign、license、misspell掃描你的代碼把示例中的模塊引用替換為你的項(xiàng)目即可Pkg.go.devGo 包發(fā)現(xiàn)與文檔的新入口可用其 badge 生成工具為項(xiàng)目創(chuàng)建 badgeRelease badge展示項(xiàng)目的最新 release 版本號(hào)把鏈接指向你的項(xiàng)目即可。十、小結(jié)這套布局的完整心法可以壓縮為三句話cmd薄、internal硬、pkg慎——可執(zhí)行入口保持極小私有性交給編譯器強(qiáng)制的internal公開(kāi) API 才進(jìn)pkg并視為對(duì)外承諾非 Go 組件各歸其位——api、web、configs、init、deployments、scripts、test、docs、website等目錄讓根目錄始終可讀按需裁剪——學(xué)習(xí)期只用main.gogo.mod項(xiàng)目長(zhǎng)大、多人協(xié)作、對(duì)外發(fā)布三個(gè)階段逐級(jí)加結(jié)構(gòu)目錄在模板中存在不等于你必須使用它。文檔末尾的 Notes 還提到一個(gè)包含簡(jiǎn)單可復(fù)用配置、腳本與代碼的更有主見(jiàn)的標(biāo)準(zhǔn)項(xiàng)目模板仍在進(jìn)行中WIP可作為后續(xù)關(guān)注方向?!久赓M(fèi)下載鏈接】project-layoutStandard Go Project Layout項(xiàng)目地址: https://gitcode.com/GitHub_Trending/pr/project-layout創(chuàng)作聲明:本文部分內(nèi)容由AI輔助生成(AIGC),僅供參考