清晰架構(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)”,這是業務邏輯層。

頂層程序結構:

file

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

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

config: 設置程序和配置數據,包括配置文件。

container: 應用程序依賴注入容器,負責創建具體類型並將它們注入每個函數。

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

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

script: 與設置應用程序相關的腳本,例如,數據庫腳本。

tool: 第三方工具。

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

用例和數據服務層功能全部由接口調用,因此可以輕松更改實現。

頂級包下的子文件包:

adapter:

file

當程序需要與微服務或其他外部服務進行交互時,你需要創建接口以減少依賴性。例如,本程序中的“緩存服務”是一個gRPC微服務。每個外部服務都有自己的子包和文件。例如,緩存服務具有“cacheclient”包和“cacheClient.go”文件,該文件定義了與外部“緩存”微服務交互的類型和方法。

在我們的示例中,與其他數據服務不同,“cacheClient.go”文件沒有定義緩存服務的接口。實際上它有一個,但是interface是在“dataservice”包中定義的,因為“緩存服務”也是一個數據服務。更明確的方法可能是在兩個包中各自創建一個接口,這將保持包結構的統一。但是這兩個接口將是相同且冗余的,所以我刪除了適配器中的接口。

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

cmd:

file

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

config:

file

在此保存所有應用配置文件。 “appConfig.go”負責從配置文件中讀取並數據將它們加載到應用程序配置結構中。 你可以為不同的環境提供不同的配置文件(YAML文件),例如“Dev”和“Prod”。

container:

file

本程序中最復雜的包,它為每個接口創建具體結構並將它們注入其他層。此包中的子包結構類似於應用邏輯分層,它還具有“用例(usecase)”,“數據服務(dataservice)”和“數據存儲(datastore)”層。

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

dataservice:

file

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

對於域模型中的每種類型,都有相應的數據服務。 例如,對於模型“User”,有一個“userdata”子包,它包含用戶持久性服務的所有實現,包括sqldb(MySql)和CouchDB。

model:

file

該層不需要接口,模型都是具體結構。

Script:

file

目前它只有MySql的數據庫腳本。

tool:

file

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

useCase:

file

頂級包下只有一個文件“useCase.go”,它包含所有用例接口。 目前,有三個用例:“RegistrationUseCase”,“ListUserUseCase”和“ListCourse”。

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

可能的問題:

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

file

如果你為其他人編寫一個外部庫,那么將代碼放入一個大包中是一個很好的規則,因為人們不需要多個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.html)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM