Droplet——一款轻量的Golang应用层框架( 二 )

{code: 0, msg: "", data:{}}) , 想要在中间件去自动包装上它,也很难执行;最后就是——如果只依靠 http.Request/httpResponse,你也难在中间件感知到其他参与者的处理状态,。相信我说的这些问题,使用过的同学应该都有所感触 , 而这些问题并非难以解决 , 它们中的大部分基本都是可以通过自行建立一套约定来得以缓解(比如将这些信息都通过 context 去获取),而 Droplet 也是诞生于我在过往团队中去克服这些问题的实践之中,是一个相对可靠的实现 。
工作原理带着上面提到的这些问题,我们来看看 Droplet 的工作原理是怎样的 , 如下图所示:

Droplet——一款轻量的Golang应用层框架

文章插图
如我所说的那样,Droplet 的核心在于 提供基于pipeline的请求/响应处理能力,因此我们可以看见这个图中涉及的所有模块都是基于 pipleline,可以说 Droplet 的所有能力都是由其扩展而来 。这里我们先介绍下图中出现的几个中间件(Middleware,这是组成 pipepine 关键元素):
  1. HttpInfoInjector: 注入 http 相关的一些信息,如 requestid, http.Request 等
  2. RespReshape: 根据 handler 的响应结果来进行一些调整 , 包括:发生错误时设置上默认的错误码、错误信息;如果缺少响应 wrapper 时包装上配置好的 wrapper
  3. HttpInput: 如果你设置了 API 的输入参数类型,那么该中间件会自动根据 Content-Type、 struct tag 来读取对应的参数值,同时自动使用 validator 来检测参数错误
  4. TrafficLog: 如名字所示,如果你配置了响应的 logger,那么该中间件会执行日志记录 。请注意该中间件工作在其他默认中间件的后面、你的handler之前,因此它统计的耗时是你业务函数的真正耗时,而不包含其他中间件的耗时时间,你可以考虑通过 网关 或 Mesh 来记录完整的接口耗时 。
Tips
  • Middleware 处理请求和响应顺序是相反的——即第一个处理请求的中间件它会是最后一个处理响应的 。
  • 框架工作在应用层的优势有两点:
    • 与接入层框架解耦,保证绝项目代码可平滑 扩展/切换 其他接入层框架
    • 能够获取到结构化的接口 输入参数 与 输出参数 你可以对其进行更具精细的切面操作
GetStart
这里以 Gin 为例,其他框架类似 。
首先获取对应 wrapper 的 submodule:
go get github.com/shiningrush/droplet/wrapper/gin// if you want to ensure the droplet is latest, you can get dropletgo get github.com/shiningrush/droplet然后程序代码如下:
package mainimport ( "reflect" "github.com/gin-gonic/gin" "github.com/shiningrush/droplet/core" "github.com/shiningrush/droplet/wrapper" ginwrap "github.com/shiningrush/droplet/wrapper/gin")func main() { r := gin.Default()// 使用 wrapper 包装原始的 API r.POST("/json_input/:id", ginwrap.Wraps(JsonInputDo, wrapper.InputType(reflect.TypeOf(&JsonInput{})))) r.Run() // listen and serve on 0.0.0.0:8080 (for windows "localhost:8080")}type JsonInput struct {// 从 path 读取, 并且为必须参数 IDstring`auto_read:"id,path" json:"id" validate:"required"`// 从 header 读取, 并且为必须参数 Userstring`auto_read:"user,header" json:"user" validate:"required"`// 从 json unmarshal 后的ips字段读取 IPs[]string `json:"ips"`// 从 json unmarshal 后的 count 字段读取 Count int`json:"count"`// 读取原始的 http body , 接收参数类型必须为 []byte or io.ReadCloser Body[]byte`auto_read:"@body"`}func JsonInputDo(ctx core.Context) (interface{}, error) { input := ctx.Input().(*JsonInput) return input, nil}参数绑定如 Usage 一节中所展示的,我们可以通过 wrapper.InputType 选项来告诉 Droplet 是否期望自动化进行参数绑定,如果某些场景下你不需要从 Body 进行自动的参数绑定了,可以通过显式的选项来禁止它 , 如:
r.POST("/json_input/:id", ginwrap.Wraps(JsonInputDo, wrapper.InputType(reflect.TypeOf(&JsonInput{}), wrapper.DisableUnmarshalBody())))参数绑定的Tag格式如下:
auto_read: ({key},{source}) or @body其中值如下:
  • key: 用于到各个来源中匹配对应值
  • source: 可选值有 query, header, path, body(缺省默认)
  • @body: 特殊的取值 , 意味着获取原生的body作为字段值,此时你的字段类型应该为 []byte or io.ReadCloser
同时 Droplet 会自动使用 validator 对入参进行校验,因此你可以使用其 tag 来辅助验证参数合法性 。
响应整形通常来说API都会在响应的最外层进行一层包装,比如 Droplet 自带的 wrapper 如下所示:

推荐阅读