Goa Media
使用 Goa 的 Media
之前,首先得明白其存在的意义。
当终端访问我们的 HTTP API 时,应有一个双方约定好的信息格式载体,我们以常用的 JSON
为例。 Response
body 信息为 JSON,同时 server 端反馈的信息也应该是 JSON。在 Go
代码里,JSON 的本质是一个 type 为 string 的 key-value 的 map,代码表述为:
map[string]interface{}
其中 interface
接受各种类型。
当我们需要实现这么一个 map 时,最规范也是最常用的办法,就是将此段 JSON 的模板结构体直接赋值转换为 map。
因此,go 生成 API-JSON 返回信息的基本流程为:
- 1.定义结构体&定义该结构体对应的 JSON 模板
- 2.为结构体变量赋值
- 3.将结构体变量映射到 JSON 模板中,生成 JSON 型 map
- 4.将此 map(已成为 JSON)返回给终端
除了 第 2 步 需要人为的写代码赋值外,Goa 的 MediaType
均可以在 Design
过程中指定关键字来实现。
基于官方的 Test code
举个栗子:
design.go
中关于 API resource: bottle
的 testcode 解读:
var _ = Resource("bottle", func() { //定义资源,资源名为 bottle
BasePath("/bottles")
DefaultMedia(BottleMedia) //指定 generated code 中默认使用的 Media
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")
TypeName("BottleMedia") //给 Media 重命名
Attributes(func() {
Attribute("id", Integer, "Unique bottle ID") //参数分别为 JSON-key,type,描述
Attribute("href", String, "API href for making requests on the bottle")
Attribute("name", String, "Name of wine")
})
View("default", func() { //定义好 Media 后,必须要指定一个 default 的 View
Attribute("id") //在这个 View 中暴露出的属性
Attribute("href")
Attribute("name")
})
})
代码说明:
DefaultMedia(BottleMedia)
中的变量不一定非得是在View
中指定为“default”的 Media,而是说要使用哪个 Media(Test code 里只定义了一个名为 default 的 media)。如果没有这句代码,默认生成的Show
method 里没有 media 的框架(可以手动添加)。TypeName("BottleMedia")
该行代码的作用是将 Media 重命名。如果没有这一行,Goa 会根据MediaType
函数的第一个参数生成 Media 名(即结构体名字),上面的例子中,如果去掉该行,生成的 Media 名为GoaExampleBottle
。View
可以定义多个,在生成的代码中,决定使用哪个 JSON 模板做 Media 的载体。但 View 中必须要有一个名为default
的定义,当没有指定 view 时,系统默认暴露 default。只要有 media,就要有个 default 的 view。- 关于
Media
和View
的关系。View 是 Media 的子集,REST API 中一个资源可以有很多属性,但并不是每个该资源的 HTTP 请求都应反馈整个属性 list,而是根据不同的请求地址,返回不同需求的资源信息,这种方式可以在 Design 过程中通过 View 实现。
上面的 design 代码生成的次代代码(media_types.go) 如下:
// A bottle of wine
// Identifier: application/vnd.goa.example.bottle+json
type BottleMedia struct {
// API href for making requests on the bottle
Href *string `json:"href,omitempty"`
// Unique bottle ID
ID *int `json:"id,omitempty"`
// Name of wine
Name *string `json:"name,omitempty"`
}
// Dump produces raw data from an instance of BottleMedia running all the
// validations. See LoadBottleMedia for the definition of raw data.
func (mt *BottleMedia) Dump() (res map[string]interface{}, err error) {
res, err = MarshalBottleMedia(mt, err)
return
}
// MarshalBottleMedia validates and renders an instance of BottleMedia into a interface{}
// using view "default".
func MarshalBottleMedia(source *BottleMedia, inErr error) (target map[string]interface{}, err error) {
err = inErr
tmp23 := map[string]interface{}{
"href": source.Href,
"id": source.ID,
"name": source.Name,
}
target = tmp23
return
}
从代码中可以看出,当我们需要给该 Media 赋值时,需要将资源(BottleMedia 型的结构体指针)传值给 MarshalBottleMedia
。该资源在对应的 contexts.go
中进行定义。
生成的 Action Func
代码(bottle.go)如下:
// Show runs the show action.
func (c *BottleController) Show(ctx *app.ShowBottleContext) error {
res := &app.BottleMedia{}
return ctx.OK(res)
}
可以看到定义的 res
变量即为我们要的 BottleMedia
型的结构体指针,而函数 MarshalBottleMedia
进行多重封装后在 return ctx.OK(res)
这一句中被调用,因此上面讲的第二步,即是我们需要做的修改,根据业务,将信息写入变量 res
,然后发出。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
上一篇: Goa 日志管理
下一篇: MyBatis 介绍和使用
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论