导读
在阅读 Kubernetes
API 或 其他项目的 API时,细心的读者会发现这些 API 中有些字段包含了 // +optional
标记(下面简称optional
标记),比如 Deployment
API中的 Replicas
字段就包含这个标记:
// DeploymentSpec is the specification of the desired behavior of the Deployment.
type DeploymentSpec struct {
// Number of desired pods. This is a pointer to distinguish between explicit
// zero and not specified. Defaults to 1.
// +optional
Replicas *int32 `json:"replicas,omitempty"`
...
// Template describes the pods that will be created.
Template v1.PodTemplateSpec `json:"template"`
...
}
作为对比,Deployment
API中的 Template
字段就没有optional
标记,你知道他们的区别吗?
读者可能会说,这还不简单,包含optional
标记的字段是可选的,反之就是必选的。事实确实如此,不过,对于API设计者还需要思考下面的问题:
optional
标记除了提高可读性以外,还有什么作用?- 在设计API时,字段是否应该包含
omitempty
标签? - 在设计API时,字段是否应该定义为指针类型?
笔者最初没有深入地了解optional
标记,直到自己设计API时走了一些弯路才意识到这里面大有学问,更准确地说是前人经验的总结。本文站在API设计者角度来介绍应该如何处理字段的可选性。
本节内容由Kubernetes社区相关讨论、案例中总结而来,需要说明的是,在Kubernetes项目早期,关于API字段的可选性设计并没有统一的原则,这也导致了目前仍有部分API并不是十分规范。
optional标记的作用
optional
标记本身是一个特殊格式的注释,其特殊性体现在两方面:
- 该标记占用单行注释
- 注释以空格开始,然后附加以“+”为前缀的标记(类似Golang语言中的build 标签)。
该标记除了提高代码可读性以外,主要用于生成OpenAPI文档以及相应的校验规则。比如controller-gen
工具就会根据这个标记生成CRD的校验规则。
optional
标记仅用于标记字段的可选性,除此之外,API设计者还需要了解一些字段设计的约定,或者说是经验之谈。
字段可选性约定
可选字段通常具备以下特征:
- 注释中包含
optional
标记; - 字段类型通常为指针、map、slice;
- 字段的
Tag
中通常包含omitempty
标记;
必选字段通常具备以下特征:
- 注释中没有
optional
标记; - 字段类型不是指针;
- 字段的
Tag
中没有omitempty
标记;
关于omitempty标记
在optional
标记出现以前,Kubernetes的API中广泛依赖字段的omitempty
标记来判断字段的可选性,拥有omitempty
标记的被自动识别的可选字段,反之则为必选字段。现在慢慢过渡到使用optional
标记来识别可选性。
如何区别空值和零值
对于下面的可选字段而言,如果用户设置字段为0
(空值),由于该值等同于类型的零值,开发者无法区别出用户到底有没有设置。
// +optional
Foo int32 `json:"foo,omitempty"`
所以,对于可选字段,建议使用指针,如果指针为nil表示用户没有设置,反之则代表用户显式地设置了字段值。
除此之外,如果可选字段类型为自定义结构体类型,使用指针还可以简化JSON编码。参考下面的例子:
type DummyStruct struct {
// +optional
Foo *int `json:"foo,omitempty"`
}
type MyStruct struct {
// +optional
Dummy DummyStruct `json:"dummy,omitempty"`
}
尽管Dummy
字段标签中包含omitempty
标记,在将其JSON编码(json.Marshal)时,仍然为出现一个空的JSON键,如下所示
{"dummy":{}}
如果API中包含大量这样的字段,则在JSON编码时会比较丑陋,而将其定义为指针类型可消除这个问题。
参考资料:
- 《API Conventions》https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#optional-vs-required