• 首页 首页 icon
  • 工具库 工具库 icon
    • IP查询 IP查询 icon
  • 内容库 内容库 icon
    • 快讯库 快讯库 icon
    • 精品库 精品库 icon
    • 问答库 问答库 icon
  • 更多 更多 icon
    • 服务条款 服务条款 icon

Go微服务架构系列--gin框架(上) | 技术专题第二期征文

武飞扬头像
肥狐
帮助1

学新通

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
// 获取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的语法特性,飞狐推荐把路由放到控制层中维护

学新通

路由改造

路由改造之前我们新建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设计模式之单例模式

今天的内容其实很轻松,加餐部分我们来个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)
}

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

🏆 技术专题第二期 | 我与 Go 的那些事......

这篇好文章是转载于:学新通技术网

  • 版权申明: 本站部分内容来自互联网,仅供学习及演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,请提供相关证据及您的身份证明,我们将在收到邮件后48小时内删除。
  • 本站站名: 学新通技术网
  • 本文地址: /boutique/detail/tanhfjjggk
系列文章
更多 icon
同类精品
更多 icon
继续加载