共计 4626 个字符,预计需要花费 12 分钟才能阅读完成。
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) | |
} |
好啦,这篇的内容就全副完结啦,后续内容会讲中间件、错误处理等等。