hi,你們好,小弟飛狐。此次帶來的是Golang微服務系列。Deno從零到架構級系列文章裏就提到過微服務。最近一次項目重構中,採用了go-micro微服務架構。又恰逢deno1.0正式版推出,因而乎node業務層也用deno重寫。把Java的業務模塊也所有用go重構了。前端
重構業務的時候,咱們用go-micro來作微服務,全面的替代了Java棧。好比:node
訂單、支付等等都做爲單獨的服務。而deno之上都歸前端來處理業務層,這樣職責明確,更利於先後端協做。另外,咱們這套將會採用最新的go-micro V3來搭建架構。git
話很少說,即刻開始。這套微服務系列不是入門教程,須要有go項目經驗。從框架選型開始,到go-micro構建微服務架構。go的框架選型不用糾結。在go的web框架中,飛狐推薦兩個框架:github
介紹這兩框架的文章太多了,優點與區別我就很少說了。這兩個框架你們能夠任選其一,能夠任憑喜愛,那飛狐選擇gin框架,並將gin框架集成到go-micro中。咱們先從gin基礎架構搭建開始。先來個簡單的例子,以下:web
package main // 獲取gin import "github.com/gin-gonic/gin" // 主函數 func main() { // 取r是router的縮寫 r := gin.Default() // 這裏很是簡單,很像deno、node的路由吧 r.GET("/", func(c \*gin.Context) { c.JSON(200, gin.H{ "message": "pong", }) }) // 監聽端口8080 r.Run(":8080") }
這個例子很是簡單,直接copy的gin官方代碼。加了中文註釋,運行便可,相信有點基礎的童鞋都能看懂。這裏的路由,通常會單獨寫文件來維護。不過,我在deno架構系列中提到過,拿到項目直接就是幹路由,不要去維護一個單獨的路由文件。deno系列咱們用的是註解路由。雖然go也能夠經過反射實現註解路由,但go不是一門面向對象的語言。根據go的語法特性,飛狐推薦把路由放到控制層中維護。segmentfault
路由改造以前咱們新建controller層,而後操做以下:後端
// 新建userController.go package controller import ( "github.com/gin-gonic/gin" ) type UserController struct { *gin.Engine } // 這裏是構造函數 func NewUserController(e *gin.Engine) *UserController { return &UserController{e} } // 這裏是業務方法 func (this *UserController) GetUser() gin.HandlerFunc { return func(ctx *gin.Context) { ctx.JSON(200, gin.H{ "data": "hello world", }) } } // 這裏是處理路由的地兒 func (this *UserController) Router () { this.Handle("GET", "/", this.GetUser()) }
這樣路由就維護到每一個控制器中了,那如何映射呢?咱們改造主文件以下:設計模式
func main () { r := gin.Default() NewUserController(r).Router() r.Run(":8080") }
關鍵代碼就是將構造器的Router方法在主函數中執行。這樣就達到目的,不用去維護單獨的路由文件了。不過,你們發現沒?這樣也帶來了一些弊端。好比:架構
爲了解決上述弊端,基於gin咱們搭建一個腳手架。就如同咱們基於oak搭建deno的腳手架同樣。一樣換作echo框架也一樣適用。新建server目錄,在此目錄下新建server.go文件,代碼以下:框架
package server import ( "github.com/gin-gonic/gin" ) // 這裏是定義一個接口,解決上述弊端的規範性 type IController interface { // 這個傳參就是腳手架主程 Router(server *Server) } // 定義一個腳手架 type Server struct { *gin.Engine // 路由分組一下子會用到 g *gin.RouterGroup } // 初始化函數 func Init() *Server { // 做爲Server的構造器 s := &Server{Engine: gin.New()} // 返回做爲鏈式調用 return s } // 監聽函數,更好的作法是這裏的端口應該放到配置文件 func (this *Server) Listen() { this.Run(":8080") } // 這裏是路由的關鍵代碼,這裏會掛載路由 func (this *Server) Route(controllers ...IController) *Server { // 遍歷全部的控制層,這裏使用接口,就是爲了將Router實例化 for _, c := range controllers { c.Router(this) } return this }
這一步完成了,主函數就減負了,主函數改造以下:
// main.go package main import ( . "feihu/controller" "feihu/server" ) // 這裏其實以前飛狐講的deno入口文件改造幾乎同樣 func main () { // 這裏就是腳手架提供的服務 server. // 初始化 Init(). // 路由 Route( NewUserController(), ). // 監聽端口 Listen() }
那控制層的代碼也會相應簡化,以前的控制層代碼改造以下:
package controller import ( "github.com/gin-gonic/gin" "feihu/server" ) // 這裏的gin引擎直接移到腳手架server裏 type UserController struct { } // 這裏是構造函數 func NewUserController() *UserController { return &UserController{} } // 這裏是業務方法 func (this *UserController) GetUser() gin.HandlerFunc { return func(ctx *gin.Context) { ctx.JSON(200, gin.H{ "data": "hello world", }) } } // 這裏依然是處理路由的地兒,而因爲咱們定義了接口規範,就必須實現Router方法 func (this *UserController) Router (server *server.Server) { server.Handle("GET", "/", this.GetUser()) }
這樣就比較完善了。不過衆所周知,gin支持路由分組。如何實現呢?咱們繼續往下。
路由分組只須要在server.go里加一個方法就OK了,代碼以下:
func (this *Server) GroupRouter(group string, controllers ...IController) *Server { this.g = this.Group(group) for _, c := range controllers { c.Router(this) } return this }
使用路由分組時,主函數main.go的代碼以下:
package main import ( . "feihu/controller" "feihu/server" ) func main () { server. Init(). Route( NewUserController(), ). // 這裏就是路由分組啦 GroupRouter("v1", NewOrderController(), ). Listen() }
好啦,這篇內容就結束了。下面是彩蛋部分,還有激情的小夥伴,鼓勵繼續學。
今天的內容其實很輕鬆,加餐部分咱們來個Go的設計模式好了。幾年前《聽飛狐聊JavaScript設計模式》中有講到單利模式。JS、Java實現單利模式都特別簡單,但Go不太同樣,咱們就拿單利模式來玩玩兒。從最簡單的例子開始
package main import "fmt" // 定義結構 type Singleton struct { MobileUrl string } // 變量 var instance *Singleton // 這裏是單例,返回的是單例結構 func GetSingleton() *Singleton { // 先判斷變量是否存在,若是不存在才建立 if instance == nil { instance = &Singleton{MobileUrl: "https://www.aizmen.com"} } return instance } func main () { x := GetSingleton() // 單獨打印x,能夠獲得:&{https://www.aizmen.com} x1 := GetSingleton() // 單獨打印x1,也獲得:&{https://www.aizmen.com} fmt.Println(x == x1) }
打印結果爲:true,說明是同一塊內存。這樣就實現了最簡單的單利模式了。
Go其實提供了一個更簡潔的sync.Once,實現以下:
package main import ( "fmt" "sync" ) type Singleton struct { MobileUrl string } var ( once sync.Once instance *Singleton ) func GetSingleton() *Singleton { once.Do(func() { instance = &Singleton{MobileUrl: "https://www.aizmen.com"} }) return instance } func main () { x := GetSingleton() x1 := GetSingleton() fmt.Println(x == x1) }
衆所周知,Go語言的協程很強大,在使用協程時,可使用sync.Once來控制。
Go還提供了一個基礎對象sync.Mutex,用以實現協程之間的同步邏輯,代碼實現以下:
package main import ( "fmt" "sync" ) type Singleton struct { MobileUrl string } var ( once sync.Once instance *Singleton mutex sync.Mutex ) func GetSingleton() *Singleton { mutex.Lock() defer mutex.Unlock() if instance == nil { instance = &Singleton{MobileUrl: "https://www.aizmen.com"} } return instance } func main () { x := GetSingleton() x1 := GetSingleton() fmt.Println(x == x1) }
好啦,這篇的內容就所有結束啦,後續內容會講中間件、錯誤處理等等。