一、 net/http包够用吗?

Go的net相关标准包提供web开发的大多数实现支持,如果你的项目的路径端点在十个以内,如一个简单的企业网站,这当然是够用的。但如果你的项目是构建一个大型电商网站,有成千上万个页面的路径端点,如下:

GET   /goods/:id
POST  /order/:id
DELTE /goods/:id
GET   /goods/:id/name
...
GET   /goods/:id/recommend

在这种情况下,如果只是用net/http包构建,那你不仅要手写url参数解析器,且单单写handler函数就足以让你奔溃。所以对于大多数中大型项目而言,使用框架技术开发是明智之举。

根据经验,只要你的路由带有参数,并且这个项目的API数目超过了10,就尽量不要使用net/http中默认的路由。在Go开源界应用最广泛的router是httpRouter,很多开源的router框架都是基于httpRouter进行一定程度的改造的成果。

二、Web框架技术

现代Web技术大多基于HTTP协议构建,所谓Web框架技术,其本质是在框架底层对HTTP进行有设计的再度封装,最基本的为对Request和对Response处理的封装,除此也包括路由调度,请求参数校验,模板引擎等。再附加一些加速开发和模块管理的套件,如:中间件、MVC分层设计、数据库IO处理等开发套件。使用框架的目的在于屏蔽一些通用的底层编码,封装对用户友好的调用方法,让你把精力集中在业务模块的开发。当然这种封装有利有弊。利在于你不用重复造轮子,有专业的框架团队帮你维护比较规范和安全的底层逻辑,因为框架规范,团队开发中也比较易于沟通;弊在于框架的通用设计有可能不适合你的产品特性,且如果你不深入研究框架底层实现,有可能在项目开发过程中出现一些不可控的bug,这意味着如果你使用一种框架,团队中必须有深入理解该框架的成员,才能让项目开发的进展在可控的范围内。

1. 框架类型:

开源社区有许许多多不同类型的web框架,帮助用户实现快速上手开发,根据不同的项目需求,你可能需要构建API,有可能构建web页面服务,也有可能构建分布式的微服务,这就区分出许多框架类型,以下为框架类型的划分及热度较高的开源项目:

  • Router型框架

    • Ace: 快速开发的Web框架。
    • api2go: Go的JSON API实现。
    • Gin: 一个微框架,类似Martinier的API,重点是小巧、易用、性能好很多,也因为 httprouter 的性能提高了40倍。
    • Goat: Go中的简约REST API服务器
    • goMiddlewareChain: 一个像express.js的中间件调用链框架。
    • Hikaru: 支持独立部署和谷歌AppEngine
    • Hitch: 将 httprouter, httpcontext, 和中间件 捆绑在一起。
    • httpway: 具有httprouter上下文的简单中间件扩展和具有正常关闭支持的服务框架
    • kami: 使用 x/net/context的小型Web框架。
    • Medeina: 灵感来自Ruby的 Roda 和Cuba。
    • Neko: 一个轻量级Web应用程序框架。
    • River: 一个简单轻巧的REST服务框架。
    • Roxanna:httprouter的合并,更好的日志记录和热重载。
    • siesta:具有上下文的可组合HTTP处理程序。
    • xmux: xmux is a httprouter fork on top of xhandler (net/context aware) aware)

基于httpRouter进行简单的封装,然后提供定制的中间件和一些简单的小工具集成比如gin,主打轻量,易学,高性能。

  • 迁移型框架

    • Beego:一个快速开发 Go 应用的 HTTP 框架,他可以用来快速开发 API、Web 及后端服务等各种应用,是一个 RESTful 的框架,主要设计灵感来源于 tornado、sinatra 和 flask 这三个框架,但是结合了 Go 本身的一些特性(interface、struct 嵌入等)而设计的一个框架。beego 是基于八大独立的模块构建的,是一个高度解耦的框架。设计之初就考虑功能模块化,用户即使不使用 beego 的 HTTP 逻辑,也依旧可以使用这些独立模块。
    • Iris:Iris的理念是为HTTP提供强大的工具,使其成为单页面应用程序,网站,混合或公共HTTP API的理想解决方案。

借鉴其它语言的编程风格,使用MVC开发模式的框架,例如beego、Iris-go等,这种框架方便从其它语言迁移过来的程序员快速上手,快速开发,很多PHP、JAVA程序员对这种框架更容易上手。

  • 代码生成型框架

    • Goa:Goa采用不同的方法来构建服务,使用简单的Go DSL 来描述服务API 的设计。Goa使用描述生成专门的服务帮助程序代码,客户端代码和文档。Goa可以通过插件扩展,例如 goakit插件生成利用Go套件库的代码。

使用GO DSL描述来生成服务或API代码,支持数据库schema设计,插件设计,大部分代码可直接生成。goa就是这种类型的框架,它着重于构建微服务和API的系统。如果你玩过Yii2或Laravel,你会感觉这种框架开发的套路很熟悉。

  • 微服务型框架

    • Goa:以上。
    • go-micro:Go Micro提供了分布式系统开发的核心要求,包括RPC、事件驱动的异步通信、消息编解码、基于服务发现的负载均衡以及可插拔的插件机制。
    • go-kit:Go kit是一个编程工具包,用于在Go中构建微服务(或优雅的整体)。解决了分布式系统和应用程序架构中的常见问题,因此你可以专注于开发业务。

微服务架构在近年来越来越盛行,在容器化服务的时代,微服务化的应用也代表着未来,go在微服务的开发方面也有得天独厚的优势,这类框架有go-micro、go-kit等,其中go-micro更像是构建微服务的框架,而go-kit更像是微服务开发的工具集。

2. 框架设计解析

Go在2009年才诞生,迄今也就十年历史,在框架技术方面很多都是借鉴于其他语言的框架设计经验,所以在很多使用框架开发的项目中beego很流行,主要是因为人们更熟悉MVC这种开发套路,但如果你使用beego开发过你会发现有些许别扭,相较于MVC的框架,许多Gopher更喜欢使用轻量级的Router型框架构建REST API,或使用微服务框架搭建运行于容器的应用。或许它更符合Go Web开发的设计理念。至于如何选择框架,这得考虑产品的特性和团队的成员技术栈了,没有唯一答案,适合的才是最好的。

下面我们来解析一下大多数框架设计的共同特性吧:

2.1 HTTP封装

Web系统的本质就是对网络通信协议的应用设计,大体上基于HTTP协议进行应用的封装,如许多RESTful风格的API框架,就是完全贴合HTTP协议而设计的。HTTP的一次交互可抽象为Request(请求)和Response(响应),在上一篇中我们了解了Request和Response的具体结构和信息,Request包含八种请求Method来表明客户对资源的操作方式,并赋予请求头、请求参数等信息(在此抽象概述先忽略其它请求报文的处理),而Response则包含服务端在接收到用户请求并进行业务处理后的响应信息,如响应头、状态码、响应体等(在此抽象概述先忽略其他响应报文内容)。这是Web服务端和客户端交互的简单抽象。go的标准库net/http包已实现这些基本的处理,大多web框架根据各自设计的特性进行Request和Response的深度封装,使其对用户开发更友好,或增强其他处理的特性等。

一般框架会对Request和Response进行接口设计或直接设计为结构体实现,在服务中的Handler处理器进行传递,这是net/http包原生的处理方法,但多数框架会把Request和Response以及其他的附加结构封装到一个统一的Context接口或结构体,在服务中的Handler处理器进行传递,如gin:

type Context struct {writermem responseWriterRequest   *http.RequestWriter    ResponseWriterParams   Paramshandlers HandlersChainindex    int8engine *Engine// Keys is a key/value pair exclusively for the context of each request.Keys map[string]interface{}// Errors is a list of errors attached to all the handlers/middlewares who used this context.Errors errorMsgs// Accepted defines a list of manually accepted formats for content negotiation.Accepted []string
}

像你以上看到的,许多应用框架会把一次HTTP的交互中的所有要素统一抽象到一个上下文Context中,由路由调度指派的Handler处理器接收处理,于是你会看到gin框架的做法,也为多数web框架的做法:

package mainimport "github.com/gin-gonic/gin"func main() {r := gin.Default()//将访问路径/ping的请求的处理指派到一个handler匿名函数,传递的是*gin.Context。r.GET("/ping", func(c *gin.Context) {c.JSON(200, gin.H{"message": "pong",})})r.Run() // listen and serve on 0.0.0.0:8080
}
2.2 路由设计

在Web交互中,客户端都是通过URL(统一资源定位符)对服务端资源进行HTTP 请求的,一个URL包含资源的请求路径,该路径应由系统路由(router)调度到请求处理器。在常见的Web框架中,router是必备的组件。Go语言圈子里router也时常被称为http的multiplexer。

net/http包内置的ServeMux来完成简单的路由功能,如果开发Web系统对路径中没有带参数要求的话,用net/http标准库中的mux就可以了,但现实并没那么简单,一个稍微复杂的项目都有路径划分及携带参数的要求,特别是RESTful风格的API设计,这就需要对请求路径的URL进行路由调度设计。

在RESTful中除了GET和POST之外,还使用了HTTP协议定义的几种其它的标准化语义。具体包括:

const (MethodGet     = "GET"MethodHead    = "HEAD"MethodPost    = "POST"MethodPut     = "PUT"MethodPatch   = "PATCH" // RFC 5789MethodDelete  = "DELETE"MethodConnect = "CONNECT"MethodOptions = "OPTIONS"MethodTrace   = "TRACE"
)

来看看RESTful中常见的请求路径:

GET /repos/:owner/:repo/comments/:id/reactions
POST /projects/:project_id/columns
PUT /user/starred/:owner/:repo
DELETE /user/starred/:owner/:repo

这是常见的几个RESTful API设计。RESTful风格的API重度依赖请求路径。会将很多参数放在请求URI中。除此之外还会使用很多并不那么常见的HTTP状态码。

如果我们的系统也想要这样的URI设计,使用http包的ServeMux显然不够。

较流行的开源go Web框架大多使用httprouter,或是基于httprouter的变种对路由进行支持。前面提到的github的参数式路由在httprouter中都是可以支持的。

因为httprouter中使用的是显式匹配,所以在设计路由的时候需要规避一些会导致路由冲突的情况,例如:

//conflict:
GET /user/info/:name
GET /user/:id//no conflict:
GET /user/info/:name
POST /user/:id

简单来讲的话,如果两个路由拥有一致的http方法(指 GET/POST/PUT/DELETE)和请求路径前缀,且在某个位置出现了A路由是wildcard(指:id这种形式)参数,B路由则是普通字符串,那么就会发生路由冲突。路由冲突会在初始化阶段直接panic。

除了正常情况下的路由支持,httprouter也支持对一些特殊情况下的回调函数进行定制,例如404的时候:

r := httprouter.New()
r.NotFound = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {w.Write([]byte("oh no, not found"))
})
或者内部panic的时候:r.PanicHandler = func(w http.ResponseWriter, r *http.Request, c interface{}) {log.Printf("Recovering from panic, Reason: %#v", c.(error))w.WriteHeader(http.StatusInternalServerError)w.Write([]byte(c.(error).Error()))
}

目前开源界较流行的Web框架gin使用的就是httprouter的变种。

2.3 中间件

我们来看看现实开发的场景,假如你正在编写一处产品提出的业务需求,和往常一样,你使用一个路径绑定一个Handler处理器实现业务编码,而正在这时,你的开发主管为了统计每个业务中的耗时任务,要求开发成员在业务代码中插入耗时计算的逻辑,你照做了;再过一段时间,你的开发主管为了收集用户的行为数据,要求开发成员在用户的业务逻辑上安插日志记录,以供后期离线计算,你也照做了;后来随着业务不断扩大,系统业务衍生出了“大会员”子系统,需要对符合要求的用户进行鉴权并过滤,要求开发人员在每个业务处理做权限控制,这时你崩溃了,系统已经随着业务发展变得越来越复杂,而且掺杂了各种业务和非业务的逻辑代码,你还继续一个一个改吗?很显然你已经陷入代码泥潭!

所谓中间件,是在实际处理用户业务逻辑的生命周期中,在对应的节点安插逻辑处理,可以在核心业务逻辑前或后,是剥离非业务逻辑的重要系统组件。

中间件设计像是一个调用链,context在链中各节点传递,各节点进行各自的功能处理:

中间件.png

看上图大家就明白,在核心业务的前后,将非业务逻辑剥离出来,实现预先处理和后置处理的效果,这就是中间件的本质。

实现中间件思路非常简单,Go的高阶函数编程可以非常容易的实现中间件设计,还是以Gin的中间件设计为例:

//编写一个Logger中间件,该中间件统一返回一个HandlerFunc,每个HandlerFunc都传递Context。
func Logger() gin.HandlerFunc {return func(c *gin.Context) {t := time.Now()// 设置 example 变量c.Set("example", "12345")//请求前...//执行下一层调用节点,根据Use()方法的调用顺序执行,执行完所有中间件后再进入核心业务Handler。c.Next()//请求后...latency := time.Since(t)log.Print(latency)// 获取发送的 statusstatus := c.Writer.Status()log.Println(status)}
}func main() {s := gin.New()//使用中间件调用Use()方法即可s.Use(Logger())s.GET("/test", func(c *gin.Context) {example := c.MustGet("example").(string)// 打印:"12345"log.Println(example)})// 监听并在 0.0.0.0:8080 上启动服务s.Run(":8080")
}
2.4 请求参数校验
请求参数校验波动拳.jpg

你或许看过上图,这是社区中嘲笑PHP校验请求参数的例子,实际上,这种做法与语言无关,很多没有设计的参数校验都是这种粗糙的写法。这种if-else风暴组织的代码无疑是丑陋的。从代码的易读性和逻辑性角度看,都应该对条件分支进行重构,其实许多if嵌套都可重构为顺序条件结构,来提高代码的优雅程度,在此不便展开。回到正题,其实在Handler写一大堆参数校验的代码是挺难看的,既然参数校验是每个请求必须的流程,有没有什么方法可以把参数校验分离出Handler处理器呢?答案是Validator验证器设计。

从设计的角度讲,我们一定会为每个请求都声明一个结构体。

这里我们引入一个新的validator库:https://github.com/go-playground/validator

以下为使用示例:

import "gopkg.in/go-playground/validator.v9"type RegisterReq struct {// 字符串的 gt=0 表示长度必须 > 0,gt = greater thanUsername       string   `validate:"gt=0"`// 同上PasswordNew    string   `validate:"gt=0"`// eqfield 跨字段相等校验PasswordRepeat string   `validate:"eqfield=PasswordNew"`// 合法 email 格式校验Email          string   `validate:"email"`
}validate := validator.New()func validate(req RegisterReq) error {err := validate.Struct(req)if err != nil {doSomething()return err}...
}

这样就不需要在每个请求进入业务逻辑之前都写重复的validate()函数了。本例中只列出了这个校验器非常简单的几个功能。

我们试着跑一下这个程序,输入参数设置为:

//...var req = RegisterReq {Username       : "Fun",PasswordNew    : "abc12138",PasswordRepeat : "ABC12138",Email          : "fun@abc.com",
}err := validate(req)
fmt.Println(err)// Key: 'RegisterReq.PasswordRepeat' Error:Field validation for
// 'PasswordRepeat' failed on the 'eqfield' tag

错误信息可以针对每种tag定制,各位可查阅各验证器文档,开源社区有许多验证器的独立项目,也可以参考各框架的验证器实现,如有特殊需求也可以自定义验证器。

2.5 数据库交互

在《Go进阶系列》前三篇中,我们已经了解Go操作数据库的大体实现方法,对关系型数据库的操作,主要基于标准库的database/sql包,开源社区也有许多对sql包的再度封装项目,但这些库的使用许多都是对sql原生语句的操作,使用这些包的项目中,你会发现许多非常相似的sql语句,当项目比较大时会充斥许多冗余代码。于是为了提高项目生产效率,许多框架或独立开源项目都提供了ORM或SQL Builder的支持。在进阶系列开篇我们简单使用了gorm。

数据库交互在Web系统中可为是标配组件,因为数据持久化的场景随处可见,在其他语言的框架中,许多框架都内置了数据库交互组件,如PHP的Laravel、Yii等,数据访问层都是框架的标配,但许多静态语言的框架中,数据访问层是独立的框架,如JAVA的Hibernate,框架的设计依赖于语言的特点,由于Go包管理的特点,Go的框架中大部分数据库访问层是独立的,类似在Go Web框架中Router型框架占多数,它们只专注于处理HTTP相关的核心部分,其他的由更专业的数据访问层框架来做,如gorm、xorm等ORM框架,然而有些迁移型框架也包揽数据访问层,如beego、goa等,但这种框架的数据访问层也是独立的模块,是可独立使用的。

Go的数据访问层框架相对比较自由,数据访问层的库大体分三种:

  • 基于database/sql进行再度封装:如sqlx;
  • ORM框架:如gorm、xorm、beego/orm;
  • SQL Builder:如 gendry、 goqu
ORM 利弊

使用ORM框架的项目,基本已经屏蔽了SQL语句的编写,所有的数据库交互都使用对象映射的方式,用操作对象替代SQL语句,这无疑对OOP程序员是福利。

ORM的目的就是屏蔽掉DB层,实际上很多语言的ORM只要把你的类或结构体定义好,再用特定的语法将结构体之间的一对一或者一对多关系表达出来。那么任务就完成了。然后你就可以对这些映射好了数据库表的对象进行各种操作,例如save,create,retrieve,delete。至于ORM在背地里做了什么阴险的勾当,你是不一定清楚的。使用ORM的时候,我们往往比较容易有一种忘记了数据库的直观感受。ORM设计初衷就是为了让数据的操作和存储的具体实现所剥离。但是在上了规模的公司的人们渐渐达成了一个共识,由于隐藏重要的细节,ORM可能是失败的设计。其所隐藏的重要细节对于上了规模的系统开发来说至关重要。

SQL Builder 利弊

相比ORM来说,SQL Builder在SQL和项目可维护性之间取得了比较好的平衡。首先sql builer不像ORM那样屏蔽了过多的细节,其次从开发的角度来讲,SQL Builder简单进行封装后也可以非常高效地完成开发:

where := map[string]interface{} {"order_id > ?" : 0,"customer_id != ?" : 0,
}
limit := []int{0,100}
orderBy := []string{"id asc", "create_time desc"}orders := orderModel.GetList(where, limit, orderBy)

读写SQL Builder的相关代码都不费劲,也非常接近SQL语句所表达的意思。所以通过代码就可以对这个查询是否命中数据库索引,是否走了覆盖索引,是否能够用上联合索引进行分析了。

说白了SQL Builder是sql在代码里的一种特殊方言,如果你们没有DBA但研发有自己分析和优化sql的能力,或者你们公司的DBA对于学习这样一些sql的方言没有异议。那么使用SQL Builder是一个比较好的选择,不会导致什么问题。

另外在一些本来也不需要DBA介入的场景内,使用SQL Builder也是可以的,例如你要做一套运维系统,且将MySQL当作了系统中的一个组件,系统的QPS不高,查询不复杂等等。

一旦你做的是高并发的OLTP在线系统,且想在人员充足分工明确的前提下最大程度控制系统的风险,使用SQL Builder就不合适了。

小结

使用什么框架作为数据访问层并无同一说法,完全看项目特性,如项目中对SQL的审核非常严格,建议使用基于database/sql封装的库,这样sql语句就会相对透明;如果你的团队不太注重SQL细节且非常注重开发效率,可使用ORM;相对以上两种而言,SQL Builder是比较折中的做法,如果你能控制系统风险,并可牺牲一些高并发性能,SQL Builder是不错的选择。

2.6 分层架构

说到分层,我们就会想到大名鼎鼎的MVC分层设计模式,在前后端分离架构大热前,MVC分层设计的框架可谓大行其道。在那时MVC单体架构可谓“万能”,只要做web项目第一个想到的就是MVC。

在MVC中,为了方便对业务模块进行划分和团队的分工合作,将程序划分为:

  • 控制器(Controller)- 负责转发请求,对请求进行处理。
  • 视图(View) - 界面设计人员进行图形界面设计。
  • 模型(Model) - 程序员编写程序应有的功能(实现算法等等)、数据库专家进行数据管理和数据库设计(可以实现具体的功能)。
MVC.jpeg

但随着移动互联网的兴起,C/S结构的软件又再度兴起,许多web应用的客户端都独立运行,逐渐兴起大前端热潮,MVC中的V从此分离出去,衍生出MC+V的程序结构:

前后端分离交互图.png

其实在Go兴起的年代,移动互联网应用已是主流,编写Go后端程序很多是不倡导使用MVC模式的,这与go的设计思想不符。但Go是新兴的语言,许多开发者对软件开发的理解还没与Go的设计思想适配,于是乎出现了许多迁移型框架,提供熟悉的设计方法方便其他语言的程序员迁移过来,这当中beego就是代表,其mvc设计是该框架的核心组件,最近兴起的Iris也对MVC结构的程序提供框架级别的支持。对于这种现象其实也有利有弊,利在于让更多的程序员更容易使用GO上手实际项目,弊在于这可能对Go设计Web程序的思想有点误导,Go倡导大道至简,许多更简单高效的Router型框架更切合Go Web的设计思想。

以下提供一个Iris实现MVC的编写示例:

package mainimport ("github.com/kataras/iris""github.com/kataras/iris/mvc""github.com/kataras/iris/middleware/logger""github.com/kataras/iris/middleware/recover"
)func newApp() *iris.Application {app := iris.New()app.Use(recover.New())app.Use(logger.New())// Serve a controller based on the root Router, "/".mvc.New(app).Handle(new(ExampleController))return app
}func main() {app := newApp()app.Run(iris.Addr(":8080"))
}// 定义一个控制器
type ExampleController struct{}// Get serves
// Method:   GET
// Resource: http://localhost:8080
func (c *ExampleController) Get() mvc.Result {return mvc.Response{ContentType: "text/html",Text:        "<h1>Welcome</h1>",}
}// GetPing serves
// Method:   GET
// Resource: http://localhost:8080/ping
func (c *ExampleController) GetPing() string {return "pong"
}// GetHello serves
// Method:   GET
// Resource: http://localhost:8080/hello
func (c *ExampleController) GetHello() interface{} {return map[string]string{"message": "Hello Iris!"}
}func (c *ExampleController) BeforeActivation(b mvc.BeforeActivation) {anyMiddlewareHere := func(ctx iris.Context) {ctx.Application().Logger().Warnf("Inside /custom_path")ctx.Next()}b.Handle("GET", "/custom_path", "CustomHandlerWithoutFollowingTheNamingGuide", anyMiddlewareHere)}func (c *ExampleController) CustomHandlerWithoutFollowingTheNamingGuide() string {return "hello from the custom handler without following the naming guide"
}

相比之下,直接使用Router + Handler的编写方式会简单得多:

package mainimport ("github.com/kataras/iris""github.com/kataras/iris/middleware/logger""github.com/kataras/iris/middleware/recover"
)func main() {app := iris.New()app.Logger().SetLevel("debug")app.Use(recover.New())app.Use(logger.New())// Method:   GET// Resource: http://localhost:8080app.Handle("GET", "/", func(ctx iris.Context) {ctx.HTML("<h1>Welcome</h1>")})// same as app.Handle("GET", "/ping", [...])// Method:   GET// Resource: http://localhost:8080/pingapp.Get("/ping", func(ctx iris.Context) {ctx.WriteString("pong")})// Method:   GET// Resource: http://localhost:8080/helloapp.Get("/hello", func(ctx iris.Context) {ctx.JSON(iris.Map{"message": "Hello Iris!"})})// http://localhost:8080// http://localhost:8080/ping// http://localhost:8080/helloapp.Run(iris.Addr(":8080"), iris.WithoutServerError(iris.ErrServerClosed))
}

个人建议:不要为了MVC而MVC!在分层设计方面,使用经典的三层架构就可以了,表现层的前端独立分离,后端实现业务逻辑层和数据访问层使软件结构足够精简,更何况我们渐渐进入了容器服务时代,许多大型应用会进化成分布式微服务独立运行,没必要再坚守单体架构的MVC模式了。

2.7 插件机制

所谓插件,是系统中与业务无关的可插拔工具集。插件机制是面向接口编程应用的典范,一个类型的插件可有多个具体的插件实现,系统只对插件接口设计特定的签名方法即可。如缓存插件,RABC插件,日志插件。插件是一种框架的机制设计,当系统允许某些组件是可替换时,把组件编写出插件形式,即编写特定的接口规范,具体的插件实现可由第三方实现,也可由用户自定义。

如Beego框架为了实现某些组件可替换、可由用户自定义,规范了以下插件:

  • gorelic
  • 支付宝 SDK
  • pongo2
  • keenio
  • casbin - RBAC ACL plugins

这种设计有助于用户定制自己的系统。

更极致的使用插件机制的框架像go-micro,它是一个微服务框架。Go Micro为每个分布式系统抽象出接口。因此,Go Micro的接口都是可插拔的,允许其在运行时不可知的情况下仍可支持。所以只要实现接口,可以在内部使用任何的技术。

Go Micro对整个框架的核心组件都做了插件机制的设计,你可以随意选择消息总线插件、RPC通信插件、编解码插件、服务注册插件、负载均衡插件、消息传输插件等。你可以自由选择插件库中的第三方库替换插件,也可以自己实现插件,只要符合框架定义的接口规范。

插件 描述
Broker PubSub messaging; NATS, NSQ, RabbitMQ, Kafka
Client RPC Clients; gRPC, HTTP
Codec Message Encoding; BSON, Mercury
Micro Micro Toolkit Plugins
Registry Service Discovery; Etcd, Gossip, NATS
Selector Load balancing; Label, Cache, Static
Server RPC Servers; gRPC, HTTP
Transport Bidirectional Streaming; NATS, RabbitMQ
Wrapper Middleware; Circuit Breakers, Rate Limiting, Tracing, Monitoring

我们来看一下其实现,以Broker插件为例:

//定义了Broker类型插件的接口
// Broker is an interface used for asynchronous messaging.
type Broker interface {Init(...Option) errorOptions() OptionsAddress() stringConnect() errorDisconnect() errorPublish(topic string, m *Message, opts ...PublishOption) errorSubscribe(topic string, h Handler, opts ...SubscribeOption) (Subscriber, error)String() string
}...

我们再看一看一个具体的使用Redis作为Broker的定义:

// Package redis provides a Redis broker
package redisimport ("context""errors""strings""time""github.com/gomodule/redigo/redis""github.com/micro/go-micro/broker""github.com/micro/go-micro/codec""github.com/micro/go-micro/codec/json""github.com/micro/go-micro/config/cmd"
)//此处省略代码...// broker implementation for Redis.
type redisBroker struct {addr  stringpool  *redis.Poolopts  broker.Optionsbopts *brokerOptions
}// String returns the name of the broker implementation.
func (b *redisBroker) String() string {return "redis"
}// Options returns the options defined for the broker.
func (b *redisBroker) Options() broker.Options {return b.opts
}// Address returns the address the broker will use to create new connections.
// This will be set only after Connect is called.
func (b *redisBroker) Address() string {return b.addr
}// Init sets or overrides broker options.
func (b *redisBroker) Init(opts ...broker.Option) error {//此处省略代码...
}// Connect establishes a connection to Redis which provides the
// pub/sub implementation.
func (b *redisBroker) Connect() error {//此处省略代码...
}// Disconnect closes the connection pool.
func (b *redisBroker) Disconnect() error {//此处省略代码...
}// Publish publishes a message.
func (b *redisBroker) Publish(topic string, msg *broker.Message, opts ...broker.PublishOption) error {//此处省略代码...
}// Subscribe returns a subscriber for the topic and handler.
func (b *redisBroker) Subscribe(topic string, handler broker.Handler, opts ...broker.SubscribeOption) (broker.Subscriber, error) {//此处省略代码...}// NewBroker returns a new broker implemented using the Redis pub/sub
// protocol. The connection address may be a fully qualified IANA address such
// as: redis://user:secret@localhost:6379/0?foo=bar&qux=baz
func NewBroker(opts ...broker.Option) broker.Broker {// Default options.bopts := &brokerOptions{maxIdle:        DefaultMaxIdle,maxActive:      DefaultMaxActive,idleTimeout:    DefaultIdleTimeout,connectTimeout: DefaultConnectTimeout,readTimeout:    DefaultReadTimeout,writeTimeout:   DefaultWriteTimeout,}// Initialize with empty broker options.options := broker.Options{Codec:   json.Marshaler{},Context: context.WithValue(context.Background(), optionsKey, bopts),}for _, o := range opts {o(&options)}return &redisBroker{opts:  options,bopts: bopts,}
}

可见Redis Broker插件实现了Broker插件类型的接口签名方法。Micro框架就是根据插件机制搭建的,你可以根据插件自由的定制自己的微服务系统。

三、总结

Web技术发展日新月异,框架也层出不穷,面对如此眼花缭乱的技术,我们难免会心生畏惧,那么多框架,那么多技术该如何选择?刚学完这个框架,又有更新更好的框架出来。对于这个问题,我认为我们不应该迷信框架,Web技术的本质是什么,它的底层核心技术是什么?我们只要理解其本质,其他从其衍生的技术都万变不离其宗。Web框架的设计初衷是屏蔽开发底层的逻辑使我们更专注于业务,但由于其屏蔽底层逻辑,我们更需要深入理解它,读懂它。

至此《Go进阶系列》告一段落,之后计划输出《Go实战系列》和《Go高级系列》,如果对你有帮助,不要忘了点个赞或关注本人哈,谢谢!


http://www.taodudu.cc/news/show-3200551.html

相关文章:

  • 软件研发业务流程的制定及改进
  • 【AI视野·今日Robot 机器人论文速览 第七期】Tue, 15 Jun 2021
  • Go Web——HttpRouter路由
  • SSM中的拦截器机制
  • 《预训练周刊》第51期:无重构预训练、零样本自动微调、一键调用OPT
  • 精通国际象棋的AI研究员:AlphaZero真的是一次突破吗?
  • 智能肛珠作弊案反转:19岁小将告世界冠军诽谤索赔7亿
  • 快速绘制流程图「GitHub 热点速览 v.22.47」
  • C语言(求圆柱体的体积)
  • 使用c++,类实现求圆柱体体积
  • C++期末考试1:求圆柱体体积(完整代码含测试)
  • JAVA继承案例--计算圆柱体体积
  • 用java实现圆柱体体积
  • C语言例题:求出圆柱体的体积
  • 利用继承,实现圆柱体体积的计算
  • python计算圆柱体体积代码_继承实现圆柱体面积体积的计算
  • 0013 求圆柱体体积
  • 求圆柱体体积
  • 通过对象实现圆柱体体积计算
  • 计算圆柱体体积
  • 【半年总结】——2015.08
  • 【半年总结】思想与技术的腾飞
  • 【总结】羽翼日渐丰满—年终总结
  • 众咖云集的 PyCon 2019 上海站,大佬们都讲了啥
  • 苹果画画软件_三只苹果改变世界,京东Apple创意节引爆创意能量
  • 关于批处理的学习之一[前言]
  • 英语网站荟萃
  • 我的电脑学习经历
  • 英语总结—— 翩若惊鸿, 婉若游龙
  • 英语学习网站

19 Go Web 框架(二):框架技术详解相关推荐

  1. SpringSecurity权限管理框架系列(六)-Spring Security框架自定义配置类详解(二)之authorizeRequests配置详解

    1.预置演示环境 这个演示环境继续沿用 SpringSecurit权限管理框架系列(五)-Spring Security框架自定义配置类详解(一)之formLogin配置详解的环境. 2.自定义配置类 ...

  2. 【H.264/AVC视频编解码技术详解】二十三、帧间预测编码(1):帧间预测编码的基本原理

    <H.264/AVC视频编解码技术详解>视频教程已经在"CSDN学院"上线,视频中详述了H.264的背景.标准协议和实现,并通过一个实战工程的形式对H.264的标准进行 ...

  3. Android Binder框架实现之Parcel详解之基本数据的读写

       Android Binder框架实现之Parcel详解之基本数据的读写 Android Binder框架实现目录: Android Binder框架实现之Binder的设计思想 Android ...

  4. java集合框架史上最详解(list set 以及map)

    title: Java集合框架史上最详解(list set 以及map) tags: 集合框架 list set map 文章目录 一.集合框架总体架构 1.1 集合框架在被设计时需满足的目标 1.2 ...

  5. androidentity什么用_Android ORM 框架:GreenDao 使用详解(进阶篇)

    前言 在 Android ORM 框架:GreenDao 使用详解(基础篇) 中,我们了解了 GreenDao 的基本使用,本文我们将深入讲解 GreenDao 的使用 . 一.复杂表结构 a, 使用 ...

  6. 《视频直播技术详解》之二:编码和封装、推流和传输

    视频编码是本系列一个重要的部分,如果把整个流媒体比喻成一个物流系统,那么编解码就是其中配货和装货的过程,这个过程非常重要,它的速度和压缩比对物流系统的意义非常大,影响物流系统的整体速度和成本.同样,对 ...

  7. Android Binder框架实现之bindService详解

        Android Binder框架实现之bindService详解 Android Binder框架实现目录: Android Binder框架实现之Binder的设计思想 Android Bi ...

  8. 【H.264/AVC视频编解码技术详解】十九:熵编码算法(5)——H.264的CABAC(上):语法元素的二值化方法...

    <H.264/AVC视频编解码技术详解>视频教程已经在"CSDN学院"上线,视频中详述了H.264的背景.标准协议和实现,并通过一个实战工程的形式对H.264的标准进行 ...

  9. 超轻量级DI容器框架Google Guice与Spring框架的区别教程详解及其demo代码片段分享...

    超轻量级DI容器框架Google Guice与Spring框架的区别教程详解及其demo代码片段分享 DI框架 Google-Guice入门介绍 转载于:https://www.cnblogs.com ...

  10. 自动化测试框架[Cypress元素操作详解]

    前提 已经熟练掌握了Cypress的基本知识,请参考自动化测试框架[Cypress概述]和自动化测试框架[各自动化测试框架比较] 已经熟练掌握Cypress环境配置,请参考自动化测试框架[Cypres ...

最新文章

  1. 卸载系统预装McAfee Agent
  2. Redis 概念以及底层数据结构
  3. CreateRemoteThread注入DLL
  4. 文巾解题 292. Nim 游戏
  5. 《数据结构与算法 C语言版》—— 2.7习题
  6. 后端技术:一个注解解决 SpringBoot 接口防刷
  7. 华为触摸提示音怎么换_抖音苹果iPhone手机怎么改微信消息提示音 自定义换声音教程...
  8. 微课|中学生可以这样学Python(例4.2):打印九九乘法表
  9. 店庆遇上双11,买书的最大优惠来了!
  10. node.js + express服务端,客户端请求图片,在浏览器出现乱码解决方案
  11. 正则去除汉字和只取数字
  12. python网络编程 赵宏_【干货收藏】Python面试指南大全
  13. php 批量上传图片插件,diyUpload - jQuery多张图片批量上传插件
  14. 游戏场景设计探究:空间潜意识
  15. 8 EXCEL选择填充与粘贴
  16. python 多态app_Python——多态
  17. 爬取6874条数据,告诉你数据分析师的薪资待遇~!
  18. linux安装python任意版本,一键安装和一键卸载shell脚本
  19. 有道再出发:真正的教育事业没有终点
  20. python培训学费多少钱-python培训学费多少钱

热门文章

  1. 创建Windows10 密码重设盘
  2. 【金猿产品展】极盾·觅踪——数据使用安全管控平台
  3. 远程关闭重启计算机CMD命令
  4. 远程桌面连接的开启,和借助内外网软件,实现外网PC手机远程控制内网电脑,详细图文教程
  5. Consult IDE log for more details (Help | Show Log),read failed, socket might closed or timeout,
  6. android 轻量级缓存框架ASimpleCache
  7. 推荐一个外文图书专著下载网站
  8. 开源社区活跃度分析——api.github.com的使用
  9. vue中怎么根据不同的(分屏模式)调整【自定义不同视频布局】?
  10. java三国志吞食天地_FC三国志吞食天地完整版下载-街机吞食天地完整版模拟器(含出招表)-电玩咖...