这周尝试连续更技术文。上期说到,Validator 组件能够将复杂的字段校验工作完全自动化完成,只需使用到 go-playground 中的 validator 包就能实现这一功能。今天继续聊聊其他组件。
Request Binder
这是一段摘自 gin 框架的代码:
1// Content-Type MIME of the most common data formats.
2const (
3 MIMEJSON = "application/json"
4 MIMEHTML = "text/html"
5 MIMEXML = "application/xml"
6 MIMEXML2 = "text/xml"
7 MIMEPlain = "text/plain"
8 MIMEPOSTFORM = "application/x-www-form-urlencoded"
9 MIMEMutipartPOSTForm = "multipart/form-data"
10 MIMEPROTOBUF = "application/x-protobuf"
11 MIMEMSGPACK = "application/x-msgpack"
12 MIMEMSGPACK2 = "application/msgpack"
13 MIMEYAML = "application/x-yaml"
14)
由于 gin 走的是 HTTP 协议,所以 Request binding 请求绑定,本质上是根据 HTTP header 中的 Contet-Type 的各种类型情况。
根据这个 header 我们可以判断用户传的 body 和 url 是什么东西。
下面就是我们在代码中,根据 http 的 contentType 做的简单的 switch-case:
1func Default(method, contentType string) Binding {
2 if method == http.MethodGet {
3 return Form
4 }
5
6 switch contentType {
7 case MIMEJSON:
8 return JSON
9 case MIMEXML, MIMEXML2:
10 return XML
11 case MIMEPROTOBUF:
12 return ProtoBuf
13 case MIMEMSGPACK, MIMEMSGPACK2:
14 return MsgPack
15 case MIMEYAML:
16 return YAML
17 case MIMEMultipartPOSTForm:
18 return FormMultipart
19 default: // case MIMEPOSTForm:
20 return Form
21 }
22}
最后,switch-case 返回的是一个 binding 结构。这个过程其实是设计模式中简单工厂模式的应用。
而不同的工厂实现很简单,其实就是某种 codec 的 unmarshal。
SQL Binder
我们在刚接触 Go 语言的时候,一般都是通过标准库的 database.sql 和 mysql 的驱动去写一些 db 相关的代码。
1func main() {
2 // db 是一个 sql.DB 类型的对象
3 // 该对象线程安全,且内部已包含了一个连接池
4 // 连接池的选项可以在 sql.DB 的方法中设置,这里为了简单省略了
5 db, err := sql.Open("mysql", "user:password@tcp(127.0.0.1:3306)/hello")
6 if err != nil {
7 log.Fatil(err)
8 }
9 defer db.Close()
10
11 var (
12 id int
13 name string
14 )
15 rows, err := db.Query("select id, name from users where id = ?", 1)
16 if err != nil {
17 log.Fatal(err)
18 }
19 defer rows.Close()
20
21 // 必须把 rows 里面的内容读完,或者显示调用 Close() 方法,
22 // 否则在 defer 的 rows.Close() 执行之前,连接永远不会释放
23 for rows.Next() {
24 err := rows.Scan(&id, &name)
25 if err != nil {
26 log.Fatal(err)
27 }
28 log.Println(id, name)
29 }
30}
- 标准库的 API 难用且容易犯错。比如 db 是可以不用关闭的。
- 有无数的新手 Gopher 倒在 sql.Rows 忘记关闭的坑下。
这里我们也可以看出来,Google 工程师平时不怎么写和业务打交道的代码的,这种 API 的设计明显是有问题的。
这就是为什么我们不用标准的 API 去写代码的原因。因此我们在公司里大多数开发工作都需要在这个基础上包一层,不可能直接去用这个。哪怕是对 rows.Close 的操作简单封装一层,也能防止业务开发人员在做开发的时候忘了这件事,这种事故太容易发生了。
还有一个比较麻烦的地方是,如果我们想要将 rows 和我们的结构体绑定也是比较麻烦,必须一行一行地用 rows.Next 遍历读取,再将所需的字段扫描出来,比如数组、字符串、结构体、map 绑定。
因此,我们在写业务代码的时候都会用一些社区维护好的库去做开发,比如 sqlx,以下摘自它的 README:
这段代码是和事物相关,看起来略微复杂,但普通的查询应该比这个更简单些。它给用户提供的 SQL 有以下几个功能,不过总的来说,实现很简单,核心只有一句话:占位符替换。
如果我们想要自己写代码来实现这种类似功能,就是个字符串遍历,只要能把特殊开头的单词提取出来就可以了,⽐如:开头,@开头,$开头。
go-micro
最后来聊一聊目前最流行的微服务框架 go-micro 实现的插件原理,以下摘自 go 夜读:
它的原理是在每一层定义了都定义了一个接口,也就是说为每个组件强定义了接口。
如果我们亲自去看了它的源码,会发现这个项目写得非常规范,是个非常好的开源项目学习范本。
还需要注意一点,虽然开源框架的设计比较好,但整体的结构还是略微复杂的。我们在学习开源框架的时候不必完整地梳理流程,看每一层的抽象就好了;而如果真正用到这个框架的时候才需要深入去看。
OK,到此为止,常见的 web 框架原理就讲解完了,你学会了吗。