1. 程式人生 > >清晰架構(Clean Architecture)的Go微服務: 程式結構

清晰架構(Clean Architecture)的Go微服務: 程式結構

我使用Go和gRPC建立了一個微服務,並試圖找出最佳的程式結構,它可以用作我未來程式的模板。 我有Java背景,並發現自己在Java和Go之間掙扎,它們之間的程式設計理念完全不同。我寫了一系列關於在專案工作中做出的設計決策和取捨的文章。 這是其中的第一篇, 是關於程式結構的。

程式結構的資源

Go的標準程式結構的最佳資源可能是Github上的標準Go程式結構¹,但它不適合我的專案。在閱讀了Sylvain Wallez的文章²之後,我終於得到了一些關於其背後原因的資訊。 Go起初是專為API和網路伺服器而設計,Github上的大多數Go專案都是以庫的形式編寫的,因此“標準Go程式結構”可能非常適合。 商業微服務專案是一種完全不同的動物,需要不同的程式結構。但我還是採用了“標準Go程式結構”中適用的一些建議,這些建議約佔30%。

一般來說,建立應用程式結構有兩種不同的方法:基於業務功能或基於技術結構。大家的共識³是基於業務功能的更好,對於單體專案(monolithic project)來說也確實如此。在微服務架構中,情況發生了變化,因為每個服務都有自己的原始碼庫,這相當於已經把應用程式按業務功能查分成了一個個的微服務。因此,在每個微服務中,基於技術結構建立專案結構實際上是可行的。

我還找到了Ben Johnson關於應用程式結構⁴和包結構⁵的一些好建議。但它沒有為我的專案提供完整的解決方案,所以我決定建立自己的程式結構。程式結構取決於專案要求,以下是需求。

專案需求:

1.在資料持久層上支援不同的資料庫(Sql和NoSql資料庫)

2.使用不同的協議(如gRPC或REST)支援來自其他微服務的資料

3.鬆散耦合和高度內聚

4.支援簡單一致的日誌記錄,並能夠更改它(例如,日誌記錄級別和日誌記錄庫),而無需修改程式中的日誌記錄語句。

5.支援業務級別的事物交易。

程式結構也受到程式設計的影響。 我採用了 Bob Martin的清晰架構(Clean Architecture)⁶ 和 Go的 簡潔⁷ 設計風格.

在業務邏輯方面,有三層:“模型(model)”,即域模型; “資料服務(dataservice)”,它是資料永續性(資料庫)層; “用例(usecase)”,這是業務邏輯層。

頂層程式結構:

adapter: 這是應用程式和外部資料服務之間的介面,例如另一個gRPC服務。 所有資料轉換都發生在這裡,這樣你的業務邏輯程式碼不需要了解外部服務的具體實現(無論是gRPC還是REST)。

cmd: 命令。 所有不同型別的“main.go”都在這裡,你可以有多個。 這是應用程式的起點。

config: 設定程式和配置資料,包括配置檔案。

container: 應用程式依賴注入容器,負責建立具體型別並將它們注入每個函式。

dataservice: 持久層,負責檢索和修改域模型的資料。 它只依賴(depend)於模型(model)層。 資料服務可以通過RPC或RESTFul呼叫從資料庫或其他微服務獲取資料。

model: 域模型層,具有域結構(model struct)。 所有其他層依賴於此層,而此層不依賴於任何其他層。

script: 與設定應用程式相關的指令碼,例如,資料庫指令碼。

tool: 第三方工具。

usecase : 這是一個重要的層並且是業務邏輯的切入點。 每個業務功能都由用例實現。 它是程式頂層,因此沒有其他層依賴於它(“cmd”除外),但它依賴於其他層。

用例和資料服務層功能全部由介面呼叫,因此可以輕鬆更改實現。

頂級包下的子檔案包:

adapter:

當程式需要與微服務或其他外部服務進行互動時,你需要建立介面以減少依賴性。例如,本程式中的“快取服務”是一個gRPC微服務。每個外部服務都有自己的子包和檔案。例如,快取服務具有“cacheclient”包和“cacheClient.go”檔案,該檔案定義了與外部“快取”微服務互動的型別和方法。

在我們的示例中,與其他資料服務不同,“cacheClient.go”檔案沒有定義快取服務的介面。實際上它有一個,但是interface是在“dataservice”包中定義的,因為“快取服務”也是一個數據服務。更明確的方法可能是在兩個包中各自建立一個介面,這將保持包結構的統一。但是這兩個介面將是相同且冗餘的,所以我刪除了介面卡中的介面。

由於我們還需要將應用程式本身釋出為gRPC服務,因此需要在此處建立“userclient”子包。 “generatedclient”子包是為gRPC和Protobuf生成的程式碼。“userGrpc.go”用來處理域模型結構和gRPC結構之間的格式轉換。

cmd:

應用程式的命令,是整個程式的起點。 你可以將應用程式作為本地應用程式執行,也可以將其作為微服務應用程式執行,在這種情況下,你同時擁有客戶端(grpcClientMain.go)和伺服器端(grpcServerMain.go)主檔案。 所有未來的主檔案(main.go)也將在此處,例如,Web應用程式伺服器主檔案。

config:

在此儲存所有應用配置檔案。 “appConfig.go”負責從配置檔案中讀取並資料將它們載入到應用程式配置結構中。 你可以為不同的環境提供不同的配置檔案(YAML檔案),例如“Dev”和“Prod”。

container:

本程式中最複雜的包,它為每個介面建立具體結構並將它們注入其他層。此包中的子包結構類似於應用邏輯分層,它還具有“用例(usecase)”,“資料服務(dataservice)”和“資料儲存(datastore)”層。

在本包頂層,“container.go”定義容器介面,它的實現檔案“serviceContainer.go”在“servicecontainer”子包中。它是此包的入口點,它將此包中的其他程式碼粘合在一起。 “usecasefactory”子包中的“registrationFactory.go”和其他工廠(factory)使用工廠方法模式(factory method pattern)建立具體結構(struct)。 日誌⁸不屬於任何應用層,因此我為它建立了一個單獨的子包“loggerfactory”。它還有一個“logger”子包來定義日誌記錄介面。我在文章程式容器9中解釋了這個包中的所有內容。

dataservice:

域模型的持久層。 頂層只有一個檔案 - “dataService.go”,它具有資料服務的所有介面,包括其他微服務提供的資料服務(例如gRPC)。 其他包只需要依賴於頂級資料服務介面,而不需要了解特定資料庫的實現細節。

對於域模型中的每種型別,都有相應的資料服務。 例如,對於模型“User”,有一個“userdata”子包,它包含使用者永續性服務的所有實現,包括sqldb(MySql)和CouchDB。

model:

該層不需要介面,模型都是具體結構。

Script:

目前它只有MySql的資料庫指令碼。

tool:

此程式包適用於第三方工具。 如果你不想直接依賴第三方庫,或者需要增強這些第三方庫,請在此處進行封裝。 不要與“adapter”包混淆,後者也處理第三方庫,但只適用於應用程式級資料服務。 “tool”包更適用於較低級別的庫。

useCase:

頂級包下只有一個檔案“useCase.go”,它包含所有用例介面。 目前,有三個用例:“RegistrationUseCase”,“ListUserUseCase”和“ListCourse”。

每個用例都有一個子包。 該子包中的檔案定義了實現用例介面的具體結構。 所有其他包僅依賴於頂級的用例介面,不需要了解每個用例的實現細節。

可能的問題:

這個程式結構最終會產生很多小的子包,每個子包只有一個或幾個檔案。 這與Go習慣用法“考慮更少,更大的包¹⁰相矛盾. 以下才是習慣用法應建立的包:

如果你為其他人編寫一個外部庫,那麼將程式碼放入一個大包中是一個很好的規則,因為人們不需要多個import語句來使用你的庫。 但是在你自己的應用程式中,擁有小包是可以的,特別是當你只將介面暴露給其他層時。

本程式為什要用小包呢? 首先“useCase.go”只定義介面,而其他包(容器除外)僅依賴於介面,因此“useCase.go”需要一個獨立的包。 其次,用資料夾分隔每個用例使程式更清晰易讀。

結論:

對於gRPC微服務專案,“標準Go程式結構”可能不太適合。 基於業務邏輯而不是技術結構建立應用程式結構對單體專案是一個很好的建議,不一定適合微服務專案。 本文使用一個真實的應用程式作為示例來展示什麼是一個很好的微服務應用程式結構及其背後的原因。

源程式:

完整的源程式連結 github: https://github.com/jfeng45/servicetmpl

索引:

[1][golang-standards/project-layout]
(https://github.com/golang-standards/project-layout)

[2][Go: the Good, the Bad and the Ugly]
(https://bluxte.net/musings/2018/04/10/go-good-bad-ugly/)

[3][Package by feature, not layer]
(http://www.javapractices.com/topic/TopicAction.do?Id=205)

[4][Structuring Applications in Go]
(https://medium.com/@benbjohnson/structuring-applications-in-go-3b04be4ff091)

[5][Standard Package Layout]
(https://medium.com/@benbjohnson/standard-package-layout-7cdbc8391fc1)

[6][The Clean Code Blog]
(https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html)

[7][Go at Google: Language Design in the Service of Software Engineering]
(https://talks.golang.org/2012/splash.article)

[8][Go Microservice with Clean Architecture: Application Logging]
(https://jfeng45.github.io/posts/go_logging_and_error_handling/)

[9][Go Microservice with Clean Architecture: Application Container]
(https://jfeng45.github.io/posts/application_container/)

[10][Practical Go: Real world advice for writing maintainable Go programs]
(https://dave.cheney.net/practical-go/presentations/qcon-china.ht