From 42f4b525fea9f404e440ba49685d8069b32995b6 Mon Sep 17 00:00:00 2001 From: Xargin Date: Mon, 24 Dec 2018 11:31:00 +0800 Subject: [PATCH] fix ch5 --- ch5-web/ch5-02-router.md | 10 ++++----- ch5-web/ch5-07-layout-of-web-project.md | 28 ++++++++++++++++++------- ch5-web/ch5-08-interface-and-web.md | 8 ++++--- ch5-web/ch5-09-gated-launch.md | 10 +++++++-- ch5-web/ch5-10-ext.md | 6 +++--- ch5-web/readme.md | 4 ++-- 6 files changed, 43 insertions(+), 23 deletions(-) diff --git a/ch5-web/ch5-02-router.md b/ch5-web/ch5-02-router.md index 730e80e..dd1bed1 100644 --- a/ch5-web/ch5-02-router.md +++ b/ch5-web/ch5-02-router.md @@ -153,7 +153,7 @@ type Router struct { } ``` -trees中的key即为http 1.1的RFC中定义的各种method,具体有: +`trees`中的`key`即为http 1.1的RFC中定义的各种方法,具体有: ```shell GET @@ -165,16 +165,16 @@ PATCH DELETE ``` -每一种method对应的都是一棵独立的压缩字典树,这些树彼此之间不共享数据。具体到我们上面用到的路由,PUT和GET是两棵树而非一棵。 +每一种方法对应的都是一棵独立的压缩字典树,这些树彼此之间不共享数据。具体到我们上面用到的路由,`PUT`和`GET`是两棵树而非一棵。 -简单来讲,某个method第一次插入的路由就会导致对应字典树的根节点被创建,我们按顺序,先是一个PUT: +简单来讲,某个方法第一次插入的路由就会导致对应字典树的根节点被创建,我们按顺序,先是一个`PUT`: ```go r := httprouter.New() r.PUT("/user/installations/:installation_id/repositories/:reposit", Hello) ``` -这样PUT对应的根节点就会被创建出来。把这棵PUT的树画出来: +这样`PUT`对应的根节点就会被创建出来。把这棵`PUT`的树画出来: ![put radix tree](../images/ch6-02-radix-put.png) @@ -197,7 +197,7 @@ indices: 子节点索引,当子节点为非参数类型,即本节点的 wild ``` -当然,PUT路由只有唯一的一条路径。接下来,我们以后续的多条GET路径为例,讲解子节点的插入过程。 +当然,`PUT`路由只有唯一的一条路径。接下来,我们以后续的多条GET路径为例,讲解子节点的插入过程。 ### 5.2.3.2 子节点插入 diff --git a/ch5-web/ch5-07-layout-of-web-project.md b/ch5-web/ch5-07-layout-of-web-project.md index d635227..b1507fd 100644 --- a/ch5-web/ch5-07-layout-of-web-project.md +++ b/ch5-web/ch5-07-layout-of-web-project.md @@ -6,10 +6,12 @@ 2. 视图(View) - 界面设计人员进行图形界面设计。 3. 模型(Model) - 程序员编写程序应有的功能(实现算法等等)、数据库专家进行数据管理和数据库设计(可以实现具体的功能)。 -随着时代的发展,前端也变成了越来越复杂的工程,为了更好地工程化,现在更为流行的一般是前后分离的架构。可以认为前后分离是把V层从MVC中抽离单独成为项目。这样一个后端项目一般就只剩下 M和C层了。前后端之间通过ajax来交互,有时候要解决跨域的问题,但也已经有了较为成熟的方案。下面是一个前后分离的系统的简易交互图。 +随着时代的发展,前端也变成了越来越复杂的工程,为了更好地工程化,现在更为流行的一般是前后分离的架构。可以认为前后分离是把V层从MVC中抽离单独成为项目。这样一个后端项目一般就只剩下 M和C层了。前后端之间通过ajax来交互,有时候要解决跨域的问题,但也已经有了较为成熟的方案。*图 5-13* 是一个前后分离的系统的简易交互图。 ![前后分离](../images/ch6-08-frontend-backend.png) +*图 5-13 前后分离交互图* + 图里的Vue和React是现在前端界比较流行的两个框架,因为我们的重点不在这里,所以前端项目内的组织我们就不强调了。事实上,即使是简单的项目,业界也并没有完全遵守MVC框架提出者对于M和C所定义的分工。有很多公司的项目会在controller层塞入大量的逻辑,在model层就只管理数据的存储。这往往来源于对于model层字面含义的某种擅自引申理解。认为字面意思,这一层就是处理某种建模,而模型是什么?就是数据呗! 这种理解显然是有问题的,业务流程也算是一种“模型”,是对真实世界用户行为或者既有流程的一种建模,并非只有按格式组织的数据才能叫模型。不过按照MVC的创始人的想法,我们如果把和数据打交道的代码还有业务流程全部塞进MVC里的M层的话,这个M层又会显得有些过于臃肿。对于复杂的项目,一个C和一个M层显然是不够用的,现在比较流行的纯后端API模块一般采用下述划分方法: @@ -18,14 +20,18 @@ 2. Logic/Service,逻辑(服务)层,一般是业务逻辑的入口,可以认为从这里开始,所有的请求参数一定是合法的。业务逻辑和业务流程也都在这一层中。常见的设计中会将该层称为 Business Rules。 3. DAO/Repository,这一层主要负责和数据、存储打交道。将下层存储以更简单的函数、接口形式暴露给 Logic 层来使用。负责数据的持久化工作。 -每一层都会做好自己的工作,然后用请求当前的上下文构造下一层工作所需要的结构体或其它类型参数,然后调用下一层的函数。在工作完成之后,再把处理结果一层层地传出到入口。 +每一层都会做好自己的工作,然后用请求当前的上下文构造下一层工作所需要的结构体或其它类型参数,然后调用下一层的函数。在工作完成之后,再把处理结果一层层地传出到入口,如*图 5-14所示*。 ![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) +*图 5-15 多协议示意图* + 这样我们controller中的入口函数就变成了下面这样: ```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) +*图 5-16 通过Go代码定义struct生成项目入口* + 至于用什么手段来生成,你可以通过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) +*图 5-17 也可以从thrift生成其它部分* + 看起来比之前的图顺畅一点,不过如果你选择了这么做,你需要自行对thrift的IDL进行解析,也就是相当于可能要手写一个thrift的IDL的Parser,虽然现在有Antlr或者peg能帮你简化这些Parser的书写工作,但在“解析”的这一步我们不希望引入太多的工作量,所以量力而行即可。 既然工作流已经成型,我们可以琢磨一下怎么让整个流程对用户更加友好。 比如在前面的生成环境引入Web页面,只要让用户点点鼠标就能生成SDK,这些就靠读者自己去探索了。 -虽然我们成功地使自己的项目在入口支持了多种交互协议,但是还有一些问题没有解决。本节中所叙述的分层没有将middleware作为项目的分层考虑进去。如果我们考虑middleware的话,请求的流程是什么样的? +虽然我们成功地使自己的项目在入口支持了多种交互协议,但是还有一些问题没有解决。本节中所叙述的分层没有将middleware作为项目的分层考虑进去。如果我们考虑中间件的话,请求的流程是什么样的?见*图 5-18*所示。 ![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的代码中完成即可。 diff --git a/ch5-web/ch5-08-interface-and-web.md b/ch5-web/ch5-08-interface-and-web.md index 9a90400..b1b24c2 100644 --- a/ch5-web/ch5-08-interface-and-web.md +++ b/ch5-web/ch5-08-interface-and-web.md @@ -74,12 +74,14 @@ type OrderCreator interface { 我们只要把之前写过的步骤函数签名都提到一个interface中,就可以完成抽象了。 -在进行抽象之前,我们应该想明白的一点是,引入interface对我们的系统本身是否有意义,这是要按照场景去进行分析的。假如我们的系统只服务一条产品线,并且内部的代码只是针对很具体的场景进行定制化开发,那么实际上引入interface是不会带来任何收益的。至于说是否方便测试,这一点我们会在之后的章节来讲。 +在进行抽象之前,我们应该想明白的一点是,引入interface对我们的系统本身是否有意义,这是要按照场景去进行分析的。假如我们的系统只服务一条产品线,并且内部的代码只是针对很具体的场景进行定制化开发,那么实际上引入接口是不会带来任何收益的。至于说是否方便测试,这一点我们会在之后的章节来讲。 -如果我们正在做的是平台系统,需要由平台来定义统一的业务流程和业务规范,那么基于interface的抽象就是有意义的。举个例子: +如果我们正在做的是平台系统,需要由平台来定义统一的业务流程和业务规范,那么基于接口的抽象就是有意义的。举个例子: ![interface-impl](../images/ch6-interface-impl.uml.png) +*图 5-19 实现公有的接口* + 平台需要服务多条业务线,但数据定义需要统一,所以希望都能走平台定义的流程。作为平台方,我们可以定义一套类似上文的interface,然后要求接入方的业务必须将这些interface都实现。如果interface中有其不需要的步骤,那么只要返回nil,或者忽略就好。 在业务进行迭代时,平台的代码是不用修改的,这样我们便把这些接入业务当成了平台代码的插件(plugin)引入进来了。如果没有interface的话,我们会怎么做? @@ -158,7 +160,7 @@ func BusinessProcess(bi BusinessInstance) { ## 5.8.4 interface的优缺点 -Go被人称道的最多的地方是其interface设计的正交性,模块之间不需要知晓相互的存在,A模块定义interface,B模块实现这个interface就可以。如果interface中没有A模块中定义的数据类型,那B模块中甚至都不用import A。比如标准库中的`io.Writer`: +Go被人称道的最多的地方是其接口设计的正交性,模块之间不需要知晓相互的存在,A模块定义接口,B模块实现这个接口就可以。如果接口中没有A模块中定义的数据类型,那B模块中甚至都不用`import A`。比如标准库中的`io.Writer`: ```go type Writer interface { diff --git a/ch5-web/ch5-09-gated-launch.md b/ch5-web/ch5-09-gated-launch.md index 14cf987..09553ad 100644 --- a/ch5-web/ch5-09-gated-launch.md +++ b/ch5-web/ch5-09-gated-launch.md @@ -17,6 +17,8 @@ ![online group](../images/ch5-online-group.png) +*图 5-20 分组部署* + 为什么要用2倍?这样能够保证我们不管有多少台机器,都不会把组划分得太多。例如1024台机器,实际上也就只需要1-2-4-8-16-32-64-128-256-512部署十次就可以全部部署完毕。 这样我们上线最开始影响到的用户在整体用户中占的比例也不大,比如 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) -如果采用随机策略,可能会出现像下图这样的问题: +*图 5-21 先set然后马上get* + +如果采用随机策略,可能会出现像*图 5-22*这样的问题: ![set 和 get 流程不应该因为灰度走到不同版本的 API](../images/ch5-set-time-line_2.png) +*图 5-22 先set然后马上get* + 举个具体的例子,网站的注册环节,可能有两套API,按照用户ID进行灰度,分别是不同的存取逻辑。如果存储时使用了V1版本的API而获取时使用V2版本的API,那么就可能出现用户注册成功后反而返回注册失败消息的诡异问题。 ## 5.9.3 如何实现一套灰度发布系统 diff --git a/ch5-web/ch5-10-ext.md b/ch5-web/ch5-10-ext.md index 23e1593..60c5432 100644 --- a/ch5-web/ch5-10-ext.md +++ b/ch5-web/ch5-10-ext.md @@ -1,7 +1,7 @@ # 5.10 补充说明 -现代的软件工程是离不开 Web 的,广义地来讲,Web 甚至可以不用非得基于 http 协议。只要是 CS 或者 BS 架构,都可以认为是 Web 系统。 +现代的软件工程是离不开Web的,广义地来讲,Web甚至可以不用非得基于http协议。只要是CS或者BS架构,都可以认为是Web系统。 -即使是在看起来非常封闭的游戏系统里,因为玩家们与日俱增的联机需求,也同样会涉及到远程通信,这里面也会涉及到很多 Web 方面的技术。 +即使是在看起来非常封闭的游戏系统里,因为玩家们与日俱增的联机需求,也同样会涉及到远程通信,这里面也会涉及到很多Web方面的技术。 -所以这个时代,Web 编程是一个程序员所必须接触的知识领域。无论你的目标是成为架构师,是去创业,或是去当技术顾问。Web 方面的知识都会成为你的硬通货。 +所以这个时代,Web编程是一个程序员所必须接触的知识领域。无论你的目标是成为架构师,是去创业,或是去当技术顾问。Web方面的知识都会成为你的硬通货。 diff --git a/ch5-web/readme.md b/ch5-web/readme.md index f427742..81f0de9 100644 --- a/ch5-web/readme.md +++ b/ch5-web/readme.md @@ -2,7 +2,7 @@ *不管何种编程语言,适合自己的就是最好的。不管何种编程语言,能稳定实现业务逻辑的就是最好的。世间编程语言千千万,世间程序猿万万千,能做到深入理解并应用的就是最好的。——kenrong* -本章将会阐述 go 在 Web 开发方面的现状,并以几个典型的开源 Web 框架为例,带大家深入 Web 框架本身的执行流程。 +本章将会阐述Go在Web开发方面的现状,并以几个典型的开源Web框架为例,带大家深入Web框架本身的执行流程。 -同时会介绍现代企业级 Web 开发面临的一些问题,以及在 go 中如何面对,并解决这些问题。 +同时会介绍现代企业级Web开发面临的一些问题,以及在Go中如何面对,并解决这些问题。