Go微服務架構系列--gin框架(上)

image
hi,你們好,小弟飛狐。此次帶來的是Golang微服務系列。Deno從零到架構級系列文章裏就提到過微服務。最近一次項目重構中,採用了go-micro微服務架構。又恰逢deno1.0正式版推出,因而乎node業務層也用deno重寫。把Java的業務模塊也所有用go重構了。前端

Go-micro重構Java業務

重構業務的時候,咱們用go-micro來作微服務,全面的替代了Java棧。好比:node

  • 服務註冊發現用到了etcd
  • 通訊用到了grpc
  • 框架集成了gin

訂單、支付等等都做爲單獨的服務。而deno之上都歸前端來處理業務層,這樣職責明確,更利於先後端協做。另外,咱們這套將會採用最新的go-micro V3來搭建架構。git

image

gin框架初體驗

話很少說,即刻開始。這套微服務系列不是入門教程,須要有go項目經驗。從框架選型開始,到go-micro構建微服務架構。go的框架選型不用糾結。在go的web框架中,飛狐推薦兩個框架:github

  • echo
  • gin

介紹這兩框架的文章太多了,優點與區別我就很少說了。這兩個框架你們能夠任選其一,能夠任憑喜愛,那飛狐選擇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

image

路由改造

路由改造以前咱們新建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方法在主函數中執行。這樣就達到目的,不用去維護單獨的路由文件了。不過,你們發現沒?這樣也帶來了一些弊端。好比:架構

  • 規範性不好
  • 代碼耦合性高
  • 靈活性不夠、維護起來就很麻煩

image

搭建腳手架

爲了解決上述弊端,基於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()
}

好啦,這篇內容就結束了。下面是彩蛋部分,還有激情的小夥伴,鼓勵繼續學。

image

彩蛋:Go設計模式之單例模式

今天的內容其實很輕鬆,加餐部分咱們來個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,說明是同一塊內存。這樣就實現了最簡單的單利模式了。

sync.Once單例模式

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)
}

好啦,這篇的內容就所有結束啦,後續內容會講中間件、錯誤處理等等。

相關文章
相關標籤/搜索