LOULIZCategoryプライバシーポリシー

GAEのスタンダード環境からGo言語でDatastoreの基本操作を行う方法

今回は Google App Engine (公式ページ) のスタンダード環境から Go言語を使って Datastore (公式ページ) へ接続し、基本的なデータ操作を行う方法をご紹介します。

Contents

必要なパッケージ

Go
1
2
3
4
5
6
7
8
9
import (
    "context"
    "net/http"
    "time"

    "google.golang.org/appengine"
    "google.golang.org/appengine/datastore"
    "google.golang.org/appengine/log"
)

上のコードでは今回のプログラムで必要なパッケージをインポートしています。『time』と『google.golang.org/appengine/log』パッケージ以外は App Engine のスタンダード環境から Datastore を操作するのに必須です。

スタンダード環境以外の環境から操作する場合には、また違ったパッケージが必要なようですのでご注意下さい。

今回扱うデータ

Go
1
2
3
4
5
6
7
8
// Page 記事メタデータ
type Page struct {
    Domain  string // 記事が存在するドメイン
    Slug    string // スラグ
    Title   string // タイトル
    Date    time.Time // 公開日時
    Publish bool // 記事を公開するかどうか
}

今回扱うデータはブログサービスで使えそうなものを用意してみました。より複雑なものとなっていますが、こういった構造体は実際に Datastore を活用して運用している当ブログでも使用しています。

上の構造体はブログ内に存在する記事のメタデータを扱うために存在し、その記事が(複数のドメインを所持している場合)どのドメインのものなのか、スラグは何か、タイトルは? 公開日時はいつか、公開中であるか非公開であるか、そういったデータを保管できます。

記事を新規作成したら、この構造体を元に Datastore に追加し、ブログにアクセスがあれば対象の記事が Datastore に存在するか検索し、ヒットすればこのデータを元に処理を行う形になります。

Datastore では上のような構造体が、MySQL のテーブルような存在となります。

データの追加

Go
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// New 記事メタデータの追加
func (p *Page) New(ctx context.Context) (*datastore.Key, error) {
    key := datastore.NewIncompleteKey(ctx, "Page", nil) // 新しいキーを取得
    return datastore.Put(ctx, key, p) // 取得したキーにデータを追加
}

func handler(w http.ResponseWriter, r *http.Request) {
    ctx := appengine.NewContext(r) // App Engine 用のコンテキストを取得

    // Page構造体を指定したデータで初期化し、ポインタを取得する
    p := &Page{
        Domain:  "ja.louisaandlily.com",
        Slug:    "abc",
        Title:   "エービーシー",
        Date:    time.Now(),
        Publish: true,
    }

    _, err := p.New(ctx) // ページ追加
    if err != nil { //エラーがあればログ表示
        log.Errorf(ctx, "New page: %v", err)
    }
}

上のコードは Datastore に先程の構造体データを追加するためのものです。『handler』関数が何か分からない場合は『GAEで初めてのGo言語製ウェブサーバーの作り方』の記事を、そしてログの出力方法については『GAEのスタンダード環境でGo言語を使ってログを出力する方法』で書きましたので確認してみて下さい。

新しいキーの取得

Go
1
key := datastore.NewIncompleteKey(ctx, "Page", nil)

Datastore ではデータとそれに対応するキーが1対1で管理されています。新規にデータを追加する場合は、新しいキーを用意する必要があり、『datastore』パッケージの『NewIncompleteKey』関数を使えば自動的に最適なキーが生成されます。通常はこの関数を使うと良いでしょう。

引数は1番目にコンテキスト、2番目にデータの種類、3番目は追加するデータに親を指定する場合に、そのキーをセットします。

2番目のものは MySQL のテーブル名に相当します。

Putする

Go
1
key, err := datastore.Put(ctx, key, p)

Put 関数はデータの追加と更新する際に使用する関数で、引数の1番目にコンテキスト、2番目に対応するキー、3番目に追加するデータのポインタを渡します。

戻り値は1番目にキー、2番目にエラー値です。

クエリーを作成してデータを1つ取得

Go
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// Get 記事メタデータの取得
func (p *Page) Get(ctx context.Context) (*datastore.Key, error) {
    // クエリー作成
    q := datastore.NewQuery("Page").Filter("Domain =", p.Domain).Filter("Slug =", p.Slug)

    // クエリー実行
    it := q.Run(ctx)

    return it.Next(p) // クエリー結果からデータを取得
}

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

    p := &Page{
        Domain: "ja.louisaandlily.com",
        Slug:   "abc",
    }

    _, err := p.Get(ctx)
    if err != nil {
        log.Errorf(ctx, "New page: %v", err)
    }
}

上のコードでは、ドメイン名とスラグに一致するデータを取得しています。尚、今回の例では、返ってくるデータはゼロか1つというのを前提に実行しています。

ドメイン名に一致する全ての記事のメタデータを取得する例は後ほどご紹介します。

クエリーの新規作成

Go
1
q := datastore.NewQuery("Page")

Datastore でデータを検索して取得する際にはクエリーを作成する必要があり、それは『NewQuery』関数を使うことで実現できます。

引数は1つで、種類を文字列で渡します。

フィルターの追加

Go
1
q := datastore.NewQuery("Page").Filter("Domain =", p.Domain).Filter("Slug =", p.Slug)

NewQuery で作成したクエリーに、Filter関数を使ってフィルターを追加していきます。今回はドメイン名とスラグ両方が一致するデータを取得しています。

Filter関数の引数は2つで、1番目にプロパティ名と比較演算子、2番目に比較するデータを渡します。比較演算子は『=』以外、例えば『>』『<』『>=』『<=』を使用する場合はインデックスを作成する必要があります。

また、現在は AND検索のみがサポートされており、OR検索は出来ません。今回の例はドメイン名、スラグの2つのプロパティで検索しており、その2つのデータを満たす結果のみが返却されます。

ドメイン名、スラグのどちらか1方だけ一致するような OR検索は出来ません。

クエリーの実行

Go
1
it := q.Run(ctx)

クエリーを実行するには、作成したクエリーに『Run』関数を使用します。引数にはコンテキストを渡します。戻り値はイテレータです。

結果を取得

Go
1
key, err := it.Next(p)

イテレータに『Next』関数を使用すると結果を取得できます。引数にデータを格納するためのポインタを渡します。戻り値は、データに対応するキーとエラー値です。

クエリーを作成してデータを複数取得

Go
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
var pages []Page
for {
    var page Page
    _, err := it.Next(&p) // 次のデータを取得
    if err != nil {
        if err == datastore.Done { // データが無くなったらループを抜ける
            break
        }
        return err // エラー発生!
    }
    pages = append(pages, page) // スライスにデータを追加
}

複数の結果があると考えられる場合には、datastoreパッケージの Done というエラーが発生するまでループして、it.Next を使ってデータを取得し続けます。

it.Next 関数を実行してエラーが無ければデータを取得出来たということですので、スライスに追加していくと良いでしょう。

データの更新

Go
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
var p Page
key, err := it.Next(&p) // クエリー結果からデータを1つ取得
if err != nil {
    // エラー処理
}

// 取得したデータに新しいタイトルをセット
p.Title = newTitle

// キーを元に Datastore にあるデータを更新
_, err = datastore.Put(ctx, key, &p)
if err != nil {
    // エラー処理
}

データの更新はとてもシンプルで、今回の例では ドメインとスラグ両方に一致するデータを1件取得し、取得した構造体に新しいタイトルをセットし、Put関数を使って Datastore にあるデータを更新しています。

MySQL のように条件と一致したデータを指定したデータで直接更新することは出来ません。なので、一度データを取得してから新しいデータをセットし、キーを元に丸ごと Put します。

データの削除

Go
1
err := datastore.Delete(ctx, key)

Datastore からデータを削除するためには、クエリーなどを実行して対象のキーを取得し、Delete関数を呼ぶことで削除が可能です。

プロパティの追加と削除

プロパティ構成の更新もシンプルです。更新したいデータのキーを取得し、新しい構造体に必要なデータを入れて、そのキーに対して Put すれば更新できます。

さいごに

今回は最低限必要となるであろう機能のみをご紹介しましたが、キー情報を元にデータを取得したり、複数のデータを同時に更新する方法や、複数のデータを一括で削除する方法などなど、他にも沢山あります。

詳しくは『The datastore package (GCP公式ページ)』に載ってありますので、参考にしてみてください。