[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 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 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,代碼的自動生成原理已經知道了,后面就要分析框架整體的架構和代碼了。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM