gRPC FieldMask类型及其使用
项目中使用proto定义接口时,使用到了FieldMask类型。本文将对FieldMask类型的适用场景及方法做一些整理。
FieldMask类型定义于google/protobuf/field_mask.proto
中,是用于描述属性路径的集合。通常用于指定获取请求要返回的字段集合,或者更新请求要修改的字段集合。
- proto中定义
message FieldmaskTestReq {
string msg = 1;
Nested nested = 2;
google.protobuf.FieldMask field_mask = 3;
}
- 调用方使用
使用RPC调用方式,需要在客户端调用时显示指定FieldMask,代码如下:
fm, err := fieldmaskpb.New(req, "msg")
if err != nil {
log.Printf("construct field_mask error: %+v", err)
return err
}
req.FieldMask = fm
- 服务端代码
if req.FieldMask != nil {
mask, err := fieldmaskutils.MaskFromPaths(req.FieldMask.Paths, func(s string) string {
s = strings.Replace(s, "_", " ", -1)
s = strings.Title(s)
return strings.Replace(s, " ", "", -1)
})
if err != nil {
log.Printf("mask error: %+v", err)
return nil, err
}
fieldmaskutils.StructToStruct(mask, full.Nested, res.Nested)
}
服务端代码中,需要使用"github.com/mennanov/fieldmask-utils"
提供的MaskFromPaths
函数,对于http请求,需要将FieldMask
结构体中path
中存储的属性值由蛇形转换为go结构体中对应的大驼峰格式。关于HTTP下FieldMask
的使用,后续将有介绍。
关于FieldMask
的使用场景,在protobuf
官方文档中已有说明如下:
// Field masks are used to specify a subset of fields that should be
// returned by a get operation or modified by an update operation.
// Field masks also have a custom JSON encoding (see below).
主要有两个场景:
- 指定查询接口返回的字段
- 指定需更新字段
关于场景1,有一篇Netflix的帖子做了更详细的介绍,原文链接为Practical API Design at Netflix, Part 1: Using Protobuf FieldMask。InfoQ有一篇译文介绍为Netflix实用API设计第1部分:使用Protobuf FieldMask。服务端的核心实现为使用类似于fieldmaskutils.StructToStruct
的方法,根据FieldMask
去构造Response字段。
关于场景2,当使用FieldMask
表示需要更新的字段时,需要在response中显式判断FieldMask.Path
中传入的值,并根据传入值处理数据。个人观点,认为此场景下使用Wrapper
进行处理更为方便,可避免很多侵入的判断代码。
通常情况(http方法非PATCH)下,grpc-gateway会将FieldMask
当作普通字段处理,意味着调用方在发送http请求时,需要显示指定FieldMask
字段,grpc-gateway并不会自动填充。测试命令如下:
- 不指定FieldMask
curl -kv http://localhost:8081/v1/fieldmasktestwithbodystar -H 'content-type: applicationo/json' -d '{"msg": "request", "nested": {"attr1": "1", "attr2": "2"}}' -X POST
{"code":200,"data":{"msg":"","nested":{"attr1":"","attr2":""}},"error":""}
- 显式指定FieldMask
curl -kv http://localhost:8081/v1/fieldmasktestwithbodystar -H 'content-type: applicationo/json' -d '{"msg": "request", "nested": {"attr1": "1", "attr2": "2"},"field_mask": {"paths": ["msg", "nested.attr1"]}}' -X POST
{"code":200,"data":{"msg":"reply","nested":{"attr1":"attr1","attr2":""}},"error":""}
当http调用方法为PATCH,且满足以下两个条件时,grpc-gateway会自动填充FieldMask
字段:
-
PATCH路由定义在
additional_bindings
中 -
http body定义不为
*
grpc-gateway官方描述如下:
If a binding is mapped to patch and the request message has exactly one FieldMask message in it, additional code is rendered for the gateway handler that will populate the FieldMask based on the request body. FieldMask is treated as a regular field by the gateway if the request method is not PATCH, or if the HttpRule body is "*"
测试命令如下:
curl -kv http://localhost:8081/v1/fieldmasktest -H 'content-type: applicationo/json' -d '{"attr1": "1", "attr2": "2"}' -X PATCH
{"code":200,"data":{"msg":"","nested":{"attr1":"attr1","attr2":"attr2"}},"error":""}
关于所有测试代码,可参考grpc-helloworld repo。
Practical API Design at Netflix, Part 1: Using Protobuf FieldMask