设为首页 收藏本站
查看: 1019|回复: 0

[经验分享] [goa]golang微服务框架学习(二)

[复制链接]

尚未签到

发表于 2018-9-20 08:36:33 | 显示全部楼层 |阅读模式
  之前用过go语言的反射来做一些代码生成,参考这篇。
  但是这种方式,入侵太强,需要执行对应的申明调用, 所以对GOA框架的自动生成非常感兴趣,于是仔细研究了一下,发现用的比较巧妙, 这里先卖个关子,先看看生成的代码目录结构。
  这里使用adder的desgin文件来生成:
  

package design  

  
import (
  
.
"github.com/goadesign/goa/design"  
.
"github.com/goadesign/goa/design/apidsl"  
)
  

  

var _ = API("adder", func() {  
Title(
"The adder API")  
Description(
"A teaser for goa")  
Host(
"localhost:8080")  
Scheme(
"http")  
})
  

  

var _ = Resource("operands", func() {  
Action(
"add", func() {  
Routing(GET(
"add/:left/:right"))  
Description(
"add returns the sum of the left and right parameters in the response body")  
Params(func() {
  
Param(
"left", Integer, "Left operand")  
Param(
"right", Integer, "Right operand")  
})
  
Response(OK,
"text/plain")  
})
  

  
})
  

  然后生成对应的目录结构如下(如果不知道怎么生成,参考第一篇):
  

qpzhang@qpzhang:~/gocode/src/goa-adder $tree  
.
  
├── app
  
│   ├── contexts.go
  
│   ├── controllers.go
  
│   ├── hrefs.go
  
│   ├── media_types.go
  
│   ├── test
  
│   │   └── operands.go
  
│   └── user_types.go
  
├── client
  
│   ├── adder
-cli  
│   │   ├── commands.go
  
│   │   └── main.go
  
│   ├── client.go
  
│   ├── datatypes.go
  
│   └── operands.go
  
├── design
  
│   └── design.go
  
├── main.go
  
├── operands.go
  
└── swagger
  
├── swagger.json
  
└── swagger.yaml
  



  • APP目录,生成的框架相关代码,包含HTTP的路由
  • client目录,生成是go原生请求server的client测试程序,方便测试
  • swagger目录, 生成的swagger文件,可以用swagger来进行API的描述,这样不用自己写API接口文档了(cool)
  • 然后是main.go  , 程序的主入口
  • operands.go 业务逻辑代码,你需要在这里进行修改
  

//operands.go  

  
package main
  

  
import (
  
"github.com/goadesign/goa"
  
"goa-adder/app"
  
)
  

  
// OperandsController implements the operands resource.
  
type OperandsController struct {
  
*goa.Controller
  
}
  

  
// NewOperandsController creates a operands controller.
  
func NewOperandsController(service *goa.Service) *OperandsController {
  
return &OperandsController{Controller: service.NewController("OperandsController")}
  
}
  

  
// Add runs the add action.
  
func (c *OperandsController) Add(ctx *app.AddOperandsContext) error {
  
// TBD: implement   在这里写对应的函数逻辑
  
return nil
  
}
  

  非常棒,不用再重复写框架低层那些代码了(路由、编解码等等)。
  虽然之前也用过前公司的框架(那个是利用java的反射自动生成代码),但遇到自动生成代码这事儿,还是止不住兴奋。
  这里先不研究生成的框架代码,先研究一下利用go语言是如何自动生成的吧。
  一般自动生成可以分三个步骤:
  1)通过自描述语言来定义服务和接口(IDL,DSL都OK)
  2)解析描述语言,获取元数据(服务名称,接口名称,接口参数神马的)
  3)根据元数据,以及框架对应的模板,生成重复的代码部分
  我们来看GOA怎么做的,在goagen中加上 --debug 选项,可以保留中间文件。
  

//使用命令  
goagen --debug bootstrap -d goa-adder/design
  

  
//生成目录
  
qpzhang@qpzhang:~/gocode/src/goa-adder $tree -L 1
  
.
  
├── app
  
├── client
  
├── design
  
├── goagen009966755
  
├── goagen174102868
  
├── goagen511141286
  
├── goagen585483469
  
├── main.go
  
├── operands.go
  
└── swagger
  

  

├── goagen009966755  
│   ├── goagen
  
│   └── main.go
  
├── goagen174102868
  
│   ├── goagen
  
│   └── main.go
  
├── goagen511141286
  
│   ├── goagen
  
│   └── main.go
  
├── goagen585483469
  
│   ├── goagen
  
│   └── main.go
  

  我们看到,多出几个目录来,而且每个目录,都包含一个main.go和生成的可执行程序,我们随便进入一个目录看看:
  

//************************************************************************//  
// Code Generator
  
//
  
// Generated with goagen v0.0.1, command line:
  
// $ goagen
  
// --debug bootstrap -d goa-adder/design
  
//
  
// The content of this file is auto-generated, DO NOT MODIFY
  
//************************************************************************//
  

  
package main
  

  
import (
  
"github.com/goadesign/goa/goagen/gen_main"
  
"fmt"
  
"strings"
  
"github.com/goadesign/goa/dslengine"
  
_ "goa-adder/design"
  
)
  

  

  
func main() {
  
// Check if there were errors while running the first DSL pass
  
    dslengine.FailOnError(dslengine.Errors)
  

  
// Now run the secondary DSLs
  
    dslengine.FailOnError(dslengine.Run())
  

  
files, err :=genmain.Generate()
  
dslengine.FailOnError(err)
  

  
// We're done
  
fmt.Println(strings.Join(files, "\n"))
  
}
  

  然后看出一些端倪,它先把我们design目录整个包含进来,然后调用引擎里面的函数,进行代码的生成。
  这里再回到我们的DSL语言写的文件 design.go
  

package design  

  
import (
  
.
"github.com/goadesign/goa/design"  
.
"github.com/goadesign/goa/design/apidsl"  
)
  

  

var _ = API("adder", func() {  
Title("The adder API")
  
Description("A teaser for goa")
  
Host("localhost:8080")
  
Scheme("http")
  
})
  

  这里的API,其实就是在调用引擎里预先定义好的函数,在那里定义的呢?看源码:
  

func API(name string,dsl func()) *design.APIDefinition {  

if design.Design.Name != "" {  
dslengine.ReportError(
"multiple API definitions, only one is allowed")  

return nil  
}
  

if !dslengine.IsTopLevelDefinition() {  
dslengine.IncompatibleDSL()
  

return nil  
}
  

  

if name == "" {  
dslengine.ReportError(
"API name cannot be empty")  
}
  
design.Design.Name
= name  
design.Design.DSLFunc = dsl
  

return design.Design  
}
  

  API函数的调用,生成了对应的Design实例,然后把元数据(这里是Name 和一个匿名函数) 都保存到内存里面了。
  design对象是在程序初始化的时候(源码这里)就定义好了,并把实例注册到生成引擎中去(其实就是把对象实例传过去,方便后续调用)。
  后面调用Generate函数来进行代码的自动生成。
  大概就是这个意思,确实很巧妙,DSL定义的都是匿名全局变量,全局变量又是对已经定义好的元数据函数的调用(例如:API等),然后通过包引用把DSL文件包含进来,这样元数据都存在对应的实例内存中去了。
  然后就可以随便怎么玩了,通过元数据的类型,来生成对应的文件,妙哉!
  但是由于要支持各种嵌套、不同类型以及容错等等,所以实现写起来的代码非常多。
  不过,我们可以按照这个思路,来实现一个简单的例子:
  

//main.go  

  
package main
  

  
import "fmt"
  

  
//定义DSL语言描述的结构体,用于保存DSL里面的数据
  
type APIDefinition struct {
  
// Name of API
  
Name string

  
//>  
Title string
  
// Description of API
  
Desc string
  

  
// DSLFunc contains the DSL used to create this definition if any
  
    DSLFunc func()
  
}
  

  
//实现DSL对应的API,用于实例化
  
func API(name string, dsl func()) *APIDefinition {
  
api := new(APIDefinition)
  
api.Name = name
  
api.DSLFunc = dsl
  

  
//偷偷赋值
  
g_api = api
  

  
return api
  
}
  

  
//对应的Title赋值

  
func>  
if g_api != nil {
  
g_api.Title = val
  
}
  
}
  

  
func Description(d string) {
  
if g_api != nil {
  
g_api.Desc = d
  
}
  
}
  

  
//当前design的实例,这里用全局变量示意
  
var g_api *APIDefinition
  

  
//根据内存中的存储数据来进行代码生成
  
func generateTest() {
  
//这里需要执行一下对应的DSLFunc
  
    g_api.DSLFunc()
  
fmt.Println("get Name: ", g_api.Name)
  
fmt.Println("get>  
fmt.Println("get Desc: ", g_api.Desc)
  
}
  

  
//这里是DSL申明
  
var _ = API("adder", func() {
  
Title("The adder API")
  
Description("A teaser for goa")
  
})
  

  
func main() {
  
generateTest()
  
}
  

  最后运行一下执行的结果:
  

qpzhang@qpzhang:~/gocode/auto-gen $go run main.go  

get Name:  adder  

get>

get Desc:  A teaser for goa  

  我们已经拿到用户在DSL里面定义的数据了(当然,这里DSL描述是直接写到同一个文件里面,省去了合并引入的过程)。
  OK,代码的自动生成原理已经知道了,后面就要分析框架整体的架构和代码了。



运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-598548-1-1.html 上篇帖子: golang 请求带验证信息的坑 下篇帖子: golang代码覆盖率
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表