[goa]golang微服务框架学习(二)-- 代码自动生成
来源:互联网 发布:淘宝分销一件代发 编辑:程序博客网 时间:2024/06/05 09:18
之前用过go语言的反射来做一些代码生成,参考这篇。
但是这种方式,入侵太强,需要执行对应的申明调用, 所以对GOA框架的自动生成非常感兴趣,于是仔细研究了一下,发现用的比较巧妙, 这里先卖个关子,先看看生成的代码目录结构。
这里使用adder的desgin文件来生成:
package designimport ( . "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.gopackage mainimport ( "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 mainimport ( "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 designimport ( . "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.gopackage mainimport "fmt"//定义DSL语言描述的结构体,用于保存DSL里面的数据type APIDefinition struct { // Name of API Name string // Title of API 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 Title(val string) { 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 Title: ", g_api.Title) 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.goget Name: adderget Title: The adder APIget Desc: A teaser for goa
我们已经拿到用户在DSL里面定义的数据了(当然,这里DSL描述是直接写到同一个文件里面,省去了合并引入的过程)。
OK,代码的自动生成原理已经知道了,后面就要分析框架整体的架构和代码了。
- [goa]golang微服务框架学习(二)-- 代码自动生成
- [goa]golang微服务框架学习--安装使用
- Golang 微服务框架 Go kit 介绍
- 代码自动生成(二)
- 初学springcloud微服务框架(二)
- [golang]反射的用处--代码自动生成
- 代码自动生成框架----CodeAutomaticGenerationFramework
- 微服务(二)
- AOS 自动生成代码(二) Dao生成
- Dropwizard框架搭建微服务入门学习(maven)
- 25、SSH框架-Mybatis自动生成代码(7)
- 代码自动生成工具(二)-miniproto介绍
- php框架yii gii代码自动生成
- SpringMvc+Mybatis自动生成代码框架_1
- SSM框架----使用Generator自动生成代码
- Go实战--golang中使用Goji微框架(Goji+Mongodb构建微服务)
- 代码自动生成工具MyGeneration之二
- 代码自动生成工具MyGeneration之二
- uboot_配置过程
- 使用animate.css为你的网页添加动画
- Angular2 路由刷新404解决办法
- Android进阶篇【chapter-10】 RecyclerView的使用与RecyclerView多类型视图的构造
- Matlab 中的cell类型
- [goa]golang微服务框架学习(二)-- 代码自动生成
- 分数数列求和
- CSS基础02
- leetcode7: Reverse Integer
- WIKIOI 1001
- 欢迎使用CSDN-markdown编辑器
- 王朝 第九周 利润
- STM32学习笔记-Flash做为存储器储存数据
- Jquery技巧:使用ajax技术提交表单数据