GoogleAppEngineのJavaアプリケーションからCloud Datastoreを利用するための
Objectifyというライブラリがあります。
2018年4月に、このライブラリのv6がリリースされたのですが、
過去のバージョンと大きく仕組みが変わっているので、その内容のメモを残しておきます。

objectify:
https://github.com/objectify/objectify

v6での最も大きな変更点

Objectify v6の最も大きな変更点は、利用しているSDKの切り替えです。
以下のように利用するSDKが変更になっています。

  • v5まで: Google App Engine SDK
  • v6から: Google Cloud SDK

この2つのSDKの違いですが。
「Google App Engine SDK」はAppEngine上で動作するアプリケーションでのみ利用可能なSDKです。
「Google Cloud SDK」は任意の環境からGCPの各サービスを利用するためのSDKです。

これによってv6からは、
(AppEngine以外の)任意の環境からDatastoreを利用出来るようになりました。

ですが、逆に出来なくなったこともあり。
v5まででは、AppEngine上のmemcacheに、Datastoreのアクセスをキャッシュしていたのですが、
v6では、このキャッシュが(標準では)出来なくなりました。

AppEngineのmemcacheはAppEngineからのみ利用可能で、
利用する為には「Google App Engine SDK」を利用する必要があるためです。

# 2つのSDKの見分け方ですが、
# パッケージ名が com.google.appengine で始まるものが Google App Engine SDK
# com.google.cloud で始まるものが Google Cloud SDK と見分ければ良いと思います。

v6でのローカル開発環境

v5までのObjectifyで開発を行う場合は、AppEngineのローカル開発用サーバを利用しますが、

(v5までの場合) Javaローカル開発用サーバの使用 | GCP
https://cloud.google.com/appengine/docs/standard/java/tools/using-local-server?hl=ja

v6で開発を行う場合は、以下のどちらかの方法をとることになります。

  • Datastoreエミュレータを利用する
  • 直接GCP環境のDatastoreにアクセスする

(v6の場合) Cloud Datastoreエミュレータの実行 | GCP
https://cloud.google.com/datastore/docs/tools/datastore-emulator?hl=ja

# 紛らわしいのですが、このCloud Datastoreエミュレータと
# AppEngineのローカル開発用サーバにあるdatastoreのエミュレータ機能は全く別のものです。

Cloud Datastoreエミュレータの起動と実行

Cloud Datastoreエミュレータの起動と実行は、以下のコマンドになります。

インストール

gcloud components install cloud-datastore-emulator

実行

gcloud beta emulators datastore start --host-port=localhost:8484

Objectifyから接続する場合は、以下のようなコードでObjectifyServiceを初期化します。

ObjectifyService.init(new ObjectifyFactory(
    DatastoreOptions.newBuilder()
        .setHost("http://localhost:8484")
        .setProjectId("my-project")
        .build()
        .getService()
));

v6でのdatastoreのキャッシュ

v6でdatastoreのキャッシュを行う場合は2つの方法があります。

  • 自前のmemcachedでキャッシュする
  • AppEngineのmemcacheにキャッシュする(要実装)

後者のAppEngineのmemcacheにキャッシュがv5までと同様の挙動になりますが、
Objectifyに用意されているMemcacheSerivceのInterfaceを持った、
AppEngineのMemcacheに保存する為のクラスを自分で実装する必要があります。
※将来的にはObjectify標準のものが提供されるような気がしていますが。。

自前のmemcachedでキャッシュする

memcachedを立ち上げて、
ObjectifyServiceを初期化時に、memcachedの接続情報を指定します。

ObjectifyService.init(new ObjectifyFactory(
    DatastoreOptions.newBuilder().setHost("http://localhost:8484")
        .setProjectId("my-project")
        .build().getService(),
    new SpyMemcacheService(new MemcachedClient(new InetSocketAddress("localhost", 11211)))
));

AppEngineのmemcacheにキャッシュする

AppEngineのmemcacheを使いたい場合は、
以下のMemcacheServiceのInterfaceを実装したクラスを自分で実装し、
そのクラスを、memcachedを使う時に指定した「SpyMemcacheService」の代わりに指定します。

MemcacheService.java
https://github.com/objectify/objectify/blob/master/src/main/java/com/googlecode/objectify/cache/MemcacheService.java

AppEngineのmemcacheには「Google App Engine SDK」の
「com.google.appengine.api.memcache.MemcacheService」でアクセス出来ます。

com.google.appengine.api.memcache.MemcacheService | GCP
https://cloud.google.com/appengine/docs/standard/java/javadoc/com/google/appengine/api/memcache/MemcacheService?hl=ja

Memcacheの概要 | GCP
https://cloud.google.com/appengine/docs/standard/java/memcache/?hl=ja

私が実装してみたのが、以下のコードですが、
全く動作確認していないので、イメージつかむ程度で参考にしてください。

AppEngineMemcacheClientService.java
https://github.com/takemikami/spring-boot-objectify-sample/blob/master/src/main/java/objectifysample/AppEngineMemcacheClientService.java

SpringBootアプリケーションでの利用

以下のGitHubリポジトリに、
SpringBootのアプリケーションからObjectify v6を利用したサンプルを作ってみました。

spring-boot-objectify-sample | github
https://github.com/takemikami/spring-boot-objectify-sample

gradle bootRun で起動するとキャッシュを利用しない、
gradle appengineRun で起動するとAppEngineのローカル開発サーバのMemcacheでキャッシュします。

以下のObjectifyConfigクラスで切り替えています。

ObjectifyConfig.java
https://github.com/takemikami/spring-boot-objectify-sample/blob/master/src/main/java/objectifysample/ObjectifyConfig.java

JUnitでのテスト方法

ObjectifyのコードをJUnitでテストする場合は、
「LocalDatastoreHelper」を使ったdatastoreのエミュレーションで実行します。

以下にサンプルコードを作ったので、参考にしてください。

https://github.com/takemikami/spring-boot-objectify-sample/blob/master/src/test/java/objectifysample/HelloControllerTest.java

ここで1点、はまったポイントがあったのですが、
「LocalDatastoreHelper」はデフォルトでは、consistencyが0.9になります。
この設定では、
0.1の割合で、書き込みオペレーション実行直後の結果反映が行われず、
一定の割合で単体テストが失敗してしまいます。

なので、「LocalDatastoreHelper」の初期化時は、明示的にconsistencyに1.0を指定します。

private static LocalDatastoreHelper helper = LocalDatastoreHelper.create(1.0);;

これはDatastoreの特性によるものですが、 この辺りは、以下の結果整合性の説明が参考になります。

Google Cloud Datastore での強整合性と結果整合性のバランス | GCP https://cloud.google.com/datastore/docs/articles/balancing-strong-and-eventual-consistency-with-google-cloud-datastore/?hl=ja