近期因為項目架構升級原因,筆者著手調研一些go項目monorepo的代碼架構設計,目標是長期把既有微服務項目重要的部分都轉移到monorepo上面,讓代碼更容易維護,協作開發更加方便。雖然經驗不多,但既然有了初步的調研,今天就分享一下筆者所面臨場景的monorepo設計思路。
從語言特性上講,Golang是非常適合做monorepo的,但根據不同項目研發需要,monorepo的目錄結構可以定制成不同的形式。所以要做monorepo,首先要回答的問題是,做這個事情主要解決什么研發問題,優化了什么東西,否則投入量過多,ROI就會很低。
筆者所處的團隊偏向于中臺角色,每個同學都負責一個專項,不同專項之間可能共享很多三方能力的實現,并且團隊距離業務比較近,研發更注重短平快,所以既往的代碼實現質量方面,并不是非常完美。從這個角度來看,如果用monorepo做代碼架構升級,主要能解決的問題,一是更精簡的代碼和模塊化抽象,二是減少三方能力的冗余實現,三是通過一套研發實踐規范代碼管理,這樣就能夠在保證代碼易擴展和模塊易兼容的基礎上,各個成員能夠盡可能自由發揮,代碼質量也不會輕易下降,這樣長期就能讓整個團隊的技術迭代更加效率,服務實現的穩定性會更高。
然后就是整個代碼架構長什么樣子。筆者的項目,大概是這樣設計:
- app:所有服務各自的代碼- ${service_group}:按架構劃分的服務組(模塊)- ${service_name}:單個服務的代碼(http/rpc等)- biz:業務邏輯- handler- middleware- service- conf:靜態配置- main.go- build.sh:每個服務的編譯
- lib:所有服務公有的代碼- client:三方能力的client實現- common:枚舉定義- model:數據結構(from idl)- dal:數據訪問(gorm自動生成)- util:工具函數
- idl:協議
- script:腳本(編譯/本地開發)
- Makefile
- go.mod
- go.sum
- README.md
由于很多服務需要共享數據結構,所以在設計上,idl、model做了單獨提出,被所有服務可以引用。開發約定上,通過在script、Makefile做一些腳本,就可以自動指定某服務創建/更新項目代碼,也可以隨時自動生成/更新協議結構和gorm定義。
通過這樣一個架構,首先代碼精簡和三方能力抽象都可以解決。然后,由于各類腳本的存在,每個業務專項的研發同學就不需要把過多精力關注在代碼底層架構上,可以快速適應整個項目開發。就算長期有需要單獨提出的能力,代碼也可以很容易遷移到lib模塊。這樣整體上看,整個團隊的代碼質量在未來都能夠有質的提升。
當然,從現狀來看,這個項目結構未來可能會遇到的問題是,如果存量大服務的研發過程要遷移到這個monorepo,可能會踩什么坑,比如go-mod要處理兼容性問題,服務編譯部署配置需要重新調整,研發腳本需要做額外的兼容,很多模塊需要未來打散挪到lib等等。
上述這些基本上是小問題,大的問題是怎么在遷移項目的過程中,不影響既有的業務迭代。排除需要周末加班的的情況,筆者認為一個合理的解決方法是,在遷移前抽一些時間間隙,先找一些小的存量項目去做試點遷移,踩坑,整理最佳實踐,然后再嘗試遷移大項目,這樣就不至于一下遷移遇到問題就阻塞很多。遷移的話,也是先把所有代碼挪到app,保證編譯部署是通的,然后在這個基礎上再做代碼重構和打散,把重要的公有能力給提取出來,去讓代碼逐漸演變成理想的形態。