gini:一个让你更方便使用Gin框架的库

Gin框架是一个Go语言框架中非常受欢迎的一款。今天我们开放了一个gini库,结合了我们实际开发中的使用,描述一下我们为什么开发这个库,在开发过程中的权衡。

gini简介

gini库目前发布了v0.1.0版本,支持几个常见的功能:

  • 提供请求数据和返回数据打印记录中间件
  • 提供可Mock化Bind方式
  • 提供统一输出格式管理

目前gini库功能已经实际落地在开发过程中,在采用gin框架开发的程序中获得了应用。

设计思路

在开发调试过程中,对于前后端分离模式开发过程中部分初级开发者无法进行方便的调试,尤其是在某些特殊场景(如:蓝牙通讯场景)下。借助gini.DumpReqAndResp可以非常方便的打印各种信息。不过非常值得注意的是,这个操作存在严重的数据泄漏风险,是严禁发布至线上版本中运行的

1
2
r := gin.New()
r.Use(gini.DumpReqAndResp())

在实际开发测试中,通常我们进行HTTP请求测试一般会采用如下的形式编写代码:

1
2
3
4
5
6
7
r := gin.New()
...
w := httptest.NewRecorder()
req, _ := http.NewRequest("GET", "xxx", nil)
r.ServeHTTP(w, req)
assert.Equal(t, 200, w.Code)
...

但是在实际使用过程中,我们觉得在实际过程中,gin由于设计原因,无法进行更好的进行HandlerFunc分离处理。所以我们选择使用gini.Bindgini. JSONRenderWrap分离请求处理流程与返回数据流程。让业务代码无需关心核心数据,将HTTP请求转化为类似函数式的测试方式。以一个简单的内容为例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
package main

import (
"errors"

"github.com/gin-gonic/gin"
"github.com/ipfans/gini"
)

var (
ErrNotValid = errors.New("请求数据不正确")
)

// Echo data.
func Echo(c *gin.Context) error {
var data map[string]string
err := gini.Bind(c, &data)
if err != nil {
return ErrNotValid
}
c.Set("data", data)
return nil
}

func main() {
gin.SetMode(gin.ReleaseMode)
gini.RegisterError(ErrNotValid, 400, ErrNotValid.Error())

r := gin.Default()
r.Use(gini.DumpReqAndResp())
r.GET("/echo", gini.JSONRenderWrap(Echo))
r.Run()
}

测试过程中,借助gini.Bind可以替换的特性,我们可以直接进行数据的定义。比如,我们可以这样编写测试代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
func TestEcho(t *testing.T) {
type args struct {
reqBody string
}
tests := []struct {
name string
args args
want interface{}
wantErr bool
}{
{
"Normal",
args{
"{\"name\":\"kevin\"}",
},
map[string]string{"name": "kevin"},
false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
c := &gin.Context{}
b := &gini.MockJSONBinder{}
b.Body(tt.args.reqBody)
gini.SetBinder(b)
if err := Echo(c); (err != nil) != tt.wantErr {
t.Errorf("Echo() error = %v, wantErr %v", err, tt.wantErr)
}
if data, _ := c.Get("data"); !assert.Equal(t, tt.want, data) {
t.Errorf("Echo() want = %v, got %v", tt.want, data)
}
})
}
}

这个似乎看起来是比HTTP请求方式并没有什么太多区别,但是实际开发过程中,我们会遇到各种认证等等前提条件式验证,这在开发过程中常常需要做额外的fixtures帮助,但是借助这种方式,则可以更加方便的观察书输入输出,同时在测试过程中,绕过可能会出现的额外的数据内容。

同时你户注意到,我们通常在使用json格式作为返回数据,并且包含固定的返回数据格式,这也更方便的进行统一数据管理,方便修正数据输出格式(如:可以切换成yaml输出?切换字段内容和定义)。为了方便更好的兼容数据处理,提供了gini.RegisterError方法,该方法则更好的提供返回状态值与提示信息的定义。如果未进行定义,则默认status500msg默认为error.Error()

1
gini.RegisterError(ErrNotValid, 400, ErrNotValid.Error())

结尾

gini库是我们在实践中总结出来的一套合作规约,难免存在各种由于思路与技术存在的短板,如果你有更好的建议与思路,非常欢迎到issues中与我们一起讨论。