我使用Go和gRPC建立了一個微服務,並試圖找出最佳的程序結構,它能夠用做我將來程序的模板。 我有Java背景,並發現本身在Java和Go之間掙扎,它們之間的編程理念徹底不一樣。我寫了一系列關於在項目工做中作出的設計決策和取捨的文章。 這是其中的第一篇, 是關於程序結構的。html
Go的標準程序結構的最佳資源多是Github上的標準Go程序結構¹,但它不適合個人項目。在閱讀了Sylvain Wallez的文章²以後,我終於獲得了一些關於其背後緣由的信息。 Go起初是專爲API和網絡服務器而設計,Github上的大多數Go項目都是以庫的形式編寫的,所以「標準Go程序結構」可能很是適合。 商業微服務項目是一種徹底不一樣的動物,須要不一樣的程序結構。但我仍是採用了「標準Go程序結構」中適用的一些建議,這些建議約佔30%。java
通常來講,建立應用程序結構有兩種不一樣的方法:基於業務功能或基於技術結構。你們的共識³是基於業務功能的更好,對於單體項目(monolithic project)來講也確實如此。在微服務架構中,狀況發生了變化,由於每一個服務都有本身的源碼庫,這至關於已經把應用程序按業務功能查分紅了一個個的微服務。所以,在每一個微服務中,基於技術結構建立項目結構其實是可行的。git
我還找到了Ben Johnson關於應用程序結構⁴和包結構⁵的一些好建議。但它沒有爲個人項目提供完整的解決方案,因此我決定建立本身的程序結構。程序結構取決於項目要求,如下是需求。github
1.在數據持久層上支持不一樣的數據庫(Sql和NoSql數據庫)golang
2.使用不一樣的協議(如gRPC或REST)支持來自其餘微服務的數據sql
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.html)