之前用过go语言的反射来做一些代码生成,参考这篇

但是这种方式,入侵太强,需要执行对应的申明调用, 所以对GOA框架的自动生成非常感兴趣,于是仔细研究了一下,发现用的比较巧妙, 这里先卖个关子,先看看生成的代码目录结构。

这里使用adder的desgin文件来生成:

  1. package design
  2.  
  3. import (
  4. . "github.com/goadesign/goa/design"
  5. . "github.com/goadesign/goa/design/apidsl"
  6. )
  7.  
  8. var _ = API("adder", func() {
  9. Title("The adder API")
  10. Description("A teaser for goa")
  11. Host("localhost:8080")
  12. Scheme("http")
  13. })
  14.  
  15. var _ = Resource("operands", func() {
  16. Action("add", func() {
  17. Routing(GET("add/:left/:right"))
  18. Description("add returns the sum of the left and right parameters in the response body")
  19. Params(func() {
  20. Param("left", Integer, "Left operand")
  21. Param("right", Integer, "Right operand")
  22. })
  23. Response(OK, "text/plain")
  24. })
  25.  
  26. })

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

  1. qpzhang@qpzhang:~/gocode/src/goa-adder $tree
  2. .
  3. ├── app
  4.    ├── contexts.go
  5.    ├── controllers.go
  6.    ├── hrefs.go
  7.    ├── media_types.go
  8.    ├── test
  9.       └── operands.go
  10.    └── user_types.go
  11. ├── client
  12.    ├── adder-cli
  13.       ├── commands.go
  14.       └── main.go
  15.    ├── client.go
  16.    ├── datatypes.go
  17.    └── operands.go
  18. ├── design
  19.    └── design.go
  20. ├── main.go
  21. ├── operands.go
  22. └── swagger
  23. ├── swagger.json
  24. └── swagger.yaml
  • APP目录,生成的框架相关代码,包含HTTP的路由
  • client目录,生成是go原生请求server的client测试程序,方便测试
  • swagger目录, 生成的swagger文件,可以用swagger来进行API的描述,这样不用自己写API接口文档了(cool)
  • 然后是main.go  , 程序的主入口
  • operands.go 业务逻辑代码,你需要在这里进行修改
  1. //operands.go
  2.  
  3. package main
  4.  
  5. import (
  6. "github.com/goadesign/goa"
  7. "goa-adder/app"
  8. )
  9.  
  10. // OperandsController implements the operands resource.
  11. type OperandsController struct {
  12. *goa.Controller
  13. }
  14.  
  15. // NewOperandsController creates a operands controller.
  16. func NewOperandsController(service *goa.Service) *OperandsController {
  17. return &OperandsController{Controller: service.NewController("OperandsController")}
  18. }
  19.  
  20. // Add runs the add action.
  21. func (c *OperandsController) Add(ctx *app.AddOperandsContext) error {
  22. // TBD: implement 在这里写对应的函数逻辑
  23. return nil
  24. }

非常棒,不用再重复写框架低层那些代码了(路由、编解码等等)。

虽然之前也用过前公司的框架(那个是利用java的反射自动生成代码),但遇到自动生成代码这事儿,还是止不住兴奋。

这里先不研究生成的框架代码,先研究一下利用go语言是如何自动生成的吧。

一般自动生成可以分三个步骤:

1)通过自描述语言来定义服务和接口(IDL,DSL都OK)

2)解析描述语言,获取元数据(服务名称,接口名称,接口参数神马的)

3)根据元数据,以及框架对应的模板,生成重复的代码部分

我们来看GOA怎么做的,在goagen中加上 --debug 选项,可以保留中间文件。

  1. //使用命令
  2. goagen --debug bootstrap -d goa-adder/design
  3.  
  4. //生成目录
  5. qpzhang@qpzhang:~/gocode/src/goa-adder $tree -L
  6. .
  7. ├── app
  8. ├── client
  9. ├── design
  10. ├── goagen009966755
  11. ├── goagen174102868
  12. ├── goagen511141286
  13. ├── goagen585483469
  14. ├── main.go
  15. ├── operands.go
  16. └── swagger
  1. ├── goagen009966755
  2.    ├── goagen
  3.    └── main.go
  4. ├── goagen174102868
  5.    ├── goagen
  6.    └── main.go
  7. ├── goagen511141286
  8.    ├── goagen
  9.    └── main.go
  10. ├── goagen585483469
  11.    ├── goagen
  12.    └── main.go

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

  1. //************************************************************************//
  2. // Code Generator
  3. //
  4. // Generated with goagen v0.0.1, command line:
  5. // $ goagen
  6. // --debug bootstrap -d goa-adder/design
  7. //
  8. // The content of this file is auto-generated, DO NOT MODIFY
  9. //************************************************************************//
  10.  
  11. package main
  12.  
  13. import (
  14. "github.com/goadesign/goa/goagen/gen_main"
  15. "fmt"
  16. "strings"
  17. "github.com/goadesign/goa/dslengine"
  18. _ "goa-adder/design"
  19. )
  20.  
  21. func main() {
  22. // Check if there were errors while running the first DSL pass
  23. dslengine.FailOnError(dslengine.Errors)
  24.  
  25. // Now run the secondary DSLs
  26. dslengine.FailOnError(dslengine.Run())
  27.  
  28. files, err := genmain.Generate()
  29. dslengine.FailOnError(err)
  30.  
  31. // We're done
  32. fmt.Println(strings.Join(files, "\n"))
  33. }

然后看出一些端倪,它先把我们design目录整个包含进来,然后调用引擎里面的函数,进行代码的生成。

这里再回到我们的DSL语言写的文件 design.go

  1. package design
  2.  
  3. import (
  4. . "github.com/goadesign/goa/design"
  5. . "github.com/goadesign/goa/design/apidsl"
  6. )
  7.  
  8. var _ = API("adder", func() {
  9. Title("The adder API")
  10. Description("A teaser for goa")
  11. Host("localhost:8080")
  12. Scheme("http")
  13. })

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

  1. func API(name string, dsl func()) *design.APIDefinition {
  2. if design.Design.Name != "" {
  3. dslengine.ReportError("multiple API definitions, only one is allowed")
  4. return nil
  5. }
  6. if !dslengine.IsTopLevelDefinition() {
  7. dslengine.IncompatibleDSL()
  8. return nil
  9. }
  10.  
  11. if name == "" {
  12. dslengine.ReportError("API name cannot be empty")
  13. }
  14. design.Design.Name = name
  15. design.Design.DSLFunc = dsl
  16. return design.Design
  17. }

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

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

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

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

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

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

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

  1. //main.go
  2.  
  3. package main
  4.  
  5. import "fmt"
  6.  
  7. //定义DSL语言描述的结构体,用于保存DSL里面的数据
  8. type APIDefinition struct {
  9. // Name of API
  10. Name string
  11. // Title of API
  12. Title string
  13. // Description of API
  14. Desc string
  15.  
  16. // DSLFunc contains the DSL used to create this definition if any
  17. DSLFunc func()
  18. }
  19.  
  20. //实现DSL对应的API,用于实例化
  21. func API(name string, dsl func()) *APIDefinition {
  22. api := new(APIDefinition)
  23. api.Name = name
  24. api.DSLFunc = dsl
  25.  
  26. //偷偷赋值
  27. g_api = api
  28.  
  29. return api
  30. }
  31.  
  32. //对应的Title赋值
  33. func Title(val string) {
  34. if g_api != nil {
  35. g_api.Title = val
  36. }
  37. }
  38.  
  39. func Description(d string) {
  40. if g_api != nil {
  41. g_api.Desc = d
  42. }
  43. }
  44.  
  45. //当前design的实例,这里用全局变量示意
  46. var g_api *APIDefinition
  47.  
  48. //根据内存中的存储数据来进行代码生成
  49. func generateTest() {
  50. //这里需要执行一下对应的DSLFunc
  51. g_api.DSLFunc()
  52. fmt.Println("get Name: ", g_api.Name)
  53. fmt.Println("get Title: ", g_api.Title)
  54. fmt.Println("get Desc: ", g_api.Desc)
  55. }
  56.  
  57. //这里是DSL申明
  58. var _ = API("adder", func() {
  59. Title("The adder API")
  60. Description("A teaser for goa")
  61. })
  62.  
  63. func main() {
  64. generateTest()
  65. }

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

  1. qpzhang@qpzhang:~/gocode/auto-gen $go run main.go
  2. get Name: adder
  3. get Title: The adder API
  4. get Desc: A teaser for goa

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

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

[goa]golang微服务框架学习(二)-- 代码自动生成的更多相关文章

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

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

  2. [goa]golang微服务框架学习(三)-- 使用swagger-ui展示API

    既然goa框架自动生成啦swagger-json文件,那么如何用swagger-ui展示出来呢? 这里分三步: 1.下载swagger-ui的web代码 2.添加swagger.json 和 swag ...

  3. 微服务框架学习二:Http调用

    1. HTTP接口的意义 二进制接口使用的是java/hessian序列化协议,不能很好的与其他语言通信,虽然hessian也是一种跨语言的通用协议,但很多语言没有很好的实现该协议的产品.所以为了能够 ...

  4. kratos微服务框架学习笔记一(kratos-demo)

    目录 kratos微服务框架学习笔记一(kratos-demo) kratos本体 demo kratos微服务框架学习笔记一(kratos-demo) 今年大部分时间飘过去了,没怎么更博和githu ...

  5. golang微服务框架go-micro 入门笔记2.4 go-micro service解读

    本章节阐述go-micro 服务发现原理 go-micro架构 下图来自go-micro官方 阅读本文前你可能需要进行如下知识储备 golang分布式微服务框架go-micro 入门笔记1:搭建go- ...

  6. golang微服务框架go-micro 入门笔记2.3 micro工具之消息接收和发布

    本章节阐述micro消息订阅和发布相关内容 阅读本文前你可能需要进行如下知识储备 golang分布式微服务框架go-micro 入门笔记1:搭建go-micro环境, golang微服务框架go-mi ...

  7. golang微服务框架go-micro 入门笔记2.2 micro工具之微应用利器micro web

    micro web micro 功能非常强大,本文将详细阐述micro web 命令行的功能 阅读本文前你可能需要进行如下知识储备 golang分布式微服务框架go-micro 入门笔记1:搭建go- ...

  8. 【GoLang】golang 微服务框架 go-kit

    golang-Microservice Go kit - A toolkit for microservices kubernetes go-kit_百度搜索 Peter Bourgon谈使用Go和& ...

  9. golang微服务框架go-micro 入门笔记1.搭建 go-micro环境

    微服务的本质是让专业的人做专业的事情,做出更好的东西. golang具备高并发,静态编译等特性,在性能.安全等方面具备非常大的优势.go-micro是基于golang的微服务编程框架,go-micro ...

随机推荐

  1. cpio命令用法

    [转自]流浪妖精のSKY    http://www.cnitblog.com/flutist1225/articles/18974.html cpio命令用法 cpio命令     利用cpio 可 ...

  2. PIC32MZ tutorial -- UART Communication

    At this moment, I accomplish the interface of UART communication for PIC32MZ EC Starter Kit. This in ...

  3. 数据库的NULL值讨论

    有许多关于数据库设计中NULL的讨论,我个人的设计习惯是,不使用NULL值. 我所设计所有表都是Not Null的字段的,尤其是我主要做数据仓库的表设计.刚开始使用数据库时,就栽了一次.一个Group ...

  4. bcm cmd

    BCM.1> port ge en=0 ;Disable all GbEBCM.1> tx 2 pbm=ge2,fe7 ;Transmit 2 packets out of both po ...

  5. solr清空全部索引

    http://blog.csdn.net/qing419925094/article/details/42142117

  6. python学习(解析python官网会议安排)

    在学习python的过程中,做练习,解析https://www.python.org/events/python-events/ HTML文件,输出Python官网发布的会议时间.名称和地点. 对ht ...

  7. log4net日志在app.config中assembly不起作用

    log4net 1.2.15.0日志在app.config中assembly不起作用,必须 1.手动调用方法log4net.Config.XmlConfigurator.Configure()来初始化 ...

  8. JetBrains激活

    https://www.imsxm.com/jetbrains-license-server/ 已经累计为大家激活1360577次 :) JetBrains授权服务器:http://idea.imsx ...

  9. python3.5------购物车

    笔者:QQ:   360212316 逻辑图 程序代码 # /usr/bin/env python # -*- coding: utf-8 -*- product_list = [ ["ip ...

  10. [vivado系列]Vivado软件的下载

    时间:2016.10.27 ------------------ 前言:我们知道vivado软件是用于xilinx的7系列及以上器件的FPGA开发工具. 随着版本的不断更新,也变得越来越庞大.臃肿! ...