Go整潔架構(gòu)模版,建議收藏

模板的作用 :
如何組織項目并防止它變成一坨意大利面條式的代碼。 在哪里存放業(yè)務(wù)邏輯,使其保持獨立,整潔和可擴展。 如何在微服務(wù)擴展時不失控
模版使用了 Robert Martin ( 也叫 Bob 叔叔 ) 的原則[1]。
Go-clean-template[2] 此倉庫由 Evrone[3] 創(chuàng)建及維護。
目錄內(nèi)容
快速開始 項目結(jié)構(gòu) 依賴注入 整潔架構(gòu)之道
快速開始
本地開發(fā)
# Postgres, RabbitMQ
$ make compose-up
# Run app with migrations
$ make run
集成測試 ( 可以在 CI 中運行 )
# DB, app + migrations, integration tests
$ make compose-up-integration-test
項目結(jié)構(gòu)
├── cmd
│ └── app
│ └── main.go
├── config
│ ├── config.go
│ └── config.yml
├── docs
│ ├── docs.go
│ ├── swagger.json
│ └── swagger.yaml
├── go.mod
├── go.sum
├── integration-test
│ ├── Dockerfile
│ └── integration_test.go
├── internal
│ ├── app
│ │ ├── app.go
│ │ └── migrate.go
│ ├── delivery
│ │ ├── amqp_rpc
│ │ │ ├── router.go
│ │ │ └── translation.go
│ │ └── http
│ │ └── v1
│ │ ├── error.go
│ │ ├── router.go
│ │ └── translation.go
│ ├── domain
│ │ └── translation.go
│ └── service
│ ├── interfaces.go
│ ├── repo
│ │ └── translation_postgres.go
│ ├── translation.go
│ └── webapi
│ └── translation_google.go
├── migrations
│ ├── 20210221023242_migrate_name.down.sql
│ └── 20210221023242_migrate_name.up.sql
└── pkg
├── httpserver
│ ├── options.go
│ └── server.go
├── logger
│ ├── interface.go
│ ├── logger.go
│ └── zap.go
├── postgres
│ ├── options.go
│ └── postgres.go
└── rabbitmq
└── rmq_rpc
├── client
│ ├── client.go
│ └── options.go
├── connection.go
├── errors.go
└── server
├── options.go
└── server.go
cmd/app/main.go
配置和日志實例的初始化,main 函數(shù)中調(diào)用internal/app/app.go 文件中 的 Run 函數(shù),main 函數(shù)將會在此 "延續(xù)"。
config
配置。首先讀取 config.yml,然后用環(huán)境變量覆蓋相匹配的 yaml 配置。配置的結(jié)構(gòu)體在 config.go 文件中。env-required: true 結(jié)構(gòu)體標簽強制您指定一個值 ( 在 yaml 或在環(huán)境變量中 )。
對于配置讀取,我們選擇 cleanenv[4] 庫。它在 GitHub 上沒有很多 star,但很簡單且滿足所有的需求。
從 yaml 中讀取配置違背了12 要素,但在實踐中,它比從環(huán)境變量中讀取整個配置更方便。假設(shè)默認值定義在 yaml 中,敏感的變量定義在環(huán)境變量中。
docs
Swagger 文檔。可以由 swag[5] 庫自動生成。而你不需要自己改正任何事情。
integration-test
集成測試。它們作為單獨的容器啟動,緊挨著應用程序容器。使用 go-hit[6] 測試 REST API 非常方便。
internal/app
在 app.go 文件中一般會有一個 Run 函數(shù),它“延續(xù)”了main函數(shù)。
這是創(chuàng)建所有主要對象的地方。依賴注入通過“ New...”構(gòu)造函數(shù) ( 參見依賴注入 ) 。這種技術(shù)允許我們使用依賴注入原則對應用程序進行分層,使得業(yè)務(wù)邏輯獨立于其他層。
接下來,為了優(yōu)雅的完成,我們啟動服務(wù)并在select中等待特定的信號。如果 app.go 代碼越來越多,可以將其拆分為多個文件。
對于大量的注入,可以使用 wire[7] 庫 ( wire 是一個代碼生成工具,它使用依賴注入自動連接組件)。
migrate.go 文件用于數(shù)據(jù)庫自動遷移。如果指定了 migrate 標簽的參數(shù),則會包含它。例如 :
$ go run -tags migrate ./cmd/app
internal/delivery
服務(wù)的handler層 ( MVC 控制器 )。模板展示了兩個服務(wù):
RPC ( RabbitMQ 用于傳遞消息 ) REST HTTP ( GIN 框架 )
服務(wù)的路由也以同樣的風格編寫 :
Handlers按照應用領(lǐng)域分組 ( 按公共基礎(chǔ) ) 對于每個組,都創(chuàng)建自己的路由結(jié)構(gòu),以及處理接口路徑的方法 業(yè)務(wù)邏輯的結(jié)構(gòu)被注入到路由結(jié)構(gòu)中,由handlers處理調(diào)用
internal/delivery/http
簡單的 REST 版本控制。對于 v2,我們需要添加具有相同內(nèi)容的 http/v2 文件夾。在 internal/app 程序文件中添加以下行 :
handler := gin.New()
v1.NewRouter(handler, translationService)
v2.NewRouter(handler, translationService)
你可以使用任何其他的 HTTP 框架甚至是標準的 net/http 庫來代替 Gin。
在 v1/router.go 和上面的 handler 方法中,有一些注釋是用 swag庫來生成 swagger 文檔的。
internal/domain
業(yè)務(wù)邏輯的實體 ( 模型 ) 可以在任何層中使用。也可以有方法,例如,用于驗證。
internal/service
業(yè)務(wù)邏輯
方法按應用領(lǐng)域分組 ( 在公共的基礎(chǔ)上 ) 每個組都有自己的結(jié)構(gòu) 一個文件一個結(jié)構(gòu)
Repositories、 webapi、 rpc 和其他業(yè)務(wù)邏輯結(jié)構(gòu)被注入到業(yè)務(wù)邏輯結(jié)構(gòu)中 ( 見依賴注入 )。
internal/service/repo
repository 是業(yè)務(wù)邏輯使用的抽象存儲 ( 數(shù)據(jù)庫 )。
internal/service/webapi
它是業(yè)務(wù)邏輯使用的抽象 web API。例如,它可能是業(yè)務(wù)邏輯通過 REST API 訪問的另一個微服務(wù)。包的名稱根據(jù)用途而變化。
pkg/rabbitmq
RabbitMQ RPC 模式 :
RabbitMQ 內(nèi)部沒有路由 使用Exchange fanout 廣播模式,將1 個獨立隊列綁定到其中,這是最高效的配置。 重新連接斷開丟失的連接
依賴注入
為了消除業(yè)務(wù)邏輯對外部包的依賴,使用了依賴注入。
例如,通過 NewService 構(gòu)造函數(shù),我們將依賴注入到業(yè)務(wù)邏輯的結(jié)構(gòu)中。這使得業(yè)務(wù)邏輯獨立 ( 便于移植 )。我們可以重寫接口的實現(xiàn),而不需要對 service 包進行更改。
package service
import (
// Nothing!
)
type Repository interface {
Get()
}
type Service struct {
repo Repository
}
func NewService(r Repository) *Service{
return &Service{r}
}
func (s *Service) Do() {
s.repo.Get()
}
它還允許我們自動生成模擬參數(shù) ( 例如使用 mockery[8] ) 和輕松地編寫單元測試。
我們可以不受特定實現(xiàn)的約束,來將一個組件更改為另一個組件。如果新組件實現(xiàn)了該接口,則業(yè)務(wù)邏輯中不需要進行任何更改。
整潔架構(gòu)之道
關(guān)鍵點
程序員在編寫了大量代碼后才意識到應用程序的最佳架構(gòu)。
一個好的架構(gòu)允許盡可能推遲決策。
主要原則
Dependency Inversion ( 與 SOLID 相同 ) 是依賴倒置的原則。依賴關(guān)系的方向是從外層到內(nèi)層。由于這個原因,業(yè)務(wù)邏輯和實體仍然獨立于系統(tǒng)的其他部分。
因此,應用程序分為內(nèi)部和外部兩個層次 :
業(yè)務(wù)邏輯 ( 使用 Go 標準庫 ) 工具 ( 數(shù)據(jù)庫、其他服務(wù)、消息代理、任何其他包和框架 )

業(yè)務(wù)邏輯的內(nèi)層應該是整潔的,它應該 :
沒有從外層導入的包 只使用標準庫的功能 通過接口調(diào)用外層 !
業(yè)務(wù)邏輯對 Postgres 或詳細的 web API 一無所知。業(yè)務(wù)邏輯應該具有一個用于處理抽象數(shù)據(jù)庫或抽象 web API 的接口。
外層還有其他限制 :
這一層的所有組成部分都不知道彼此的存在。如何從一個工具調(diào)用另一個工具?不是直接,而是只能通過內(nèi)層的業(yè)務(wù)邏輯來調(diào)用。 對內(nèi)層的所有調(diào)用都是通過接口來完成的 數(shù)據(jù)以便于業(yè)務(wù)邏輯的格式傳輸 ( internal/domain)
例如,你需要從 HTTP ( 控制器 ) 訪問數(shù)據(jù)庫。HTTP 和數(shù)據(jù)庫都在外層,這意味著它們對彼此一無所知。它們之間的通信是通過 service ( 業(yè)務(wù)邏輯 ) 進行的 :
HTTP > service
service > repository (Postgres)
service < repository (Postgres)
HTTP < service
符號 > 和 < 通過接口顯示層與層邊界的交集,如圖所示 :

或者更復雜的業(yè)務(wù)邏輯 :
HTTP > service
service > repository
service < repository
service > webapi
service < webapi
service > RPC
service < RPC
service > repository
service < repository
HTTP < service
層級

整潔架構(gòu)的術(shù)語
實體是業(yè)務(wù)邏輯操作的結(jié)構(gòu)。它們位于 internal/domain文件夾中。Domain 暗示我們堅持 DDD ( 領(lǐng)域驅(qū)動設(shè)計 ) 的原則,這在一定程度上是正確的。在 MVC 術(shù)語中,實體就是模型。用例是位于 internal/service中的業(yè)務(wù)邏輯。從整潔架構(gòu)的角度來看,調(diào)用業(yè)務(wù)邏輯使用service一詞不是習慣的用法,但是對于一個包名稱來說,使用一個單詞 ( service ) 比使用兩個單詞 ( use case ) 更方便。
業(yè)務(wù)邏輯直接交互的層通常稱為基礎(chǔ)設(shè)施層。它們可以是存儲庫 internal/service/repo、web API internal/service/webapi、任何pkg,以及其他微服務(wù)。在模板中,_ infrastructure 包位于 internal/service 中。
你可以根據(jù)需要去選擇如何調(diào)用入口點。選項如下 :
delivery (in our case) controllers transport gateways entrypoints primary input
附加層
經(jīng)典版本的 整潔架構(gòu)之道[9] 是為構(gòu)建大型單體應用程序而設(shè)計的,它有4層。
在最初的版本中,外層被分為兩個以上的層,兩層之間也存在相互依賴關(guān)系倒置 ( 定向內(nèi)部 ),并通過接口進行通信。
在邏輯復雜的情況下,內(nèi)層也分為兩個( 接口分離 )。
復雜的工具可以被劃分成更多的附加層,但你應該在確實需要時再添加層。
替代方法
除了整潔架構(gòu)之道,洋蔥架構(gòu)和六邊形架構(gòu) ( 端口適配器模式 ) 是類似的。兩者都是基于依賴倒置的原則。端口和適配器模式非常接近于整潔架構(gòu)之道,差異主要在術(shù)語上。
類似的項目
https://github.com/bxcodec/go-clean-arch https://github.com/zhashkevych/courses-backend
擴展閱讀鏈接
整潔架構(gòu)之道[10] 12 要素[11]
參考資料
原則: https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html
[2]Go-clean-template: https://evrone.com/go-clean-template?utm_source=github&utm_campaign=go-clean-template
[3]Evrone: https://evrone.com/?utm_source=github&utm_campaign=go-clean-template
[4]cleanenv: https://github.com/ilyakaznacheev/cleanenv
[5]swag: https://github.com/swaggo/swag
[6]go-hit: https://github.com/Eun/go-hit
[7]wire: https://github.com/google/wire
[8]mockery: https://github.com/vektra/mockery
[9]整潔架構(gòu)之道: https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html
[10]整潔架構(gòu)之道: https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html
[11]12 要素: https://12factor.net/ru/
推薦閱讀
