1
0
mirror of https://github.com/chai2010/advanced-go-programming-book.git synced 2025-05-24 04:22:22 +00:00
This commit is contained in:
Xargin 2018-12-24 11:31:00 +08:00
parent 391651c917
commit 42f4b525fe
6 changed files with 43 additions and 23 deletions

View File

@ -153,7 +153,7 @@ type Router struct {
} }
``` ```
trees中的key即为http 1.1的RFC中定义的各种method,具体有: `trees`中的`key`即为http 1.1的RFC中定义的各种方法,具体有:
```shell ```shell
GET GET
@ -165,16 +165,16 @@ PATCH
DELETE DELETE
``` ```
每一种method对应的都是一棵独立的压缩字典树这些树彼此之间不共享数据。具体到我们上面用到的路由PUT和GET是两棵树而非一棵。 每一种方法对应的都是一棵独立的压缩字典树,这些树彼此之间不共享数据。具体到我们上面用到的路由,`PUT``GET`是两棵树而非一棵。
简单来讲,某个method第一次插入的路由就会导致对应字典树的根节点被创建我们按顺序先是一个PUT 简单来讲,某个方法第一次插入的路由就会导致对应字典树的根节点被创建,我们按顺序,先是一个`PUT`
```go ```go
r := httprouter.New() r := httprouter.New()
r.PUT("/user/installations/:installation_id/repositories/:reposit", Hello) r.PUT("/user/installations/:installation_id/repositories/:reposit", Hello)
``` ```
这样PUT对应的根节点就会被创建出来。把这棵PUT的树画出来 这样`PUT`对应的根节点就会被创建出来。把这棵`PUT`的树画出来:
![put radix tree](../images/ch6-02-radix-put.png) ![put radix tree](../images/ch6-02-radix-put.png)
@ -197,7 +197,7 @@ indices: 子节点索引,当子节点为非参数类型,即本节点的 wild
``` ```
当然PUT路由只有唯一的一条路径。接下来我们以后续的多条GET路径为例讲解子节点的插入过程。 当然,`PUT`路由只有唯一的一条路径。接下来我们以后续的多条GET路径为例讲解子节点的插入过程。
### 5.2.3.2 子节点插入 ### 5.2.3.2 子节点插入

View File

@ -6,10 +6,12 @@
2. 视图View - 界面设计人员进行图形界面设计。 2. 视图View - 界面设计人员进行图形界面设计。
3. 模型Model - 程序员编写程序应有的功能(实现算法等等)、数据库专家进行数据管理和数据库设计(可以实现具体的功能)。 3. 模型Model - 程序员编写程序应有的功能(实现算法等等)、数据库专家进行数据管理和数据库设计(可以实现具体的功能)。
随着时代的发展前端也变成了越来越复杂的工程为了更好地工程化现在更为流行的一般是前后分离的架构。可以认为前后分离是把V层从MVC中抽离单独成为项目。这样一个后端项目一般就只剩下 M和C层了。前后端之间通过ajax来交互有时候要解决跨域的问题但也已经有了较为成熟的方案。下面是一个前后分离的系统的简易交互图。 随着时代的发展前端也变成了越来越复杂的工程为了更好地工程化现在更为流行的一般是前后分离的架构。可以认为前后分离是把V层从MVC中抽离单独成为项目。这样一个后端项目一般就只剩下 M和C层了。前后端之间通过ajax来交互有时候要解决跨域的问题但也已经有了较为成熟的方案。*图 5-13* 是一个前后分离的系统的简易交互图。
![前后分离](../images/ch6-08-frontend-backend.png) ![前后分离](../images/ch6-08-frontend-backend.png)
*图 5-13 前后分离交互图*
图里的Vue和React是现在前端界比较流行的两个框架因为我们的重点不在这里所以前端项目内的组织我们就不强调了。事实上即使是简单的项目业界也并没有完全遵守MVC框架提出者对于M和C所定义的分工。有很多公司的项目会在controller层塞入大量的逻辑在model层就只管理数据的存储。这往往来源于对于model层字面含义的某种擅自引申理解。认为字面意思这一层就是处理某种建模而模型是什么就是数据呗 图里的Vue和React是现在前端界比较流行的两个框架因为我们的重点不在这里所以前端项目内的组织我们就不强调了。事实上即使是简单的项目业界也并没有完全遵守MVC框架提出者对于M和C所定义的分工。有很多公司的项目会在controller层塞入大量的逻辑在model层就只管理数据的存储。这往往来源于对于model层字面含义的某种擅自引申理解。认为字面意思这一层就是处理某种建模而模型是什么就是数据呗
这种理解显然是有问题的业务流程也算是一种“模型”是对真实世界用户行为或者既有流程的一种建模并非只有按格式组织的数据才能叫模型。不过按照MVC的创始人的想法我们如果把和数据打交道的代码还有业务流程全部塞进MVC里的M层的话这个M层又会显得有些过于臃肿。对于复杂的项目一个C和一个M层显然是不够用的现在比较流行的纯后端API模块一般采用下述划分方法 这种理解显然是有问题的业务流程也算是一种“模型”是对真实世界用户行为或者既有流程的一种建模并非只有按格式组织的数据才能叫模型。不过按照MVC的创始人的想法我们如果把和数据打交道的代码还有业务流程全部塞进MVC里的M层的话这个M层又会显得有些过于臃肿。对于复杂的项目一个C和一个M层显然是不够用的现在比较流行的纯后端API模块一般采用下述划分方法
@ -18,14 +20,18 @@
2. Logic/Service逻辑(服务)层,一般是业务逻辑的入口,可以认为从这里开始,所有的请求参数一定是合法的。业务逻辑和业务流程也都在这一层中。常见的设计中会将该层称为 Business Rules。 2. Logic/Service逻辑(服务)层,一般是业务逻辑的入口,可以认为从这里开始,所有的请求参数一定是合法的。业务逻辑和业务流程也都在这一层中。常见的设计中会将该层称为 Business Rules。
3. DAO/Repository这一层主要负责和数据、存储打交道。将下层存储以更简单的函数、接口形式暴露给 Logic 层来使用。负责数据的持久化工作。 3. DAO/Repository这一层主要负责和数据、存储打交道。将下层存储以更简单的函数、接口形式暴露给 Logic 层来使用。负责数据的持久化工作。
每一层都会做好自己的工作,然后用请求当前的上下文构造下一层工作所需要的结构体或其它类型参数,然后调用下一层的函数。在工作完成之后,再把处理结果一层层地传出到入口。 每一层都会做好自己的工作,然后用请求当前的上下文构造下一层工作所需要的结构体或其它类型参数,然后调用下一层的函数。在工作完成之后,再把处理结果一层层地传出到入口,如*图 5-14所示*
![controller-logic-dao](../images/ch6-08-controller-logic-dao.png) ![controller-logic-dao](../images/ch6-08-controller-logic-dao.png)
划分为CLD三层之后在C层之前我们可能还需要同时支持多种协议。本章前面讲到的thrift、gRPC和http并不是一定只选择其中一种有时我们需要支持其中的两种比如同一个接口我们既需要效率较高的thrift也需要方便debug的http入口。即除了CLD之外还需要一个单独的protocol层负责处理各种交互协议的细节。这样请求的流程会变成下面这样 *图 5-14 请求处理流程*
划分为CLD三层之后在C层之前我们可能还需要同时支持多种协议。本章前面讲到的thrift、gRPC和http并不是一定只选择其中一种有时我们需要支持其中的两种比如同一个接口我们既需要效率较高的thrift也需要方便debug的http入口。即除了CLD之外还需要一个单独的protocol层负责处理各种交互协议的细节。这样请求的流程会变成*图 5-15* 所示。
![control-flow](../images/ch6-08-control-flow.png) ![control-flow](../images/ch6-08-control-flow.png)
*图 5-15 多协议示意图*
这样我们controller中的入口函数就变成了下面这样 这样我们controller中的入口函数就变成了下面这样
```go ```go
@ -112,26 +118,32 @@ type FeatureSetParams struct {
} }
``` ```
然后通过代码生成把thrift的IDL和http的request struct都生成出来 然后通过代码生成把thrift的IDL和http的request struct都生成出来,如*图 5-16所示*
![code gen](../images/ch6-08-code-gen.png) ![code gen](../images/ch6-08-code-gen.png)
*图 5-16 通过Go代码定义struct生成项目入口*
至于用什么手段来生成你可以通过Go语言内置的Parser读取文本文件中的Go源代码然后根据AST来生成目标代码也可以简单地把这个源struct和generator的代码放在一起编译让struct作为generator的输入参数(这样会更简单一些),都是可以的。 至于用什么手段来生成你可以通过Go语言内置的Parser读取文本文件中的Go源代码然后根据AST来生成目标代码也可以简单地把这个源struct和generator的代码放在一起编译让struct作为generator的输入参数(这样会更简单一些),都是可以的。
当然这种思路并不是唯一选择我们还可以通过解析thrift的IDL生成一套http接口的struct。如果你选择这么做那整个流程就变成了这样: 当然这种思路并不是唯一选择我们还可以通过解析thrift的IDL生成一套http接口的struct。如果你选择这么做那整个流程就变成了*图 5-17*所示。
![code gen](../images/ch6-08-code-gen-2.png) ![code gen](../images/ch6-08-code-gen-2.png)
*图 5-17 也可以从thrift生成其它部分*
看起来比之前的图顺畅一点不过如果你选择了这么做你需要自行对thrift的IDL进行解析也就是相当于可能要手写一个thrift的IDL的Parser虽然现在有Antlr或者peg能帮你简化这些Parser的书写工作但在“解析”的这一步我们不希望引入太多的工作量所以量力而行即可。 看起来比之前的图顺畅一点不过如果你选择了这么做你需要自行对thrift的IDL进行解析也就是相当于可能要手写一个thrift的IDL的Parser虽然现在有Antlr或者peg能帮你简化这些Parser的书写工作但在“解析”的这一步我们不希望引入太多的工作量所以量力而行即可。
既然工作流已经成型,我们可以琢磨一下怎么让整个流程对用户更加友好。 既然工作流已经成型,我们可以琢磨一下怎么让整个流程对用户更加友好。
比如在前面的生成环境引入Web页面只要让用户点点鼠标就能生成SDK这些就靠读者自己去探索了。 比如在前面的生成环境引入Web页面只要让用户点点鼠标就能生成SDK这些就靠读者自己去探索了。
虽然我们成功地使自己的项目在入口支持了多种交互协议但是还有一些问题没有解决。本节中所叙述的分层没有将middleware作为项目的分层考虑进去。如果我们考虑middleware的话请求的流程是什么样的 虽然我们成功地使自己的项目在入口支持了多种交互协议但是还有一些问题没有解决。本节中所叙述的分层没有将middleware作为项目的分层考虑进去。如果我们考虑中间件的话,请求的流程是什么样的?见*图 5-18*所示。
![control flow 2](../images/ch6-08-control-flow-2.png) ![control flow 2](../images/ch6-08-control-flow-2.png)
之前我们学习的middleware是和http协议强相关的遗憾的是在thrift中看起来没有和http中对等的解决这些非功能性逻辑代码重复问题的middleware。所以我们在图上写`thrift stuff`。这些`stuff`可能需要你手写去实现然后每次增加一个新的thrift接口就需要去写一遍这些非功能性代码。 *图 5-18 加入中间件后的控制流*
这也是很多企业项目所面临的真实问题遗憾的是开源界并没有这样方便的多协议middleware解决方案。当然了前面我们也说过很多时候我们给自己保留的http接口只是用来做debug并不会暴露给外人用。这种情况下这些非功能性的代码只要在thrift的代码中完成即可。 之前我们学习的中间件是和http协议强相关的遗憾的是在thrift中看起来没有和http中对等的解决这些非功能性逻辑代码重复问题的middleware。所以我们在图上写`thrift stuff`。这些`stuff`可能需要你手写去实现然后每次增加一个新的thrift接口就需要去写一遍这些非功能性代码。
这也是很多企业项目所面临的真实问题遗憾的是开源界并没有这样方便的多协议中间件解决方案。当然了前面我们也说过很多时候我们给自己保留的http接口只是用来做debug并不会暴露给外人用。这种情况下这些非功能性的代码只要在thrift的代码中完成即可。

View File

@ -74,12 +74,14 @@ type OrderCreator interface {
我们只要把之前写过的步骤函数签名都提到一个interface中就可以完成抽象了。 我们只要把之前写过的步骤函数签名都提到一个interface中就可以完成抽象了。
在进行抽象之前我们应该想明白的一点是引入interface对我们的系统本身是否有意义这是要按照场景去进行分析的。假如我们的系统只服务一条产品线并且内部的代码只是针对很具体的场景进行定制化开发那么实际上引入interface是不会带来任何收益的。至于说是否方便测试,这一点我们会在之后的章节来讲。 在进行抽象之前我们应该想明白的一点是引入interface对我们的系统本身是否有意义这是要按照场景去进行分析的。假如我们的系统只服务一条产品线并且内部的代码只是针对很具体的场景进行定制化开发那么实际上引入接口是不会带来任何收益的。至于说是否方便测试,这一点我们会在之后的章节来讲。
如果我们正在做的是平台系统,需要由平台来定义统一的业务流程和业务规范,那么基于interface的抽象就是有意义的。举个例子: 如果我们正在做的是平台系统,需要由平台来定义统一的业务流程和业务规范,那么基于接口的抽象就是有意义的。举个例子:
![interface-impl](../images/ch6-interface-impl.uml.png) ![interface-impl](../images/ch6-interface-impl.uml.png)
*图 5-19 实现公有的接口*
平台需要服务多条业务线但数据定义需要统一所以希望都能走平台定义的流程。作为平台方我们可以定义一套类似上文的interface然后要求接入方的业务必须将这些interface都实现。如果interface中有其不需要的步骤那么只要返回nil或者忽略就好。 平台需要服务多条业务线但数据定义需要统一所以希望都能走平台定义的流程。作为平台方我们可以定义一套类似上文的interface然后要求接入方的业务必须将这些interface都实现。如果interface中有其不需要的步骤那么只要返回nil或者忽略就好。
在业务进行迭代时,平台的代码是不用修改的,这样我们便把这些接入业务当成了平台代码的插件(plugin)引入进来了。如果没有interface的话我们会怎么做 在业务进行迭代时,平台的代码是不用修改的,这样我们便把这些接入业务当成了平台代码的插件(plugin)引入进来了。如果没有interface的话我们会怎么做
@ -158,7 +160,7 @@ func BusinessProcess(bi BusinessInstance) {
## 5.8.4 interface的优缺点 ## 5.8.4 interface的优缺点
Go被人称道的最多的地方是其interface设计的正交性模块之间不需要知晓相互的存在A模块定义interfaceB模块实现这个interface就可以。如果interface中没有A模块中定义的数据类型那B模块中甚至都不用import A。比如标准库中的`io.Writer` Go被人称道的最多的地方是其接口设计的正交性模块之间不需要知晓相互的存在A模块定义接口B模块实现这个接口就可以。如果接口中没有A模块中定义的数据类型那B模块中甚至都不用`import A`。比如标准库中的`io.Writer`
```go ```go
type Writer interface { type Writer interface {

View File

@ -17,6 +17,8 @@
![online group](../images/ch5-online-group.png) ![online group](../images/ch5-online-group.png)
*图 5-20 分组部署*
为什么要用2倍这样能够保证我们不管有多少台机器都不会把组划分得太多。例如1024台机器实际上也就只需要1-2-4-8-16-32-64-128-256-512部署十次就可以全部部署完毕。 为什么要用2倍这样能够保证我们不管有多少台机器都不会把组划分得太多。例如1024台机器实际上也就只需要1-2-4-8-16-32-64-128-256-512部署十次就可以全部部署完毕。
这样我们上线最开始影响到的用户在整体用户中占的比例也不大,比如 1000 台机器的服务我们上线后如果出现问题也只影响1/1000的用户。如果10组完全平均分那一上线立刻就会影响1/10的用户1/10的业务出问题那可能对于公司来说就已经是一场不可挽回的事故了。 这样我们上线最开始影响到的用户在整体用户中占的比例也不大,比如 1000 台机器的服务我们上线后如果出现问题也只影响1/1000的用户。如果10组完全平均分那一上线立刻就会影响1/10的用户1/10的业务出问题那可能对于公司来说就已经是一场不可挽回的事故了。
@ -79,14 +81,18 @@ func isTrue(phone string) bool {
} }
``` ```
这种情况可以按照指定的百分比返回对应的true和false和上面的单纯按照概率的区别是这里我们需要调用方提供给我们一个输入参数我们以该输入参数作为源来计算哈希并以哈希后的结果来求模并返回结果。这样可以保证同一个用户的返回结果多次调用是一致的在下面这种场景下必须使用这种结果可预期的灰度算法 这种情况可以按照指定的百分比返回对应的true和false和上面的单纯按照概率的区别是这里我们需要调用方提供给我们一个输入参数我们以该输入参数作为源来计算哈希并以哈希后的结果来求模并返回结果。这样可以保证同一个用户的返回结果多次调用是一致的在下面这种场景下必须使用这种结果可预期的灰度算法,见*图 5-21*所示。
![set 和 get 流程不应该因为灰度走到不同版本的 API](../images/ch5-set-time-line.png) ![set 和 get 流程不应该因为灰度走到不同版本的 API](../images/ch5-set-time-line.png)
如果采用随机策略,可能会出现像下图这样的问题: *图 5-21 先set然后马上get*
如果采用随机策略,可能会出现像*图 5-22*这样的问题:
![set 和 get 流程不应该因为灰度走到不同版本的 API](../images/ch5-set-time-line_2.png) ![set 和 get 流程不应该因为灰度走到不同版本的 API](../images/ch5-set-time-line_2.png)
*图 5-22 先set然后马上get*
举个具体的例子网站的注册环节可能有两套API按照用户ID进行灰度分别是不同的存取逻辑。如果存储时使用了V1版本的API而获取时使用V2版本的API那么就可能出现用户注册成功后反而返回注册失败消息的诡异问题。 举个具体的例子网站的注册环节可能有两套API按照用户ID进行灰度分别是不同的存取逻辑。如果存储时使用了V1版本的API而获取时使用V2版本的API那么就可能出现用户注册成功后反而返回注册失败消息的诡异问题。
## 5.9.3 如何实现一套灰度发布系统 ## 5.9.3 如何实现一套灰度发布系统

View File

@ -2,7 +2,7 @@
*不管何种编程语言适合自己的就是最好的。不管何种编程语言能稳定实现业务逻辑的就是最好的。世间编程语言千千万世间程序猿万万千能做到深入理解并应用的就是最好的。——kenrong* *不管何种编程语言适合自己的就是最好的。不管何种编程语言能稳定实现业务逻辑的就是最好的。世间编程语言千千万世间程序猿万万千能做到深入理解并应用的就是最好的。——kenrong*
本章将会阐述 go 在 Web 开发方面的现状,并以几个典型的开源 Web 框架为例,带大家深入 Web 框架本身的执行流程。 本章将会阐述Go在Web开发方面的现状并以几个典型的开源Web框架为例带大家深入Web框架本身的执行流程。
同时会介绍现代企业级 Web 开发面临的一些问题,以及在 go 中如何面对,并解决这些问题。 同时会介绍现代企业级Web开发面临的一些问题以及在Go中如何面对,并解决这些问题。