The Round

合同会社ナイツオの開発ブログ

[PR] 5分から相談できるGCP™ 開発コンサル!→こちら

GAE/Go Datastore特有の機能~PropertyLoadSaver~

注:古い記事の為、内容が最新ではない可能性がありますm(_ _)m

どうもこんにちわ!マツウラです。

今回はGo言語版のDatastoreインターフェースにおいて、
Java,Pythonとは異なる特徴的な機能を見てゆこうと思います。

参考:The datastore package - Go — Google Developers The datastore package

エンティティの内容は一般的に構造体のポインタですが、PropertyLoadSaverインターフェースを実装することで任意の型で表現することができます。
構造体ポインタを使用する一般的な利用ではPropertyLoadSaverインターフェースは実装する必要はありません。
reflectionによって自動的に変換されるためです。

PropertyLoadSaverインターフェース

エンティティのコンテンツはPropertyLoadSaverインターフェースを実装することで任意の型で表現できます。
この型は構造体ポインタである必要はありません。 データストアパッケージはエンティティのコンテンツを取得する際にLoadメソッドを呼び出し、格納する際はSaveメソッドを呼び出します。

有効な使い方としては、格納しなかったフィールド値の設定、フィールドを検証する、または値が正しい場合にのみフィールドをインデックス付けする、などがあります。

次はPropertyLoadSaverインターフェースの実装例になります。

type CustomPropsExample struct {
    I     int
    J     int
    Slice []string
    Sum   int `datastore:"-"`
}

func (x *CustomPropsExample) Load(c <-chan datastore.Property) error {
    err := datastore.LoadStruct(x, c)
    if err != nil {
        return err
    }
    x.Sum = x.I + x.J
    return nil
}

func (x *CustomPropsExample) Save(c chan<- datastore.Property) error {
    defer close(c)

    // Sumフィールドの検証
    if x.Sum != x.I+x.J {
        return errors.New("CustomPropsExample has inconsistent sum.")
    }

    // スライス表現がなければdatastore.SaveStruct(x, c)でも良いです。
    c <- datastore.Property{Name: "I", Value: int64(x.I)}
    c <- datastore.Property{Name: "J", Value: int64(x.J)}
    for _, v := range x.Slice {
        c <- datastore.Property{Name: "Slice", Value: v, Multiple: true}
    }
    return nil
}

func CuntomPropsHandler(w http.ResponseWriter, r *http.Request) {
    c := appengine.NewContext(r)

    e1 := &CustomPropsExample{
        I:     3,
        J:     4,
        Slice: []string{"one", "two", "three"},
    }
    e1.Sum = e1.I + e1.J
    key := datastore.NewKey(c, "CustomPropsExample", "custom", 0, nil)

    _, err := datastore.Put(c, key, e1)
    if err != nil {
        c.Errorf("%v", err)
        return
    }

    var e2 CustomPropsExample
    err = datastore.Get(c, key, &e2)
    if err != nil {
        c.Errorf("%v", err)
        return
    }
    fmt.Fprintf(w, "%d\n", e2.Sum)
}

スライス表現を行うためにMultipleにtrueを渡していますが、正しいサンプルが見当たりませんでした。
動作はしましたが、これがベストかどうか少々怪しいです。^^;

次は上記の例で使用したメソッドや型についてです。

PropertyList

type PropertyList []Property

PropertyListはPropertyLoadSaverを実装するため[]Propertyに変換します。

func (l *PropertyList) Load(c <-chan Property) error

func (l *PropertyList) Save(c chan<- Property) error

Loadはcのプロパティ全てをlにロードします。
その際最初に*lが空のスライスにリセットされることはありません。

Saveはclのプロパティ全てを保存します。

#

func SaveStruct(src interface{}, c chan<- Property) error

func LoadStruct(dst interface{}, c <-chan Property) error

SaveStructはcにsrcからプロパティを保存します(完了した時cを閉じます)。
LoadStructはdstにcからプロパティを読み込みます(cが閉じられるまで)。
srcおよびdstは構造体ポインタである必要があります。

PropertyLoadSaver

type PropertyLoadSaver interface {
    Load(<-chan Property) error
    Save(chan<- Property) error
}

プロパティ配列<->プロパティと変換することができます。
Loadはエラーが発生しても、終了までチャンネルを開けておく必要があります。
Saveはエラーが発生しても、チャンネルが完了したら閉じる必要があります。

上記のコード例ではdeferを用いてこのSaveの条件を解決しています。

type Property struct {
    Name     string
    Value    interface{}
    Noindex  bool
    Multiple bool
}

Valueプロパティについて

Valueフィールドについては有効な型が制限されています。
これはデータストアで有効な値型よりもさらに少なくなっています。
次のリストが有効な型の一覧です。

  • int64
  • bool
  • string
  • float64
  • *Key
  • time.Time
  • appengine.BlobKey
  • appengine.GeoPoint
  • []byte (up to 1 megabyte in length)

これらの値のスライスは無効([]byteは別)になっており、
さらに独自の型を定義しても無効です。(type myInt64 int64のような型)

プロジェクションクエリを使用した際は、PropertyLoadSaverの代わりに構造体にエンティティがロードされます。

Noindexプロパティについて

データストアがこのプロパティをインデックス付け可能か否かです。

Multipleプロパティについて

プロパティにスライスで値を格納するか否かです。
Valueでは上記でも指摘した通りスライスが無効です。
しかし、コード例のようにすることでスライスの形式を取ることが可能になります。


今回はGo言語版のDatastoreインターフェースにおいて、
Java,Pythonとは異なる特徴的な機能、PropertyLoadSaverについて見てきました。

次回はエンティティのプロパティ関連でGo言語特有の機能と仕様を見てゆこうと思います。