mirror of
https://github.com/chai2010/advanced-go-programming-book.git
synced 2025-05-24 04:22:22 +00:00
fix wp
This commit is contained in:
parent
6cd14dc270
commit
7a0fbea431
@ -12,7 +12,7 @@
|
|||||||
|
|
||||||
*图 5-13 前后分离交互图*
|
*图 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模块一般采用下述划分方法:
|
||||||
|
|
||||||
@ -32,7 +32,7 @@
|
|||||||
|
|
||||||
*图 5-15 多协议示意图*
|
*图 5-15 多协议示意图*
|
||||||
|
|
||||||
这样我们controller中的入口函数就变成了下面这样:
|
这样我们Controller中的入口函数就变成了下面这样:
|
||||||
|
|
||||||
```go
|
```go
|
||||||
func CreateOrder(ctx context.Context, req *CreateOrderStruct) (
|
func CreateOrder(ctx context.Context, req *CreateOrderStruct) (
|
||||||
@ -42,9 +42,9 @@ func CreateOrder(ctx context.Context, req *CreateOrderStruct) (
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
CreateOrder有两个参数,ctx用来传入trace_id一类的需要串联请求的全局参数,req里存储了我们创建订单所需要的所有输入信息。返回结果是一个响应结构体和错误。可以认为,我们的代码运行到controller层之后,就没有任何与“协议”相关的代码了。在这里你找不到`http.Request`,也找不到`http.ResponseWriter`,也找不到任何与thrift或者gRPC相关的字眼。
|
CreateOrder有两个参数,ctx用来传入trace_id一类的需要串联请求的全局参数,req里存储了我们创建订单所需要的所有输入信息。返回结果是一个响应结构体和错误。可以认为,我们的代码运行到Controller层之后,就没有任何与“协议”相关的代码了。在这里你找不到`http.Request`,也找不到`http.ResponseWriter`,也找不到任何与thrift或者gRPC相关的字眼。
|
||||||
|
|
||||||
在协议(protocol)层,处理http协议的大概代码如下:
|
在协议(Protocol)层,处理http协议的大概代码如下:
|
||||||
|
|
||||||
```go
|
```go
|
||||||
// defined in protocol layer
|
// defined in protocol layer
|
||||||
@ -72,11 +72,11 @@ func HTTPCreateOrderHandler(wr http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
理论上我们可以用同一个请求结构体组合上不同的tag,来达到一个struct来给不同的协议复用的目的。不过遗憾的是在thrift中,请求结构体也是通过IDL生成的,其内容在自动生成的ttypes.go文件中,我们还是需要在thrift的入口将这个自动生成的struct映射到我们logic入口所需要的struct上。gRPC也是类似。这部分代码还是需要的。
|
理论上我们可以用同一个请求结构体组合上不同的tag,来达到一个结构体来给不同的协议复用的目的。不过遗憾的是在thrift中,请求结构体也是通过IDL生成的,其内容在自动生成的ttypes.go文件中,我们还是需要在thrift的入口将这个自动生成的结构体映射到我们logic入口所需要的结构体上。gRPC也是类似。这部分代码还是需要的。
|
||||||
|
|
||||||
聪明的读者可能已经可以看出来了,协议细节处理这一层实际上有大量重复劳动,每一个接口在协议这一层的处理,无非是把数据从协议特定的struct(例如`http.Request`,thrift的被包装过了) 读出来,再绑定到我们协议无关的struct上,再把这个struct映射到controller入口的struct上,这些代码实际上长得都差不多。差不多的代码都遵循着某种模式,那么我们可以对这些模式进行简单的抽象,用代码生成的方式,把繁复的协议处理代码从工作内容中抽离出去。
|
聪明的读者可能已经可以看出来了,协议细节处理这一层实际上有大量重复劳动,每一个接口在协议这一层的处理,无非是把数据从协议特定的结构体(例如`http.Request`,thrift的被包装过了) 读出来,再绑定到我们协议无关的结构体上,再把这个结构体映射到Controller入口的结构体上,这些代码实际上长得都差不多。差不多的代码都遵循着某种模式,那么我们可以对这些模式进行简单的抽象,用代码生成的方式,把繁复的协议处理代码从工作内容中抽离出去。
|
||||||
|
|
||||||
先来看看http对应的struct、thrift对应的struct和我们协议无关的struct分别长什么样子:
|
先来看看HTTP对应的结构体、thrift对应的结构体和我们协议无关的结构体分别长什么样子:
|
||||||
|
|
||||||
```go
|
```go
|
||||||
// http 请求结构体
|
// http 请求结构体
|
||||||
@ -106,7 +106,7 @@ type CreateOrderParams struct {
|
|||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
我们需要通过一个源struct来生成我们需要的http和thrift入口代码。再观察一下上面定义的三种struct,实际上我们只要能用一个struct生成thrift的IDL,以及http服务的“IDL(实际上就是带 json或form相关tag的struct定义)” 就可以了。这个初始的struct我们可以把struct上的http的tag和thrift的tag揉在一起:
|
我们需要通过一个源结构体来生成我们需要的HTTP和thrift入口代码。再观察一下上面定义的三种结构体,实际上我们只要能用一个结构体生成thrift的IDL,以及HTTP服务的“IDL(实际上就是带json或form相关tag的结构体定义)” 就可以了。这个初始的结构体我们可以把结构体上的HTTP的tag和thrift的tag揉在一起:
|
||||||
|
|
||||||
```go
|
```go
|
||||||
type FeatureSetParams struct {
|
type FeatureSetParams struct {
|
||||||
@ -118,15 +118,15 @@ type FeatureSetParams struct {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
然后通过代码生成把thrift的IDL和http的请求结构体都生成出来,如*图 5-16所示*
|
然后通过代码生成把thrift的IDL和HTTP的请求结构体都生成出来,如*图 5-16所示*
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
*图 5-16 通过Go代码定义struct生成项目入口*
|
*图 5-16 通过Go代码定义结构体生成项目入口*
|
||||||
|
|
||||||
至于用什么手段来生成,你可以通过Go语言内置的Parser读取文本文件中的Go源代码,然后根据AST来生成目标代码,也可以简单地把这个源struct和generator的代码放在一起编译,让struct作为generator的输入参数(这样会更简单一些),都是可以的。
|
至于用什么手段来生成,你可以通过Go语言内置的Parser读取文本文件中的Go源代码,然后根据AST来生成目标代码,也可以简单地把这个源结构体和Generator的代码放在一起编译,让结构体作为Generator的输入参数(这样会更简单一些),都是可以的。
|
||||||
|
|
||||||
当然这种思路并不是唯一选择,我们还可以通过解析thrift的IDL,生成一套http接口的struct。如果你选择这么做,那整个流程就变成了*图 5-17*所示。
|
当然这种思路并不是唯一选择,我们还可以通过解析thrift的IDL,生成一套HTTP接口的结构体。如果你选择这么做,那整个流程就变成了*图 5-17*所示。
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
@ -144,6 +144,6 @@ type FeatureSetParams struct {
|
|||||||
|
|
||||||
*图 5-18 加入中间件后的控制流*
|
*图 5-18 加入中间件后的控制流*
|
||||||
|
|
||||||
之前我们学习的中间件是和http协议强相关的,遗憾的是在thrift中看起来没有和http中对等的解决这些非功能性逻辑代码重复问题的中间件。所以我们在图上写`thrift stuff`。这些`stuff`可能需要你手写去实现,然后每次增加一个新的thrift接口,就需要去写一遍这些非功能性代码。
|
之前我们学习的中间件是和HTTP协议强相关的,遗憾的是在thrift中看起来没有和HTTP中对等的解决这些非功能性逻辑代码重复问题的中间件。所以我们在图上写`thrift stuff`。这些`stuff`可能需要你手写去实现,然后每次增加一个新的thrift接口,就需要去写一遍这些非功能性代码。
|
||||||
|
|
||||||
这也是很多企业项目所面临的真实问题,遗憾的是开源界并没有这样方便的多协议中间件解决方案。当然了,前面我们也说过,很多时候我们给自己保留的http接口只是用来做debug,并不会暴露给外人用。这种情况下,这些非功能性的代码只要在thrift的代码中完成即可。
|
这也是很多企业项目所面临的真实问题,遗憾的是开源界并没有这样方便的多协议中间件解决方案。当然了,前面我们也说过,很多时候我们给自己保留的HTTP接口只是用来做调试,并不会暴露给外人用。这种情况下,这些非功能性的代码只要在thrift的代码中完成即可。
|
||||||
|
Loading…
x
Reference in New Issue
Block a user