简单使用 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
设计理念,source
在REST
中囊括了 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 工具后,该文件不会改变,只改变 app
、 client
和 swagger
下的文件。通过 goagen 命令行参数可以指定 app 名定向生成代码。
目录功能说明
app
目录中的代码是 API 的胶水代码(脚手架代码),用于实现 HTTP 底层功能,包括信息接收管理,信息反馈模板,底层路由分发,以及 goa 定义的媒介结构体。如果没有特殊要求,这个目录下的代码文件系自动生成,不需要认为修改。client
包含一个客户端的简单实现,支持命令行参数,可以进行简单的server request
测试。swagger
目录中存放 swagger 规范的 API 说明文件(swagger.json),通过访问 swagger 可以查看 API 说明。- 主目录 src 下的
main.go
和bottle.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,分别为 Bottle
和 swagger
,方法均为 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
开发。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
上一篇: Tomcat 配置调优与安全总结
下一篇: MyBatis 介绍和使用
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论