Goa(2) RESTful microservices - KerwinKoo/KerwinKoo.github.io GitHub Wiki

使用Goa

微服务环境搭建

首先目录文件一定要创建在$GOPATH/src下,假设做一个cellar服务,首先创建目录$GOPATH/src/cellar, 然后在此目录下创建子文件夹design和文件design/design.go

示例代码

source code list

在文件design/design.go中写入代码:

package design

import (
        . "github.com/raphael/goa/design"
        . "github.com/raphael/goa/design/dsl"
)

var _ = API("cellar", func() {
        Title("The virtual wine cellar")
        Description("A basic example of an API implemented with goa")
        Scheme("http")
        Host("localhost:8080")
})

var _ = Resource("bottle", func() {
        BasePath("/bottles")
        DefaultMedia(BottleMedia)
        Action("show", func() {
                Description("Retrieve bottle with given id")
                Routing(GET("/:bottleID"))
                Params(func() {
                        Param("bottleID", Integer, "Bottle ID")
                })
                Response(OK)
                Response(NotFound)
        })
})

var BottleMedia = MediaType("application/vnd.goa.example.bottle+json", func() {
        Description("A bottle of wine")
        Attributes(func() {
                Attribute("id", Integer, "Unique bottle ID")
                Attribute("href", String, "API href for making requests on the bottle")
                Attribute("name", String, "Name of wine")
        })
        View("default", func() {
                Attribute("id")
                Attribute("href")
                Attribute("name")
        })
})

代码解读

  • design包名不限制,什么名字都可。

  • API函数,用于声明API,第一个参数为API名字,第二个参数是匿名函数,用于定义该API的基本属性。

  • Resource用于声明服务资源(resource),因为Goa是RESTful设计理念,sourceREST中囊括了API所能访问的所有属性,参数即地址,地址亦参数,http的GET、POST等也属于资源范畴。因此函数Resource是Goa设计对象的主体。在上面的代码示例中,函数Resource用于声明一个名为“bottle”的资源,同样的,在匿名函数中对该资源进行属性、行为声明。

  • Resource函数中,Action可以定义该资源所提供的行为,Action函数必须在Resource函数中定义。Action同样是一个行为名称+一个匿名函数,在匿名函数中声明了该行为的描述、HTTP method、参数及返回值等属性。其中,HTTP method(代码中的Routing声明)中的路径需使用通配符(:变量名)。承接参数类型定义使用Params,如果该Action不止一个参数,需要多次调用Params对每个参数进行承接声明。同一个Resource函数中,可以定义多个Action函数。

  • Goa用代码描述设计,用设计生成代码。Goa设计的最终端点是Action,并通过函数Action描述。Goa定义了基本的response模板,这里默认返回OK(200)。

  • 当资源访问成功后,可以将访问变量提交给我们最后定义的Media,Media通过函数MediaType定义,Media的结构组成,则是匿名函数Attributes中定义。

生成代码

通过goagen将设计生成代码:

goagen bootstrap -d cellar/design

代码目录结构

在当前工作目录下会生成如下文件结构:

app
app/contexts.go
app/controllers.go
app/hrefs.go
app/media_types.go
app/user_types.go
main.go
bottle.go
client
client/cellar-cli
client/cellar-cli/main.go
client/cellar-cli/commands.go
client/client.go
client/bottle.go
swagger
swagger/swagger.json
swagger/swagger.go

bottle.go为主结构代码文件。当之后再做设计修改,运行goagen工具后,该文件不会改变,只改变appclientswagger下的文件。通过goagen命令行参数可以指定app名定向生成代码。

目录功能说明

  • app目录中的代码是API的胶水代码(脚手架代码),用于实现HTTP底层功能,包括信息接收管理,信息反馈模板,底层路由分发,以及goa定义的媒介结构体。如果没有特殊要求,这个目录下的代码文件系自动生成,不需要认为修改。

  • client包含一个客户端的简单实现,支持命令行参数,可以进行简单的server request测试。

  • swagger目录中存放swagger规范的API说明文件(swagger.json),通过访问swagger可以查看API说明。

  • 主目录src下的main.gobottle.go实现简单的server。

app包:

  • controllers.go:控制接口。在设计过程中,每一个resource都有一个接口定义。例子中的接口定义如下:
// BottleController is the controller interface for the Bottle actions.
type BottleController interface {
	goa.Controller
	Show(*ShowBottleContext) error
}

因此接口需要一个goa.Controller,需要实现Show(*ShowBottleContext) error函数,后者在bottle.go中实现。

controllers.go还实现了向server mount的功能,将其自身API挂载到server中。

  • contexts.go:内容结构体的实现。

  • hrefs.go:提供资源信息描述功能。

  • media_types.go:实现应答数据结构体类型的定义。

  • user_types.go:定义通过 "Type" 设计的结构体。

完善代码

仍旧是上面代码的例子。Goa已经实现了一个基本且功能全面的框架,剩下的需要我们实现bottle控制器。回到之前介绍的BottleController接口(定义在controllers.go文件中)和ShowBottleContext结构体(定义在app/contexts.go文件中):

// BottleController is the controller interface for the Bottle actions.
type BottleController interface {
	goa.Controller
	Show(*ShowBottleContext) error
}
// ShowBottleContext provides the bottle show action context.
type ShowBottleContext struct {
	*goa.Context
	BottleID int
}

其中BottleID int是我们在之前design时设定的。

次文件包含另外两个函数的实现:

// NotFound sends a HTTP response with status code 404.
func (ctx *ShowBottleContext) NotFound() error {
	return ctx.RespondBytes(404, nil)
}

// OK sends a HTTP response with status code 200.
func (ctx *ShowBottleContext) OK(resp *GoaExampleBottle) error {
	r, err := resp.Dump()
	if err != nil {
		return fmt.Errorf("invalid response: %s", err)
	}
	ctx.Header().Set("Content-Type", "application/vnd.goa.example.bottle+json; charset=utf-8")
	return ctx.Respond(200, r)
}

上面两个函数仍是Template,供后续开发或bottle.go文件调用。

利用上面的基础框架,我们需要实现文件bottle.go的具体功能。当前的API访问时仅返回一个200,我们需要根据具体业务来实现API功能函数。

用以下代码替换show方法(注意引用fmt包):

// Show implements the "show" action of the "bottles" controller.
func (c *BottleController) Show(ctx *app.ShowBottleContext) error {
	if ctx.BottleID == 0 {
		// Emulate a missing record with ID 0
		return ctx.NotFound()
	}
	// Build the resource using the generated data structure
	bottleID := fmt.Sprintf("Bottle #%d", ctx.BottleID)
	bottleHref := app.BottleHref(ctx.BottleID)
	bottle := app.GoaExampleBottle{
		ID:   &ctx.BottleID,
		Name: &bottleID,
		Href: &bottleHref,
	}

	// Let the generated code produce the HTTP response using the
	// media type described in the design (BottleMedia).
	return ctx.OK(&bottle)
}

上面代码与官方示例代码略有不同,但本质相同。官方代码有赋值错误,将value赋值给了point,因此我做了过程上的微小处理。

API实现后,会在main.go中进行挂载。

注意:main中进行挂载的API不能有路由冲突,比如API资源/foo/:id/foo/:fooID/bar/:id不能同时存在,需要将第一个修改为/foo/:fooID

编译测试

在当前目录进行编译并启动server:

go build -o cellar_server
./cellar_server

官方示例中是go build -o cellar,但生成过程中本身已存在cellar目录,所有会出现编译错误。这里改成cellar_server

编译过程中出现其他问题,移步到我上一篇关于Goa的日志

运行后,打印启动日志:

INFO[01-21|18:22:11] mount                                    app=API ctrl=Bottle action=Show route="GET /bottles/:bottleID"
INFO[01-21|18:22:11] mount                                    app=API file=swagger/swagger.json route="GET /swagger.json"
INFO[01-21|18:22:11] listen                                   app=API addr=:8080

从日志中看,路由中挂载了两个API,分别为Bottleswagger,方法均为GET

curl工具测试

可以通过curl工具进行测试:

curl -i localhost:8080/bottles/1234
curl -i localhost:8080/bottles/0

第二个是测试之前我们实现的功能:

        if ctx.BottleID == 0 {
		// Emulate a missing record with ID 0
		return ctx.NotFound()
	}

swagger测试:

curl -i localhost:8080/swagger.json

client CLI测试

Goa同时生成CLI测试客户端:

$ cd client/cellar-cli
$ go build -o cellar-cli
$ ./cellar-cli --dump show bottle /bottles/1234

效果与curl大体相同,多了一个href的显示:

{"href":"/bottles/1234","id":1234,"name":"Bottle #1234"}

通过这个例子,可以体会Goa以设计为基础的HTTP API开发。

⚠️ **GitHub.com Fallback** ⚠️