DAY7 — 奔放的 Golang,卻隱藏著有紀律的架構! — Clean Architecture 實作篇
被選召的 Gopher 們,從零開始探索 Golang, Istio, K8s 數碼微服務世界 — 第12屆iT邦幫忙鐵人賽
本文章同時發佈於:
文章為自己的經驗與夥伴整理的內容,設計沒有標準答案,如有可以改進的地方,請告訴我,我會盡我所能的修改,謝謝大家~
大家好,繼昨天DAY06的介紹後,相信大家已經對 Clean Architecture 的稍有概念了,接下來將介紹實作的部分,相信會讓各位更理解 Clean Architecture 的好處。
不過,你也可以先進入 DAY07 的資料夾底下把 Server 運行起來,這樣會比較有感覺。
透過swagger-generator來產生 Server 介面
進到DAY07的資料夾,使用 docker 運行以下指令:
swagger-generator 會自動產生以下的 code:
接下來我們將一步一步實作成以下的 code:
來實作吧!
請配合此兩張圖與並依照Github 範例一步一步實作,
Domain 層 — 規範一切的老大哥
如DAY06所說,
我們需要一個
interface
來告訴每個 call 的程式他們到底在 call 什麼
所以必須建立建立 diet(培育)與 digimon(數碼獸)兩個 interface 在 domain 層。
以digimon.go
來說,
講解1 - struct
: 定義了數碼獸會有哪些的屬性,當程式裡面創建了數碼獸,就必定這些屬性都要擁有。講解2 - repository interface
: 定義了 repository 層的各種方法,我們必須要按照這個定義實作,不然就會爆炸,呼叫的程式只能呼叫這些定義好的方法,不然也會爆炸 XD。講解3 - usecase interface
: 與講解-2
相同都是定義有哪些方法,不過他是定義 usecase 這個業務邏輯層。
Repository 層 — 任何外部資料都我來管
定義好了 domain 層,就可以依照 domain 來設計 repository 層,
可以看到雖然 diet 與 digimon 目前都是用 PostgreSQL 來實作,但是我們都將 repository 獨立拉出來,以便將不同的 DB 功能做區分。
以下為核心部分:
講解1 - 定義好需要哪些依賴注入(DI)
: 這是 Clean Architecture 的核心之一,如DAY06所述,將依賴的事物由外部注入,而不是寫死在裡頭
。講解2 - 設計一個DI注入的function
: 有了講解1
的定義,我們還需要一個注入的 function,將 db確實
注入。為什麼要這樣呢?因為你是可以這樣產生實例的postgresqlDigimonRepository{}
,可以發現其實沒有帶 db,但實際上還是可以運行。我們可以透過此 function 來避免這個情形。講解3 - 透過domain裡定義的interface來約束回傳值
:domain.DigimonRepository
這個 interface 定義了有哪些方法postgresqlDigimonRepository
必須要實作,如DAY06所述,有了此 interface 才能讓呼叫的程式在還沒run起來
就知道哪些呼叫方法存在
。講解4 - 實作domain.DigimonRepository interface定義的方法
:GetByID
要確實符合 interface 定義的方法,不然 Golang 在運行前就會報錯。
Usecase 層 — 業務邏輯的管轄處
講解1 - 定義依賴注入(DI)所需的Repository層
: 注入了之後我們再對 Repository 層做各種邏輯的操作,要注意的是,雖然目前只有注入所屬 PostgreSQL 的 repository,但如果 digimon 有很多不同的來源,比如說 MongoDB、Microservice 等等,我們可以注入更多 repository 來操作。講解2 - 設計確實注入的function
: 與 repository 層一樣,需要一個 function 來要求要注入哪些 repository。講解3 - 依照domain.DigimonRepository interface來實作
: 與 repository 層一樣,要以 interface 規範來實作,不過這裡是實作業務邏輯
。
Delivery 層 — 交付業務邏輯給引擎的跑腿工
看到這邊大家應該已經發現,Clean Architecture 的重點就是:
- 定義好個層介面
- 依賴注入注入再注入
- 利用各種注入的實體來實作
而 delivery 層就是在注入 usecase 層的實體,
講解1 - 定義依賴注入(DI)所需的Usecase層
: 如 usecase 層,值得注意的是,由於 digimon 相關的 Restful API 除了有用到 DigimonUsecase,還有用到 DietUsecase,所以必須都注入進來,並沒有規定只能注入digimon相關的usecase
。講解2 - 將Server引擎丟進來並且設定
: 這裡比較特別,是透過把 Golang-Gin 的 Server 引擎傳進 function 內,再把各個 handler 與 route 交付(delivery)綁定。講解3 - 透過
swagger-generator來解析各個HTTP傳遞
: 這裡終於要用到swagger-generator的好處,可以看到不論是輸入處講解3-1
、回傳錯誤處講解3-2
、成功回傳處講解3-3
都可以透過swagger.定義好的介面
來解析。這樣就省去了很多定義時間,並且也減少了很多定義不小心寫錯的可能性!
最後,把一切透過 cmd/main.go
跑起來吧!
寫了那麼多依賴注入(DI),最後我們需要再main.go
真的將這些實體注入,
講解1 - 將config從外部依賴注入(DI)
: 我們統一由 main.go 來讀取 config,因為這個 config 是外部
的,不該直接寫在各層裡頭,不然我們很難知道此層到底用了哪些 config,以避免:「嗯嗯?!怎麼爆炸了(查了 3 個小時後),喔 damn 這層原來有要用這個 config 喔 Q 口 Q!」的狀況。講解2 - 初始化DB
: 凡外部
依賴的東西,都在 main.go 中完成,不該在各層中完成,以避免不知道誰已經初始化了誰還沒。講解3 - 將外部依賴透過依賴注入(DI)來注入Repository層
: 也是因為外部
的關係,我們也該將 DB 從外部注入,我們可以更可視此 DB 被哪些 repository 層使用了。除了 DB 以外其他 Microservice 的 caller、NoSQL 都該由外部注入,以便可視。講解4 - 將Repository層透過依賴注入(DI)來注入Usecase層
: 同講解3
,對 usecase 層來說 repository 層也是外部依賴,注入下去!講解5 - 將Usecase層透過依賴注入(DI)來注入Delivery層
: 這裡要注意,除了注入以外,還有將 Golang-Gin 的引擎丟入,讓 delivery 層來綁定。講解 6 - 將Golang-Gin跑起來!
進入到我們 DAY07 資料夾底下,使用 docker-compose 把 Golang-Server 與 DB 跑起來吧!
Work!
透過Insomnia Designer測試一下
創建數碼蛋,
查看數碼蛋狀態,
培育數碼獸,
如果你有安裝Postico,可以去看看 diets table 是不是真的有吃了食物,
嗯!滿滿的蘋果,看我還不飽炸你,亞古獸 XD ~
參考
Write Medium in Markdown? Try Markdium!