之前用过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 passdslengine.FailOnError(dslengine.Errors)// Now run the secondary DSLsdslengine.FailOnError(dslengine.Run()) files, err := genmain.Generate()dslengine.FailOnError(err)// We're donefmt.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 = namedesign.Design.DSLFunc = dslreturn design.Design
}

API函数的调用,生成了对应的Design实例,然后把元数据(这里是Name 和一个匿名函数) 都保存到内存里面了。

design对象是在程序初始化的时候(源码这里)就定义好了,并把实例注册到生成引擎中去(其实就是把对象实例传过去,方便后续调用)。

后面调用Generate函数来进行代码的自动生成。

大概就是这个意思,确实很巧妙,DSL定义的都是匿名全局变量,全局变量又是对已经定义好的元数据函数的调用(例如:API等),然后通过包引用把DSL文件包含进来,这样元数据都存在对应的实例内存中去了。

然后就可以随便怎么玩了,通过元数据的类型,来生成对应的文件,妙哉!

但是由于要支持各种嵌套、不同类型以及容错等等,所以实现写起来的代码非常多。

不过,我们可以按照这个思路,来实现一个简单的例子:

//main.gopackage mainimport "fmt"//定义DSL语言描述的结构体,用于保存DSL里面的数据
type APIDefinition struct {// Name of APIName string// Title of APITitle string// Description of APIDesc string// DSLFunc contains the DSL used to create this definition if anyDSLFunc func()
}//实现DSL对应的API,用于实例化
func API(name string, dsl func()) *APIDefinition {api := new(APIDefinition)api.Name = nameapi.DSLFunc = dsl//偷偷赋值g_api = apireturn 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() {//这里需要执行一下对应的DSLFuncg_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.go
get Name:  adder
get Title:  The adder API
get Desc:  A teaser for goa

我们已经拿到用户在DSL里面定义的数据了(当然,这里DSL描述是直接写到同一个文件里面,省去了合并引入的过程)。

OK,代码的自动生成原理已经知道了,后面就要分析框架整体的架构和代码了。

GO --微服务框架(二) goa相关推荐

  1. GO --微服务框架(一) goa

    当项目逐渐变大之后,服务增多,开发人员增加,单纯的使用go来写服务会遇到风格不统一,开发效率上的问题. 之前研究go的微服务架构go-kit最让人头疼的就是定义服务之后,还要写很多重复的框架代码,一直 ...

  2. (二)surging 微服务框架使用系列之surging 的准备工作consul安装(转载 https://www.cnblogs.com/alangur/p/8377977.html)...

    (二)surging 微服务框架使用系列之surging 的准备工作consul安装 suging 的注册中心支持consul跟zookeeper.因为consul跟zookeeper的配置都差不多, ...

  3. [goa]golang微服务框架学习--安装使用

    当项目逐渐变大之后,服务增多,开发人员增加,单纯的使用go来写服务会遇到风格不统一,开发效率上的问题. 之前研究go的微服务架构go-kit最让人头疼的就是定义服务之后,还要写很多重复的框架代码,一直 ...

  4. Kratos战神微服务框架(二)

    Kratos战神微服务框架(二) 目录 Kratos战神微服务框架(二) 项目结构 api编写 protobuf编写 使用makefile service层接口实现 biz层 data层 config ...

  5. golang微服务框架对比_Go语言开发的微服务框架,你了解多少?

    Go语言开发的微服务框架 1.项目名称:Istio 项目简介:Istio是由Google.IBM和Lyft开源的微服务管理.保护和监控框架.使用istio可以很简单的创建具有负载均衡.服务间认证.监控 ...

  6. go微服务框架go-micro深度学习(一) 整体架构介绍

    产品嘴里的一个小项目,从立项到开发上线,随着时间和需求的不断激增,会越来越复杂,变成一个大项目,如果前期项目架构没设计的不好,代码会越来越臃肿,难以维护,后期的每次产品迭代上线都会牵一发而动全身.项目 ...

  7. python 微服务框架_Python微服务框架NameKo 性能体验

    Nameko是Python下的一个微服务框架,小巧简洁,通过RabbitMq消息组件来实现RPC服务 Github:NameKo 一.准备工作 1.RabbitMq 使用docker安装 docker ...

  8. 基于thrift的微服务框架

    前一阵开源过一个基于spring-boot的rest微服务框架,今天再来一篇基于thrift的微服务加框,thrift是啥就不多了,大家自行百度或参考我之前介绍thrift的文章, thrift不仅支 ...

  9. 从 Spring Cloud 看一个微服务框架的「五脏六腑]

    https://webfe.kujiale.com/spring-could-heart/ Spring Cloud 是一个基于 Spring Boot 实现的微服务框架,它包含了实现微服务架构所需的 ...

最新文章

  1. 西安邮电大学计算机学院系主任,西安邮电大学计算机学院
  2. 实时把你的脸变成名画,手机摄像头新玩法
  3. 多采样率信号处理 ——信号的抽取与插值
  4. 通用串行总线集线器(Universal SerialBus HUB)什么是USB集线器(USB HUB)?什么是USB根集线器(USB ROOT HUB)?如何判断一个USB口是独立的还是集线器上的?
  5. python 量化交易_基于Python的量化交易工具清单(上)
  6. 安装mysql connector odbc后在控制面板 数据源下没有找到mysql的驱动
  7. DOS常用命令(从入门到精通)
  8. 基础练习 Sine之舞(最近FJ为他的奶牛们开设了数学分析课,FJ知道若要学好这门课,必须有一个好的三角函数基本功。所以他准备和奶牛们做一个“Sine之舞”的游戏,寓教于乐,提高奶牛们的计算能力。)
  9. 生活小窍门——》馒头又白又大
  10. ThinkPad E430光驱面板拆卸方法
  11. VMware虚拟机ubuntu指定使用主机的wifi无线网卡
  12. 74LS85 比较器 【数字电路】
  13. 前后端分离开发下的权限管控 :SpringSecurity 框架
  14. Efficient Exchange DP 二维DP
  15. 一文读懂,CPU、精简指令集、复杂指令集该如何理解?
  16. 服务端解决故障的处理思路
  17. HbuildX打h5包/web2app包注意事项
  18. 01 A股10个月争取翻10倍实盘操作记录(前言)
  19. 不爱打空格的小孟c语言,语言学专家:很多年轻人发信息不爱用句号,其实都是有原因的...
  20. 新手利用QQ群排名技术长期引流方法分享

热门文章

  1. 无线技巧:学会设置无线上网猫以及网卡
  2. 苹果logo_苹果,太会玩LOGO了~
  3. u8跳过环境检测工具 win7_用友环境检测工具
  4. 完整优雅的卸载腾讯云云服务器安全监控组件
  5. python带cookies发送post请求_Requests发送带cookies请求
  6. kafka在Windows系统下的安装出现的错误汇总:
  7. python 全栈开发,Day45(html介绍和head标签,body标签中相关标签)
  8. detached HEAD意义详解
  9. 瑜伽教学法 | <战士三口令>手把手教你精准口令的秘诀!
  10. 由于策略,导致磁盘处于脱机状态解决办法