在实际业务中,我们经常会对一些通用的结构体(类)放到一个common包中,以减少代码copy带来出错、数据前后不一致等问题。而目前基于api的语法定义是无法使用common结构体(类)的,假设我们有这样两个服务Service A,Service B,A服务引用了Media结构体(类),B服务引用了Video,而Media中又包含Video成员,因此服务A和B有共同的结构体(类)Video,目前的解决方案是在定义每个服务时,对应的api文件都会编写一次各自需要的结构体(类),我们来看一下传统写法
传统写法 A服务中引用mediatype (
Video struct {
Name string `json:"name"`
Url string `json:"url"`
Width int `json:"width"`
Height int `json:"height"`
// bytes
Size int64 `json:"size"`
// mp4|flv|...
Ext string `json:"ext"`
}
Audio struct {
Name string `json:"name"`
Url string `json:"url"`
// bytes
Size int64 `json:"size"`
// mp3|wav|...
Ext string `json:"ext"`
}
Image struct {
Url string `json:"url"`
Width int `json:"width"`
Height int `json:"height"`
// bytes
Size int64 `json:"size"`
// png|jpg|...
Ext string `json:"ext"`
}
Media struct {
Video Video `json:"video"`
Audio Audio `json:"audio"`
Image Image `json:"image"`
}
)
service A {
@handler getMedia
get /a/media/get returns (Media)
}
B服务中引用Video
type (
Video struct {
Name string `json:"name"`
Url string `json:"url"`
Width int `json:"width"`
Height int `json:"height"`
// bytes
Size int64 `json:"size"`
// mp4|flv|...
Ext string `json:"ext"`
}
)
service B {
@handler getVideo
get /b/video/get returns (Video)
}
通过以上示例我们可以知道Video结构体(类)为公用的,也许后面还有其他服务也需要使用Video结构体(类),我们讨论出了一个方案来解决这个问题
方案一:@types通过@types
关键字来声明需要引用的元语言
中的结构体(类)
@types语法定义
元语言
:即生成的目标语言,如api需要生成golang代码,则元语言为golang
@types{
$lang(
$pathOfType
)
}
$lang: 元语言标识,如
golang
、java
,必须为小写,常见语言在设计完成后会列举出来 $pathOfType: 需要引用的通用结构体(类)的路径,注意:这里的写法和文件路径有所区别,其由$path/$package.$typeName组成,如"github.com/go-zero/common/common.Video",其中github.com/go-zero/common/为结构体(类)所在位置,可以将其理解为元语言中引用Video结构体(类)时的import值,common为元语言中的packageName,Video则为需要引用的通用结构体(类),可以将common.Video理解为元语言中使用Video时的声明类型。
其怎么对应到元语言中的结构体见下文
@types 引用golang中的Video编写示例@types{
golang(
"github.com/go-zero/common/common.Video"
"github.com/go-zero/common/common.Audio"
)
java(
"com/go-zero/common/common.Video"
"com/go-zero/common/common.Audio"
)
}
api对共用结构体(类)翻译处理
importPathOfTheTargetLang,structInUse:=filepath.Split($pathOfType)
- 在翻译后的元语言中
import $importPathOfTheTargetLang
- 获取$structInUse中的结构体$st为
Video
- $st=$structInUse
回到开头提到的服务A和服务B共用Video
结构体(类)的问题,我们在api中用@types语言去重新编写一下api,假设我们的golang的module为greet
,Video
在common包文件夹下,其在golang中的定义如下
vim greet/common/types.go
package common
type (
Video struct {
Name string `json:"name"`
Url string `json:"url"`
Width int `json:"width"`
Height int `json:"height"`
// bytes
Size int64 `json:"size"`
// mp4|flv|...
Ext string `json:"ext"`
}
)
在元语言golang中使用Video
vim greet/test/test.go
package test
import "greet/common" // greet/common对应$path
func main() {
var video common.Video // common.Video中common对应$package,Video对应$typeName
}
由以上元语言示例可得知api中引用Video
的$pathOfType为greet/common/common.Video
服务A引用Video
@types{
golang(
"greet/common/common.Video"
)
}
type (
Audio struct {
Name string `json:"name"`
Url string `json:"url"`
// bytes
Size int64 `json:"size"`
// mp3|wav|...
Ext string `json:"ext"`
}
Image struct {
Url string `json:"url"`
Width int `json:"width"`
Height int `json:"height"`
// bytes
Size int64 `json:"size"`
// png|jpg|...
Ext string `json:"ext"`
}
Media struct {
Video common.Video `json:"video"`
Audio Audio `json:"audio"`
Image Image `json:"image"`
}
)
service A {
@handler getMedia
get /media/get returns (Media)
}
服务B引用Video
@types{
golang(
"greet/common/common.Video"
)
}
service B {
@handler getMedia
get /b/get/video returns (common.Video)
}
服务A/B翻译后的types中Video
则会以Video=common.Video
形式存在
package types
import "greet/common"
type (
// 翻译后Video
Video = common.Video
Audio struct {
Name string `json:"name"`
Url string `json:"url"`
// bytes
Size int64 `json:"size"`
// mp3|wav|...
Ext string `json:"ext"`
}
Image struct {
Url string `json:"url"`
Width int `json:"width"`
Height int `json:"height"`
// bytes
Size int64 `json:"size"`
// png|jpg|...
Ext string `json:"ext"`
}
Media struct {
Video Video `json:"video"`
Audio Audio `json:"audio"`
Image Image `json:"image"`
}
)
方案二:通过命令行指定import路径说明:如果以上元语言为java,则
greet/common
会转换成greet.common
这个方案类似grpc中type结构体的做法,grpc生成举例:
protoc --go_out=plugins=grpc,Mbase/base.proto=foo/bar/base:. foo.proto
其中M{{proto_import}}={{golang_import}}
就是指定proto import路径对应目标语言的真实import值
引用grpc方案,api的写法示例:
api文件编写语法保持不变,沿用api的import语法
定义common.api$ vim common.api
type Video {
Name string `json:"name"`
Url string `json:"url"`
Width int `json:"width"`
Height int `json:"height"`
// bytes
Size int64 `json:"size"`
// mp4|flv|...
Ext string `json:"ext"`
}
import common结构体
vim A.api
import "common/common.api"
type Media{
// from common.api
video *Video `json:"video"`
....
}
service media-api {
@handler getMedia
get /media/get returns (Media)
}
服务结构
greet
├── media
│ └── api
│ ├── etc
│ │ └── media-api.yaml
│ ├── internal
│ │ ├── config
│ │ │ └── config.go
│ │ ├── handler
│ │ │ ├── getmediahandler.go
│ │ │ └── routes.go
│ │ ├── logic
│ │ │ └── getmedialogic.go
│ │ ├── svc
│ │ │ └── servicecontext.go
│ │ └── types
│ │ └── types.go
│ ├── media.api
│ └── media.go
└── common
├── common.api
└── common.go
生成A服务命令
$ cd A/api
$ goctl api go -api A.api -dir . -i common/common.api=common
-i
为新增的flag,用于指定api import翻译后目标语言的真实import值
common/common.api=common
指定生成目标语言后common中被引用结构体的真实import值,都一个import以英文逗号分割;
以golang举例,其组成形式:
{{api_import}}
={{golang_import}}
,因此common/common.api
即A.api中定义的import值,common
则为生成的types.go中引用Video
的真实import值
package types
import "common"
type Media struct{
// from common.go
Video *common.Video `json:"video"`
}