hi,大家好,小弟飞狐。这次带来的是Golang微服务系列。Deno从零到架构级系列文章里就提到过微服务。最近一次我的项目重构中,采纳了go-micro微服务架构。又恰逢deno1.0正式版推出,于是乎node业务层也用deno重写。把Java的业务模块也全副用go重构了。

Go-micro重构Java业务

重构业务的时候,咱们用go-micro来做微服务,全面的代替了Java栈。比方:

  • 服务注册发现用到了etcd
  • 通信用到了grpc
  • 框架集成了gin

订单、领取等等都作为独自的服务。而deno之上都归前端来解决业务层,这样职责明确,更利于前后端合作。另外,咱们这套将会采纳最新的go-micro V3来搭建架构。

gin框架初体验

话不多说,即刻开始。这套微服务系列不是入门教程,须要有go我的项目教训。从框架选型开始,到go-micro构建微服务架构。go的框架选型不必纠结。在go的web框架中,飞狐举荐两个框架:

  • echo
  • gin

介绍这两框架的文章太多了,劣势与区别我就不多说了。这两个框架大家能够任选其一,能够听凭爱好,那飞狐抉择gin框架,并将gin框架集成到go-micro中。咱们先从gin基础架构搭建开始。先来个简略的例子,如下:

package main// 获取ginimport "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的语法个性,飞狐举荐把路由放到管制层中保护

路由革新

路由革新之前咱们新建controller层,而后操作如下:

// 新建userController.gopackage controllerimport (    "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 serverimport (    "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.gopackage mainimport (    . "feihu/controller"    "feihu/server")// 这里其实之前飞狐讲的deno入口文件革新简直一样func main () {    // 这里就是脚手架提供的服务    server.    // 初始化    Init().    // 路由    Route(        NewUserController(),    ).    // 监听端口    Listen()}

那管制层的代码也会相应简化,之前的管制层代码革新如下:

package controllerimport (    "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 mainimport (    . "feihu/controller"    "feihu/server")func main () {    server.    Init().    Route(        NewUserController(),    ).    // 这里就是路由分组啦    GroupRouter("v1",        NewOrderController(),    ).    Listen()}

好啦,这篇内容就完结了。上面是彩蛋局部,还有激情的小伙伴,激励持续学。

彩蛋:Go设计模式之单例模式

明天的内容其实很轻松,加餐局部咱们来个Go的设计模式好了。几年前《听飞狐聊JavaScript设计模式》中有讲到单利模式。JS、Java实现单利模式都特地简略,但Go不太一样,咱们就拿单利模式来玩玩儿。从最简略的例子开始

package mainimport "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 mainimport (    "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 mainimport (    "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)}

好啦,这篇的内容就全副完结啦,后续内容会讲中间件、错误处理等等。