Skip to content

Commit

Permalink
web -> Web
Browse files Browse the repository at this point in the history
  • Loading branch information
cch123 committed Dec 19, 2018
1 parent 5d91a93 commit d5a3c92
Show file tree
Hide file tree
Showing 14 changed files with 48 additions and 48 deletions.
16 changes: 8 additions & 8 deletions SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,14 +44,14 @@
* [4.9 补充说明](ch4-rpc/ch4-09-ext.md)
* [第5章 Go和Web](ch5-web/readme.md)
* [5.1 Web开发简介](ch5-web/ch5-01-introduction.md)
* [5.2 Router请求路由](ch5-web/ch5-02-router.md)
* [5.3 Middleware中间件](ch5-web/ch5-03-middleware.md)
* [5.4 Validator请求校验](ch5-web/ch5-04-validator.md)
* [5.5 Database和数据库打交道](ch5-web/ch5-05-database.md)
* [5.6 Ratelimit 服务流量限制](ch5-web/ch5-06-ratelimit.md)
* [5.7 Layout大型web项目分层](ch5-web/ch5-07-layout-of-web-project.md)
* [5.8 interface 和 table-driven 开发](ch5-web/ch5-08-interface-and-web.md)
* [5.9 灰度发布和 A/B test](ch5-web/ch5-09-gated-launch.md)
* [5.2 请求路由](ch5-web/ch5-02-router.md)
* [5.3 中间件](ch5-web/ch5-03-middleware.md)
* [5.4 请求校验](ch5-web/ch5-04-validator.md)
* [5.5 和数据库打交道](ch5-web/ch5-05-database.md)
* [5.6 服务流量限制](ch5-web/ch5-06-ratelimit.md)
* [5.7 大型Web项目分层](ch5-web/ch5-07-layout-of-web-project.md)
* [5.8 接口和表驱动开发](ch5-web/ch5-08-interface-and-web.md)
* [5.9 灰度发布和A/B测试](ch5-web/ch5-09-gated-launch.md)
* [5.10 补充说明](ch5-web/ch5-10-ext.md)
* [第6章 分布式系统](ch6-cloud/readme.md)
* [6.1 分布式 id 生成器](ch6-cloud/ch6-01-dist-id.md)
Expand Down
22 changes: 11 additions & 11 deletions ch5-web/ch5-01-introduction.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# 5.1 web 开发简介
# 5.1 Web 开发简介

因为Go的 `net/http`包提供了基础的路由函数组合与丰富的功能函数。所以在社区里流行一种用Go编写api不需要框架的观点;在我们看来,如果你的项目的路由在个位数、URI 固定且不通过 URI 来传递参数,那么确实使用官方库也就足够。但在复杂场景下,官方的 http 库还是有些力有不逮。例如下面这样的路由:
因为Go的`net/http`包提供了基础的路由函数组合与丰富的功能函数。所以在社区里流行一种用Go编写api不需要框架的观点;在我们看来,如果你的项目的路由在个位数、URI固定且不通过URI来传递参数,那么确实使用官方库也就足够。但在复杂场景下,官方的http库还是有些力有不逮。例如下面这样的路由:

```
GET /card/:id
Expand All @@ -18,9 +18,9 @@ Go的Web框架大致可以分为这么两类:
1. Router框架
2. MVC类框架

在框架的选择上,大多数情况下都是依照个人的喜好和公司的技术栈。例如公司有很多技术人员是PHP出身,那么他们一定会非常喜欢像beego 这样的框架,但如果公司有很多 C 程序员,那么他们的想法可能是越简单越好。比如很多大厂的 C 程序员甚至可能都会去用 C 去写很小的 CGI 程序,他们可能本身并没有什么意愿去学习MVC或者更复杂的 web 框架,他们需要的只是一个非常简单的路由(甚至连路由都不需要,只需要一个基础的HTTP协议处理库来帮他省掉没什么意思的体力劳动)。
在框架的选择上,大多数情况下都是依照个人的喜好和公司的技术栈。例如公司有很多技术人员是PHP出身,那么他们一定会非常喜欢像beego这样的框架,但如果公司有很多C程序员,那么他们的想法可能是越简单越好。比如很多大厂的C程序员甚至可能都会去用 C 去写很小的CGI程序,他们可能本身并没有什么意愿去学习MVC或者更复杂的Web框架,他们需要的只是一个非常简单的路由(甚至连路由都不需要,只需要一个基础的HTTP协议处理库来帮他省掉没什么意思的体力劳动)。

Go的 net/http 包提供的就是这样的基础功能,写一个 http echo server 只需要30s。
Go的`net/http`包提供的就是这样的基础功能,写一个简单的`http echo server`只需要30s。

```go
//brief_intro/echo.go
Expand Down Expand Up @@ -50,7 +50,7 @@ func main() {

```

如果你过了30s还没有完成这个程序,请检查一下你自己的打字速度是不是慢了(开个玩笑 :D)。这个例子是为了说明在Go中写一个HTTP协议的小程序有多么简单。如果你面临的情况比较复杂,例如几十个接口的企业级应用,直接用 net/http 库就显得不太合适了。
如果你过了30s还没有完成这个程序,请检查一下你自己的打字速度是不是慢了(开个玩笑 :D)。这个例子是为了说明在Go中写一个HTTP协议的小程序有多么简单。如果你面临的情况比较复杂,例如几十个接口的企业级应用,直接用`net/http`库就显得不太合适了。

我们来看看开源社区中一个 kafka 监控项目中的做法:

Expand All @@ -69,7 +69,7 @@ func NewHttpServer(app *ApplicationContext) (*HttpServer, error) {
}
```

上面这段代码来自大名鼎鼎的 linkedin 公司的 kafka 监控项目 Burrow,没有使用任何 router 框架,只使用了 net/http。只看上面这段代码似乎非常优雅,我们的项目里大概只有这五个简单的 URI,所以我们提供的服务就是下面这个样子:
上面这段代码来自大名鼎鼎的linkedin公司的kafka监控项目 Burrow,没有使用任何router框架,只使用了`net/http`。只看上面这段代码似乎非常优雅,我们的项目里大概只有这五个简单的 URI,所以我们提供的服务就是下面这个样子:

```go
/
Expand All @@ -79,7 +79,7 @@ func NewHttpServer(app *ApplicationContext) (*HttpServer, error) {
/v2/zookeeper
```

如果你确实这么想的话就被骗了。我们再进 handleKafka 这个函数一探究竟:
如果你确实这么想的话就被骗了。我们再进`handleKafka()`这个函数一探究竟:

```go
func handleKafka(app *ApplicationContext, w http.ResponseWriter, r *http.Request) (int, string) {
Expand Down Expand Up @@ -146,11 +146,11 @@ func handleKafka(app *ApplicationContext, w http.ResponseWriter, r *http.Request
}
```

因为默认的net/http包中的 mux 不支持带参数的路由,所以Burrow 这个项目使用了非常蹩脚的字符串 Split 和乱七八糟的 switch case 来达到自己的目的,但实际上却让本来应该很集中的路由管理逻辑变得复杂,散落在系统的各处,难以维护和管理。如果读者细心地看过这些代码之后,可能会发现其它的几个 handler 函数逻辑上较简单,最复杂的也就是这个 handleKafka。但实际上我们的系统总是从这样微不足道的混乱开始积少成多,最终变得难以收拾。
因为默认的`net/http`包中的mux不支持带参数的路由,所以Burrow这个项目使用了非常蹩脚的字符串 Split 和乱七八糟的 `switch case`来达到自己的目的,但实际上却让本来应该很集中的路由管理逻辑变得复杂,散落在系统的各处,难以维护和管理。如果读者细心地看过这些代码之后,可能会发现其它的几个handler函数逻辑上较简单,最复杂的也就是这个handleKafka。但实际上我们的系统总是从这样微不足道的混乱开始积少成多,最终变得难以收拾。

根据我们的经验,简单地来说,只要你的路由带有参数,并且这个项目的 api 数目超过了 10,就尽量不要使用 net/http 中默认的路由。在Go开源界应用最广泛的 router 是 httpRouter,很多开源的 router 框架都是基于 httpRouter 进行一定程度的改造的成果。关于 httpRouter 路由的原理,会在本章节的 router 一节中进行详细的阐释
根据我们的经验,简单地来说,只要你的路由带有参数,并且这个项目的api数目超过了10,就尽量不要使用`net/http`中默认的路由。在Go开源界应用最广泛的router是httpRouter,很多开源的router框架都是基于httpRouter进行一定程度的改造的成果。关于httpRouter路由的原理,会在本章节的router一节中进行详细的阐释

再来回顾一下文章开头说的,开源界有这么几种框架,第一种是对 httpRouter 进行简单的封装,然后提供定制的 middleware 和一些简单的小工具集成比如 gin,主打轻量,易学,高性能。第二种是借鉴其它语言的编程风格的一些 MVC 类框架,例如 beego,方便从其它语言迁移过来的程序员快速上手,快速开发。还有一些框架功能更为强大,除了 db 设计,大部分代码直接生成,例如 goa。不管哪种框架,适合开发者背景的就是最好的。
再来回顾一下文章开头说的,开源界有这么几种框架,第一种是对httpRouter进行简单的封装,然后提供定制的middleware和一些简单的小工具集成比如gin,主打轻量,易学,高性能。第二种是借鉴其它语言的编程风格的一些MVC类框架,例如beego,方便从其它语言迁移过来的程序员快速上手,快速开发。还有一些框架功能更为强大,除了数据库schema设计,大部分代码直接生成,例如goa。不管哪种框架,适合开发者背景的就是最好的。

本章的内容除了会展开讲解 router 和 middleware 的原理外,还会以现在工程界面临的问题结合 Go 来进行一些实践性的说明。希望能够对没有接触过相关内容的读者有所帮助。
本章的内容除了会展开讲解router和middleware的原理外,还会以现在工程界面临的问题结合Go来进行一些实践性的说明。希望能够对没有接触过相关内容的读者有所帮助。

6 changes: 3 additions & 3 deletions ch5-web/ch5-02-router.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# 5.2 router 请求路由

在常见的 web 框架中,router 是必备的组件。golang 圈子里 router 也时常被称为 http 的 multiplexer。在上一节中我们通过对 Burrow 代码的简单学习,已经知道如何用 http 标准库中内置的 mux 来完成简单的路由功能了。如果开发 web 系统对路径中带参数没什么兴趣的话,用 http 标准库中的 mux 就可以。
在常见的 Web 框架中,router 是必备的组件。golang 圈子里 router 也时常被称为 http 的 multiplexer。在上一节中我们通过对 Burrow 代码的简单学习,已经知道如何用 http 标准库中内置的 mux 来完成简单的路由功能了。如果开发 Web 系统对路径中带参数没什么兴趣的话,用 http 标准库中的 mux 就可以。

restful 是几年前刮起的 API 设计风潮,在 restful 中除了 GET 和 POST 之外,还使用了 http 协议定义的几种其它的标准化语义。具体包括:

Expand Down Expand Up @@ -36,7 +36,7 @@ DELETE /user/starred/:owner/:repo

## 5.2.1 httprouter

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

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

Expand Down Expand Up @@ -101,7 +101,7 @@ r.PanicHandler = func(w http.ResponseWriter, r *http.Request, c interface{}) {
}
```

目前开源界最为流行(star 数最多)的 web 框架 [gin](https://github.com/gin-gonic/gin) 使用的就是 httprouter 的变种。
目前开源界最为流行(star 数最多)的Web框架 [gin](https://github.com/gin-gonic/gin) 使用的就是 httprouter 的变种。

## 5.2.2 原理

Expand Down
8 changes: 4 additions & 4 deletions ch5-web/ch5-03-middleware.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# 5.3 middleware 中间件

本章将对现在流行的 web 框架中的中间件技术原理进行分析,并介绍如何使用中间件技术将业务和非业务代码功能进行解耦。
本章将对现在流行的Web框架中的中间件技术原理进行分析,并介绍如何使用中间件技术将业务和非业务代码功能进行解耦。

## 5.3.1 代码泥潭

Expand All @@ -21,7 +21,7 @@ func main() {
}
```

这是一个典型的 web 服务,挂载了一个简单的路由。我们的线上服务一般也是从这样简单的服务开始逐渐拓展开去的。
这是一个典型的 Web 服务,挂载了一个简单的路由。我们的线上服务一般也是从这样简单的服务开始逐渐拓展开去的。

现在突然来了一个新的需求,我们想要统计之前写的 hello 服务的处理耗时,需求很简单,我们对上面的程序进行少量修改:

Expand Down Expand Up @@ -92,7 +92,7 @@ func helloHandler(wr http.ResponseWriter, r *http.Request) {
}
```

修改到这里,本能地发现我们的开发工作开始陷入了泥潭。无论未来对我们的这个 web 系统有任何其它的非功能或统计需求,我们的修改必然牵一发而动全身。只要增加一个非常简单的非业务统计,我们就需要去几十个 handler 里增加这些业务无关的代码。虽然一开始我们似乎并没有做错,但是显然随着业务的发展,我们的行事方式让我们陷入了代码的泥潭。
修改到这里,本能地发现我们的开发工作开始陷入了泥潭。无论未来对我们的这个 Web 系统有任何其它的非功能或统计需求,我们的修改必然牵一发而动全身。只要增加一个非常简单的非业务统计,我们就需要去几十个 handler 里增加这些业务无关的代码。虽然一开始我们似乎并没有做错,但是显然随着业务的发展,我们的行事方式让我们陷入了代码的泥潭。

## 5.3.2 使用 middleware 剥离非业务逻辑

Expand Down Expand Up @@ -276,7 +276,7 @@ throttler.go
=> 通过定长大小的 channel 存储 token,并通过这些 token 对接口进行限流
```

每一个 web 框架都会有对应的 middleware 组件,如果你有兴趣,也可以向这些项目贡献有用的 middleware,只要合理一般项目的维护人也愿意合并你的 pull request。
每一个 Web 框架都会有对应的 middleware 组件,如果你有兴趣,也可以向这些项目贡献有用的 middleware,只要合理一般项目的维护人也愿意合并你的 pull request。

比如开源界很火的 gin 这个框架,就专门为用户贡献的 middleware 开了一个仓库:

Expand Down
4 changes: 2 additions & 2 deletions ch5-web/ch5-04-validator.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

![validate 流程](../images/ch6-04-validate.jpg)

实际上这是一个语言无关的场景,需要进行字段校验的情况有很多,web 系统的 Form/json 提交只是一个典型的例子。我们用 go 来写一个类似上图的校验 demo。然后研究怎么一步步对其进行改进。
实际上这是一个语言无关的场景,需要进行字段校验的情况有很多,Web系统的form/json提交只是一个典型的例子。我们用go来写一个类似上图的校验demo。然后研究怎么一步步对其进行改进。

## 5.4.1 重构请求校验函数

Expand Down Expand Up @@ -233,6 +233,6 @@ func main() {

这里我们简单地对 eq=x 和 email 这两个 tag 进行了支持,读者可以对这个程序进行简单的修改以查看具体的 validate 效果。为了演示精简掉了错误处理和复杂 case 的处理,例如 reflect.Int8/16/32/64,reflect.Ptr 等类型的处理,如果给生产环境编写 validate 库的话,请务必做好功能的完善和容错。

在前一小节中介绍的 validator 组件在功能上要远比我们这里的 demo 复杂的多。但原理很简单,就是用 reflect 对 struct 进行树形遍历。有心的读者这时候可能会产生一个问题,我们对 struct 进行 validate 时大量使用了 reflect,而 go 的 reflect 在性能上不太出众,有时甚至会影响到我们程序的性能。这样的考虑确实有一些道理,但需要对 struct 进行大量校验的场景往往出现在 web 服务,这里并不一定是程序的性能瓶颈所在,实际的效果还是要从 pprof 中做更精确的判断。
在前一小节中介绍的 validator 组件在功能上要远比我们这里的 demo 复杂的多。但原理很简单,就是用 reflect 对 struct 进行树形遍历。有心的读者这时候可能会产生一个问题,我们对 struct 进行 validate 时大量使用了 reflect,而 go 的 reflect 在性能上不太出众,有时甚至会影响到我们程序的性能。这样的考虑确实有一些道理,但需要对 struct 进行大量校验的场景往往出现在 Web 服务,这里并不一定是程序的性能瓶颈所在,实际的效果还是要从 pprof 中做更精确的判断。

如果基于反射的 validator 真的成为了你服务的性能瓶颈怎么办?现在也有一种思路可以避免反射:使用 golang 内置的 parser 对源代码进行扫描,然后根据 struct 的定义生成校验代码。我们可以将所有需要校验的结构体放在单独的 package 内。这就交给读者自己去探索了。
2 changes: 1 addition & 1 deletion ch5-web/ch5-05-database.md
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ func main() {

## 5.5.2 提高生产效率的 ORM 和 SQL Builder

在 web 开发领域常常提到的 ORM 是什么?我们先看看万能的维基百科:
在Web开发领域常常提到的ORM是什么?我们先看看万能的维基百科:

```
对象关系映射(英语:Object Relational Mapping,简称ORM,或O/RM,或O/R mapping),
Expand Down
Loading

0 comments on commit d5a3c92

Please sign in to comment.