# 16.1 字段可选性设计约定

## 导读

在阅读 `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](https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#optional-vs-required)
