DatastoreからGetした時に余計なPropertyがある場合エラーになるが無視してもいい

この記事は最終更新日から3年以上が経過しています。

タイトルのまんまです。

話の前提として、以下のパッケージを使っているものとします。

google.golang.org/appengine/datastore

SDK組み込みの appengine/datastore や Cloud Datastore用の cloud.google.com/go/datastore については言及しませんが、多分似たような設計になってると思うので流用可能なテクではないでしょうか。

モチベーション

GAE/J+Slim3とかだとKindのSchemaの変更は大変気軽にできました。
新規追加のプロパティはデフォルト値になりますし、削除したプロパティはインスタンスに変換される時に無視されるからです。

一方、Goは違います。
Entityに存在するプロパティに対応するフィールドがstructにないと、エラーになります。
このため、GAE/Goでは不要になったプロパティを削除できず、ずっと残す運用をしていました。
具体的に以下のような感じです。

type Sample struct {
    ID     int64  `goon:"id" datastore:"-"`
    Name   string
}

これを以下のように変更して適当に検索かけて使ってるところ消す。

type Sample struct {
    ID               int64   `goon:"id" datastore:"-"`
    DeprecatedName   string  `datastore:"Name"`
}

もしくは、 datastore.PropertyLoadSavor を使って読み出し時に該当のプロパティを抜いてから変換します。
Goの場合 ps = ps.Filter(p => p.Name != "Name") みたいなコードが書きづらいのでひたすらめんどくさいです。

何も考えずにプロパティ削りたい。

対応

structに対し、 datastore.PropertyLoadSavor を実装します。
まずは今まで書いていたであろう実装を示しておきます。

func (entity *Foo) Load(p []datastore.Property) error {
    if err := datastore.LoadStruct(entity, p); err != nil {
        return err
    }

    // 何かやったりする

    return nil
}

func (entity *Foo) Save() ([]datastore.Property, error) {
    // 何かやったりする

    p, err := datastore.SaveStruct(entity)
    if err != nil {
        return nil, err
    }
    return p, nil
}

これを以下のように変更します。

func (entity *Foo) Load(p []datastore.Property) error {
    err := datastore.LoadStruct(entity, p)
    if fmerr, ok := err.(*datastore.ErrFieldMismatch); ok && fmerr != nil && fmerr.Reason == "no such struct field" {
        // ignore
    } else if err != nil {
        return err
    }

    // 何かやったりする

    return nil
}

func (entity *Foo) Save() ([]datastore.Property, error) {
    // 何かやったりする

    p, err := datastore.SaveStruct(entity)
    if err != nil {
        return nil, err
    }
    return p, nil
}

これだけでOKです。

これで大丈夫な理由

このへん

We don't return early, as we try to load as many properties as possible.
It is valid to load an entity into a struct that cannot fully represent it.
That case returns an error, but the caller is free to ignore it.

要するに、 datastore.LoadStruct した時に *datastore.ErrFieldMismatch が返ってきても途中で処理打ち切ったりせずにお尻まで処理してからエラー返してるので無視してもデータが壊れてることはないゾ!という話。

Reasonの値をチェックしているのは若干神経質な気もするので、おおらかな性格の人はReasonはみなくても多分大丈夫なんじゃないでしょうか。

感想

無視するために独自の datastore.LoadStruct 実装しなきゃかなと思って結構手を動かしてたんだけどある時 datastore.ErrFieldMismatch の存在とソレに関わる処理を見つけて無駄な作業だった事を知ってしまいたいへん悲しい(有益な発見

とりあえず、これから本番環境に突っ込んでみる予定です。

ユーザー登録して、Qiitaをもっと便利に使ってみませんか。
  1. あなたにマッチした記事をお届けします
    ユーザーやタグをフォローすることで、あなたが興味を持つ技術分野の情報をまとめてキャッチアップできます
  2. 便利な情報をあとで効率的に読み返せます
    気に入った記事を「ストック」することで、あとからすぐに検索できます
コメント
この記事にコメントはありません。
あなたもコメントしてみませんか :)
すでにアカウントを持っている方は
ユーザーは見つかりませんでした