golang 的不可變(Immutable)編程

不可變編程是一種編程思想。簡單地說,就是對象的屬性只能set一次。

ImmutableEphemeralVolume(immutable secret)

kubernetes 最近(2019年年底)的一個 ImmutableEphemeralVolume 爲例。

我看了一下源代碼,大意就是說,configmapsecret 在創建後不可更新。

以 secret 爲例,目前(2020-04-16)secret 的定義是這樣的:


// Secret holds secret data of a certain type. The total bytes of the values in
// the Data field must be less than MaxSecretSize bytes.
type Secret struct {
    metav1.TypeMeta `json:",inline"`
    // Standard object's metadata.
    // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata
    // +optional
    metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"`

    // Immutable, if set to true, ensures that data stored in the Secret cannot
    // be updated (only object metadata can be modified).
    // If not set to true, the field can be modified at any time.
    // Defaulted to nil.
    // This is an alpha field enabled by ImmutableEphemeralVolumes feature gate.
    // +optional
    Immutable *bool `json:"immutable,omitempty" protobuf:"varint,5,opt,name=immutable"`

    // Data contains the secret data. Each key must consist of alphanumeric
    // characters, '-', '_' or '.'. The serialized form of the secret data is a
    // base64 encoded string, representing the arbitrary (possibly non-string)
    // data value here. Described in https://tools.ietf.org/html/rfc4648#section-4
    // +optional
    Data map[string][]byte `json:"data,omitempty" protobuf:"bytes,2,rep,name=data"`

    // stringData allows specifying non-binary secret data in string form.
    // It is provided as a write-only convenience method.
    // All keys and values are merged into the data field on write, overwriting any existing values.
    // It is never output when reading from the API.
    // +k8s:conversion-gen=false
    // +optional
    StringData map[string]string `json:"stringData,omitempty" protobuf:"bytes,4,rep,name=stringData"`

    // Used to facilitate programmatic handling of secret data.
    // +optional
    Type SecretType `json:"type,omitempty" protobuf:"bytes,3,opt,name=type,casttype=SecretType"`
}

其實只看

Immutable *bool `json:"immutable,omitempty"`

就可以了。可以看到,這是一個 bool 的指針。因爲這個字段目前處於alpha 的階段,所以用了 omitempty 這個標籤忽略掉了。

判斷是否已經注入

Secret 有個 String 方法有點意思。

簡單地說就是通過反射判斷字段是否已經注入。


func (this *Secret) String() string {
    ......
    s := strings.Join([]string{`&Secret{`,
        `ObjectMeta:` + strings.Replace(strings.Replace(fmt.Sprintf("%v", this.ObjectMeta), "ObjectMeta", "v1.ObjectMeta", 1), `&`, ``, 1) + `,`,
        `Data:` + mapStringForData + `,`,
        `Type:` + fmt.Sprintf("%v", this.Type) + `,`,
        `StringData:` + mapStringForStringData + `,`,
        `Immutable:` + valueToStringGenerated(this.Immutable) + `,`,
        `}`,
    }, "")
    return s
}

valueToStringGenerated 方法展開是這樣的:

func valueToStringGenerated(v interface{}) string {
    rv := reflect.ValueOf(v)
    if rv.IsNil() {
        return "nil"
    }
    pv := reflect.Indirect(rv).Interface()
    return fmt.Sprintf("*%v", pv)
}

我簡化了一下模型,寫了個例子。

例子

package main

import (
    "fmt"
    "reflect"
)

type Secret struct {
    Immutable *bool `json:"immutable,omitempty"`
}

func main() {
    s := Secret{Immutable: &[]bool{true}[0]}
    fmt.Println(valueToStringGenerated(s.Immutable)) // *true
    s = Secret{}
    fmt.Println(valueToStringGenerated(s.Immutable)) // nil
}

func valueToStringGenerated(v interface{}) string {
    rv := reflect.ValueOf(v)
    if rv.IsNil() {
        return "nil"
    }
    pv := reflect.Indirect(rv).Interface()
    return fmt.Sprintf("*%v", pv)
}

結論

struct 增加一個字段,這個字段是一個指針。

通過反射獲取 struct 的成員(比如字段),進而判斷是否已經注入。

有些情況(比如string),用私有字段,struct 暴露一個單例模式的 Set 方法也行。我猜是 bool 類型比較特殊,所以 kubernetes 官方纔用了 *bool 這個數據結構。

參考鏈接

  1. Kubernetes: What is Immutable Infrastructure?
  2. Image volumes and container volume
  3. How to set bool pointer to true in struct literal?
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章