[goa]golang微服务框架学习(二)-- 代码自动生成
之前用过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
.
├── 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 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.go
get Name: adder
get Title: The adder API
get Desc: A teaser for goa
我们已经拿到用户在DSL里面定义的数据了(当然,这里DSL描述是直接写到同一个文件里面,省去了合并引入的过程)。
OK,代码的自动生成原理已经知道了,后面就要分析框架整体的架构和代码了。
[goa]golang微服务框架学习(二)-- 代码自动生成的更多相关文章
- [goa]golang微服务框架学习--安装使用
当项目逐渐变大之后,服务增多,开发人员增加,单纯的使用go来写服务会遇到风格不统一,开发效率上的问题. 之前研究go的微服务架构go-kit最让人头疼的就是定义服务之后,还要写很多重复的框架代码, ...
- [goa]golang微服务框架学习(三)-- 使用swagger-ui展示API
既然goa框架自动生成啦swagger-json文件,那么如何用swagger-ui展示出来呢? 这里分三步: 1.下载swagger-ui的web代码 2.添加swagger.json 和 swag ...
- 微服务框架学习二:Http调用
1. HTTP接口的意义 二进制接口使用的是java/hessian序列化协议,不能很好的与其他语言通信,虽然hessian也是一种跨语言的通用协议,但很多语言没有很好的实现该协议的产品.所以为了能够 ...
- kratos微服务框架学习笔记一(kratos-demo)
目录 kratos微服务框架学习笔记一(kratos-demo) kratos本体 demo kratos微服务框架学习笔记一(kratos-demo) 今年大部分时间飘过去了,没怎么更博和githu ...
- golang微服务框架go-micro 入门笔记2.4 go-micro service解读
本章节阐述go-micro 服务发现原理 go-micro架构 下图来自go-micro官方 阅读本文前你可能需要进行如下知识储备 golang分布式微服务框架go-micro 入门笔记1:搭建go- ...
- golang微服务框架go-micro 入门笔记2.3 micro工具之消息接收和发布
本章节阐述micro消息订阅和发布相关内容 阅读本文前你可能需要进行如下知识储备 golang分布式微服务框架go-micro 入门笔记1:搭建go-micro环境, golang微服务框架go-mi ...
- golang微服务框架go-micro 入门笔记2.2 micro工具之微应用利器micro web
micro web micro 功能非常强大,本文将详细阐述micro web 命令行的功能 阅读本文前你可能需要进行如下知识储备 golang分布式微服务框架go-micro 入门笔记1:搭建go- ...
- 【GoLang】golang 微服务框架 go-kit
golang-Microservice Go kit - A toolkit for microservices kubernetes go-kit_百度搜索 Peter Bourgon谈使用Go和& ...
- golang微服务框架go-micro 入门笔记1.搭建 go-micro环境
微服务的本质是让专业的人做专业的事情,做出更好的东西. golang具备高并发,静态编译等特性,在性能.安全等方面具备非常大的优势.go-micro是基于golang的微服务编程框架,go-micro ...
随机推荐
- 转@OneToMany或@ManyToOne的用法-annotation关系映射篇(上)
原文:http://blog.sina.com.cn/s/blog_6fef491d0100obdm.html 例如我们用一个例子来开启JPA的一对多和多对一的学习. 比如你去当当网上买书籍,当当网就 ...
- hbm.xml 详解总结
转自 http://blog.csdn.net/tuke_tuke/article/details/49717991 一.hibernate映射文件的作用: Hibernate映射文件是Hiberna ...
- 用VBS实现公司自动打卡
公司每天要求早晚打卡,方式是登录某一个页面,然后点击上面的圈圈,如下图: 每天都要打开书签,登录这个页面,然后打卡...这么重复性的工作,怎么能是程序员天天干的事情呢(其实是我太懒了),那么就尝试让程 ...
- cs11_c++_lab7
wcount.cc #include <iostream> #include <map> #include <string> #include <algori ...
- Word自动生成目录
博主最近在写报告的时候要在Word里面做个目录,再做个页码,然后上网搜了一些方法,非常零散,我弄了好久才弄好.在这里我把整套方法分享一下. 声明:内容完全独创! 工具:Word 2016. 效果:如下 ...
- PAT - 测试 01-复杂度2 Maximum Subsequence Sum (25分)
1, N2N_2N2, ..., NKN_KNK }. A continuous subsequence is defined to be { NiN_iNi, Ni+1N_{i ...
- arcgis中DEM如何生成等高线
地形图指比例尺大于1∶100万的着重表示地形的普通地图(根据经纬度进行分幅,常用有1:100万,1:50万,1比25万,1:15万,1:10万,1:5万等等).由于制图的区域范围比较小,因此能比较精确 ...
- MYSQL 中 update set from where 问题
MySQL 和 SQLSERVER不一样,update set from 一张表的时候 应该改为 UPDATE TABLE_AA INNER JOIN TABLE_BB ON TABLE_AA.ID ...
- FAST特征点检测features2D
#include <opencv2/core/core.hpp> #include <opencv2/features2d/features2d.hpp> #include & ...
- 移动端touch事件影响click事件以及在touchmove添加preventDefault导致页面无法滚动的解决方法
这两天自己在写一个手机网页,用到了触屏滑动的特效,就是往右滑动的时候左侧隐藏的菜单从左边划出来. 做完之后在手机原生浏览器中运行正常,但在QQ和微信中打开,发现touchmove只会触发一次,而且to ...