初心者向けgae/java説明資料
DESCRIPTION
課金の部分をもう少しうまくまとめたい。TRANSCRIPT
Google App Engine for Java概要とか従来のJavaアプリケーション構築時との
アーキテクチャの違い、注意点
shin1ogawa@株式会社トップゲート
自己紹介...shin1ogawaです!Google, Java, Eclipse, AppEngine, Wicket, Maven, Hudson, 構成管理... 等を主食とする Java屋です。• 参加しているOSSプロダクトやコミュニティ
• [SF.jp]Jiemamy...データベースの進化的設計
• [SF.jp]gae-j-samples...GAE/Jのサンプル、GAE/J/Maven統合プラグイン
• [SF.jp]asclipse...Amateras AIR GEAR用のAS3構文解析ライブラリ
• java-ja, wicket-ja, BOTTUKU...
株式会社トップゲート(http://www.topgate.co.jp)で、GoogleAppEngineやGoogleAppsを使用したソリューションを提供する為のお仕事をしています。
アジェンダ• Platformの特徴• 特徴• 制限
•効率よく情報収集する為のパス•実装する時の環境• Datastoreサービス• 用語と特徴• 実装する時の注意点
• その他のサービス
Platformの特徴無料• 無料でサービスを開始できるので、「小さく始める」事が簡単にできる。• 無料の場合はDisk使用量や転送量に上限が存在する。
• 規模が大きくなってから課金を考えれば良い。• その時に、AppEngine上にデプロイしたアプリケーションを「スケール可能」な設計に変更する必要がない。
• Google Appsを使っている場合はAppsのドメインを適用する事ができる。
Platformの特徴サーバの管理コストが不要• Googleの各種サービスを提供しているインフラの規模と質をそのまま利用できる。
• Googleが保守してくれている。• 物理的なインフラのコストはもちろん、ソフトウェアの設定・運用知識の習得・引き継ぎにかかるコストの削減ができる。
Platformの特徴自動スケール• 負荷が高くなると自動的にスケールする。• どの程度の負荷でスケールが始まるか?の具体的な数値は不明。時々管理コンソールのログで、新たなJVMが起動されたなという事を知る事はできるが…。
• 必要な時だけPlatformの許容量が大きくなってくれる。• AWS-EC2でも”Amazon Cloud Watch(有料)”を利用すれば、インスタンス(ノード)単位でスケールする”Auto Scaling”という機能はある。”Elastic Load Balancing”等もある。
• が、ミドルウェアがついていってくれないと結局難しいと思う。例えば、Relational Databaseを使用していれば、そこだけスケールできない、とかの問題が出てくるはず。• 分散KVSのSimpleDB(有料)を使う事はできる。
Platformの特徴様々な機能がそれぞれ分散している• アプリケーションのメインとなるWebContainer• staticなリソース専用のContainer• アプリケーションから使用するサービス• DatastoreService• MailService• URLFetchService• ImageService...
• cron実行サービスこれらは全てそれぞれが別ノードに分散されていて・それぞれがスケールされる。
クライアントに最も近いノードが選択される。
Platformの特徴管理コンソール• アプリケーションが稼働した際の様々な状況等を確認する事ができる。
受信/転送データ量, CPU使用時間, DISKの使用量, APIの使用回数, アプリケーションのログ...等々。
Platformの特徴“バージョン”の概念• 1アプリケーションに対して、複数のバージョンを存在させる事ができる。そのうち1つがデフォルトバージョン。• 例)リリースバージョン”release”をデフォルト、開発バージョンを”snapshot”にすると…• http://myappid.appspot.com/ ...最新バージョン• http://snapthot.latest.myappid.appspot.com/ ...開発バージョン
• バージョンごとに、別のアプリケーションとしてお互い影響を与えずにデプロイ、稼働できる。• datastoreは共有される• ひとつのアプリケーション内でpythonバージョンとjavaバージョンを混在させる事も可能。
Platformの特徴Webコンテナ• JVMはJDK1.6ベースのHotspot Client VM• Jettyをベースとした実装だが、将来的に実装は入れ替えられる可能性があるらしい• Jettyは単なるJEEインターフェースとApp Engine間のアダプタ、としてしか機能していない
• JettyのComet機能も使えない(30秒制限もあるし、stream配信も不可)
• スケールする=リクエスト毎にJVMは違うノード上で動作する可能性がある、という点に注意が必要• 変更が前提のstatic変数は使ってはいけない
• デフォルト状態ではセッション、https共にoff状態
Platformの特徴Webコンテナ/セッションについて• appengine-web.xmlでsessions enabled=trueに設定すると使用可能
• セッションオブジェクトはDatastoreに保存されるので、スケールの対象となる(どのノードからも参照できる)• _ah_session というKindで保存される
• Applicationのコンテナ内のセッションのデシリアライズで落ちてしまう事があるが、SessionCleanupServlet なるモジュールが提供されているのでそれでなんとかする• 普通のServletと同じようにweb.xmlに登録すれば使用可能になる
Platformの制限制限の種類• 全体の量的な制限• Diskの使用量、ファイル数
• 期間単位の量的な制限• API単位の制限• Java的な制限
量的な制限は有料サービスにする事で上限を増やす事ができる(無くなる訳ではない)ようになるものがあるが、有料版でも変わらない制限も存在するので注意が必要。
詳細: http://code.google.com/intl/ja/appengine/docs/quotas.html
Platformの制限全体の量的な制限• Diskの使用量• アップロードできるファイル数は3000まで。• Datastoreに保存できる容量は1GBまで。
• 一日にデプロイできる回数は250回まで• ...等々
高負荷が予想されるアプリケーションの場合はappengineチームへ相談すると個別に制限を解除してもらえる事もある、らしい?
Platformの制限一定期間内の量的な制限• 一回のリクエストを受けてレスポンスを返すまでの処理時間は30秒以内
• 一回のリクエスト/レスポンスのサイズは10MBまで• 送信できるメールの宛先は一日で2000件まで• メールAPIの呼び出し回数は一日で7000回まで• ...等々, 日単位/分単位の制限がある。
現在どれだけ消費したか?は管理コンソールで各制限項目毎に確認する事ができる。
Platformの制限API単位の制限• Datastoreの一行に格納できるサイズは1MBまで• MemcacheServiceでひとつのKeyに割り当てる事ができるサイズは1MBまで
• URLFetchServiceの応答時間は10秒以内に完了する必要がある
• Datasotreでクエリした結果の件数は1000件まで• JDO経由であれば1000件以上の取得も可能...ではある
• ...等々
Platformの制限Java的な制限• ファイルシステムへのアクセスができない。• Threadの生成ができない。• Socketの生成ができない。• JDKに含まれるclassの中で実装されていない物がある。• white-listとして実装されているclassが公開されている -> http://
code.google.com/intl/ja/appengine/docs/java/jrewhitelist.html
• 明示的にGCを実行する事が出来ない。• ...等々
効率よく情報収集する為のパスその1• 書籍: Googleを支える技術• 公式ドキュメント• Best practices for writing scalable applications
• Google Group• Google App Engine for Java• Google App Engine• Google-App-Engine-Japan
• Google Code• googleappengine• datanucleus-appengine
効率よく情報収集する為のパスその2• 毎月第1,第3水曜日にIRC上で開催されるOffice Hour• irc://irc.freenode.net/#appengine• Google Calendar: [email protected]
• Google App Engine Blog• AppEngine Cookbook• Under the covers of the Google App Engine Datastore
• スティルハウスの佐藤一憲氏によるtips集• shin1ogawa個人的にも色々公開しています• 公開しているプロジェクト群(主にmaven plugin関連)• ブログに記録したGoogle App Engineメモ
効率よく情報収集する為のパスその3
情報収集についての余談: AppEngineSDKのソースはまだ公開されていないので、逆コンパイルするしかない。が、利用規約で禁止されている。が、Hackathon参加時にGooglerに「逆コンパイルしないと発表できないんだが?」と質問してみたところ「調査に必要なら逆コンパイルするのは仕方無いんじゃね?でも改変した物を公開したりするのはダメだよ」という話でしたから、調査目的ならまぁ問題無いんでしょぅ。
今後、個人的なノウハウや、株式会社トップゲートの仕事で抽出したノウハウを整理・公開していく予定ですので期待して見守って頂けると嬉しいです。
• https://sites.google.com/a/topgate.co.jp/
実装するときの環境開発環境• Google Plugins for Eclipse(SDKを含む)• モジュールの依存、ローカル実行環境構成を含んだEclipseプロジェクト生成• AppEngineへのデプロイ
• Ant(SDKに含まれている)• dev_appserverによるアプリケーションの起動• app_cfgを使ったAppEngineへのデプロイ
• Python版のSDK(JavaSDKはできない事ができたり)• indexのvacuum(IndexのErrorが発生したときとかに必要)• bulkloader(DatastoreのデータのExport/Import)
• NetBeansやIntelliJ IDEA用のpluginもある…らしい
実装するときの環境ローカル実行環境に必要な特殊なファイル• war/WEB-INF/• appengine-web.xml
...appengine用のアプリケーション定義ファイル
• datastore-indexes.xml...手動で記述するindex定義
• cron.xml ...cronジョブ定義• appegnine-generated/• datastore-indexes-auto.xml
...自動生成されるindex定義
• local_db.bin ...Datastoreがデータを保存するファイル
実装するときの環境テスト環境• 単体テストのためのスタブモジュールを使用する• SDKを起動して統合テストするのであれば、単体テスト環境と同じ方法で作成したlocal_db.bin(単なるバイナリファイル)をSDKから読み込む事ができる• スタブで作成したlocal_db.binにはAppID, versionIdが含まれているので、その辺りを少し細工してlocal_db.binを作成すればおk!
• Mavenでプロジェクトを構築する事も可能• mvnsearch というホストにappengineモジュールが配置されている。• shin1ogawaがMavenからSDKを起動するpluginも公開してます。
実装するときの環境クラウド上• 今のところクラウド上での動作の調査等についてはロギングを使用して地味に調査するしか無いと思われる。
• ロギングには java.util.logging を使用する必要がある。• war/WEB-INF/appengine-web.xml で logging.properties のパスを指定する必要がある
• RemoteApiServletというモジュールがSDKに含まれており、気になって仕方無いが、まだ見ていない(ゴメンナサイ)。何かに使えるかも。
実装するときの環境appengine-web.xml• セッション機能のon/offを設定する• https機能のon/offを設定する• クライアントから直接アクセス可能なstaticファイルとして扱うリソースのパターンを設定する
• プログラムから読み込むファイルとして使用するリソースのパターンを設定する
• logging.propertiesのパスを設定する• 他は、通常のweb.xmlも使用する
Datastoreサービス用語• Kind...RDBでいう「テーブル」• Entity...RDBでいう「レコード」• Property...RDBでいう「フィールド」• Index...RDBと同じ• Key...RDBとほぼ同じ、Entityの主キーの事• Filter...SQLでいうWhere句の条件指定みたいな事• equality filter...演算子で言う”=”でのFilter• inequality filter...”>”, “>=”, “<=”, “<“
• Entity Group...親子関係が構築されたEntityの固まり
Datastoreサービス特徴• BigTableに保存される• ...がしかし、履歴機能は使えない
• RDBとの最大の違いは「スキーマレス」という事• 例(属性名=値で表現。keyという属性名が主キーって事で)• [0]id=1, name=”Hoge”• [1]id=2, height=170.5, weight=65.5• [2]id=3, tag=[“Apple”, “Java”]
• つまり、一行読み込み、読み込んだ中身を見て初めて構造が判明する。
Datastoreサービス特徴• 書き込みが遅い• ちょっとしたEntityの保存でも80ms/1件かかったりする• RDBと同じくIndexのサイズの影響を受ける
• 読み込みの速度はEntity数に依存するのではなく、読み込む件数(検索結果件数)に依存する。
• クエリ?そんなものは無い。ファイルのスキャンができるだけ。• Queryというクラスが複数用意されているが、クエリという名前を持つだけの幻だと思っておこう(使うけどね)。
• クエリは無い、ファイルのスキャン、と理解すると良い。
Datastoreサービス特徴• 書き込みが遅い• ちょっとしたEntityの保存でも80ms/1件かかったりする• RDBと同じくIndexのサイズの影響を受ける
• 読み込みの速度はEntity数に依存するのではなく、読み込む件数(検索結果件数)に依存する。
• クエリ?そんなものは無い。ファイルのスキャンができるだけ。• Queryというクラスが複数用意されているが、クエリという名前を持つだけの幻だと思っておこう(使うけどね)。
• クエリは無い、ファイルのスキャン、と理解すると良い。
DatastoreサービスEntity Group• 親子階層を構成したEntity同士の固まり。• RDBでいうRelationとは関係がない。• Java的な継承関係とも関係がない。• 例)
• 会社Kind:Entity:id=1• 組織Kind:Entity:id=2
• 個人Kind:Entity:id=3• 会社Kind:Entity:id=4
• 組織Kind:Entity:id=5
DatastoreサービスKey• Entityを一意に識別するためのProperty• Long型(id)かString型(name)をKeyのIdとして使用できる。
• EntityGroupで親子関係を構成された場合は、親EntityのKeyを子Entityに引き継ぐ(含む)• 親Entity: Kind=Parent, id=1• 子Entity: Kind=Child, name=“[email protected]”• 上記の時の子EntityのKeyは以下のようになる。• Parent(1)/Child(“[email protected]”)
DatastoreサービスProperty• Entityが保持する属性。• java.langパッケージに含まれるプリミティブな型• String型の最長は500文字まで
• Text型• 500文字を超えるString用
• Blob型• バイナリオブジェクト用
• List Property• ListとかSetとか。
DatastoreサービスIndex• Propertyの並び順• Kind Index• KindをKeyでスキャンするためのIndex
• Single Property Index• ひとつのPropertyを対象にしたIndex
• Composite Index• 複数のPropertyを対象にしたIndex
• Text型、Blob型のIndexは定義できない
インデックス爆発(index explosion)と呼ばれる現象に注意!安易にComposite Propertyとかに複雑なIndexを適用すると危険。
DatastoreサービスTransaction• 行単位の更新でACID特性を備えている• 基本的には後から更新した方が勝つ• JDOを使うと楽観的排他制御を行う事もできる• もちろんロジックで楽観的排他制御をしても良い
• ひとつのTransaction内ではひとつのEntityGroupに含まれるEntityの更新しかできない!• この点を考慮した設計が必要になる。
DatastoreサービスQuery• 使用できる条件は ’<‘, ‘<=’, ‘==’, ‘>=’, ‘>’のみ• Likeは無い。前方一致は可能(startsWith())• 複数の条件はAnd接続のみ可能• Javaでは ‘IN’, ‘!=’ は使用できない• 複数のPropertyに対して条件を指定する事ができるが、inequality filterが適用できるのはひとつのPropertyだけ
• max()とかmin()とか、便利な集計関数は存在しない。• Joinも無い。
DatastoreサービスQueryファイルスキャン• Indexで定義された特定のソート順で並んだ中から、特定の範囲のデータをブロックで転送する → Indexを定義してソートされていなければ取得できない• Kind Index, Single Property Indexはローカル環境で実際にDatastoreへアクセスする事で定義が自動生成される。
• Composite Indexは datastore-indexes.xml に自分で定義してやる必要がある。
DatastoreサービスNGワード• 連番• アプリケーションが実行されるノードが複数にまたがるため(?)、シーケンスな
ID等はサポートされていない。• 自動採番した番号は、1,2,3,1001,1002...のように採番される。
• カウンタ• 基本的には1000件以上を数える事が困難。
• JDOを使えば1000件以上でもカウントできるが、遅すぎて話にならない。
どちらも、スケールを前提にした場合に、実装するコスト・実行時のパフォーマンス、を考えてGoogleがあえて実装していないという事か。工夫のtipsもあるが「要求の実現にコストをかける”価値”がある機能なのか?」をよく考えた方が良い。
Datastoreサービススキーマの設計について• 集計するような導出項目、カウンタは別途保持する• 更新時に計算結果や中間結果を計算しておく• クエリ結果のカウントは難しい
• ジョインが必要ならジョイン済みのKindを作成する• どの部分をEntityGroupとするか(ひとつのTransactionで処理したいか?)、が肝• 補償Transactionも考慮する
• インデックス爆発を起こさないように注意する
Datastoreサービスよくあるパターン/カウンタ
• 更新する行を分けてカウンタを分割する• ID, カウンタに「ShardingNo」を追加する• 更新時に “ID,ランダム値(ShardingNo)” をキーにして更新を行う事で更新対象行をばらつかせ、更新要求がひとつの行に集中する事をする防ぐ。
• 読み込み時には分割されたカウンタを合計する
更新頻度が高いカウンタで使用する。更新頻度が高い=ひとつの行に更新が集中し、更新に失敗する可能性が高くなる。
Datastoreサービスよくあるパターン/ページング
• offset/limitの指定でも可能だが、遅くなるかも。• ページ内の件数+1件取得して常に「次のページの先頭」を保持しておく。それを次ページのFilterに使用する。• ユニークにならない条件を使う場合は「ユニークになる条件」をFilterに追加するようにすればおk。
• ソート順が重要• propertyを結合してひとつのpropertyとして扱う、等の小細工もアリ。
大量のデータの中の1ページ目、2ページ目...を順に表示する処理。
Datastoreサービス実装• JDO, JPA, Low-level APIの3種類存在する• 個人的に、JPAはオススメできない• JDOが最もドキュメントが豊富で、既存のORMに慣れた人には良いかもしれない。• しかし、本来のDatastoreから離れている気もする
• 最もパフォーマンスを発揮するのは Low-level API• JDOも内部では Low-level API に接続している
通常のJEEプラットフォーム上でも動作するように設計・実装すれば便利やん!...等と思っていた時期もありました。でもそれは設計をゆがめる事に繋がると思うんで、考えない方が良いかも。どーしても考えたいなら、実はLow-level APIが近道なのかも?
DatastoreサービスJDO/Entityの定義@PersistenceCapable(identityType = IdentityType.APPLICATION , detachable = "true")@Version(strategy = VersionStrategy.VERSION_NUMBER)public class Entity { @Persistent @PrimaryKey(valueStrategy = IdGeneratorStrategy.IDENTITY) Key id;
@Persistent String value;
@Persistent(defaultFetchGroup = “true”) Text text;}
プリミティブな型以外は自動Fetchされない。自動Fetchしたいなら明示的に指定する。@Version でJDOによる楽観的排他制御の対象となる(OPT_VERSIONというpropertyが追加される)。
DatastoreサービスJDO/Owned Relationshippublic class Parent { ... @Persistent(defaultFetchGroup = “true”) List<Child> children;}
public class Child { @Persistent @PrimaryKey(valueStrategy = IdGeneratorStrategy.IDENTITY) Key id; ...}
ChildにはKey型(またはencoded-pkなString型)の主キーが必須。
DatastoreサービスJDO/Owned Relationship(Keyのみ)public class Parent { ...}
public class Child { ... @Persistent @Extension(vendorName="datanucleus", key="gae.parent-pk", value="true") Key parentKey; ...}
親子Entity間で完全に分離してしまっているように見えるが、Childの保存前に parentKey に親EntityのKeyを設定しておけばEntityGroupが構成される。Ancestor Keyを使用したクエリを使う事で「子」として取得する事が出来る。
DatastoreサービスJDO/Owned Relationship(双方向)public class Parent { ... @Persistent(defaultFetchGroup = “true”) List<Child> children;}
public class Child { ... @Persistent(mappedBy = “children”) Parent parent; ...}
mappedByで親が自身を保持しているpropertyの名称を指定する。
DatastoreサービスJDO/Unowned Relationship@PersistenceCapable(identityType = IdentityType.APPLICATION)public class Entity { @Persistent @PrimaryKey(valueStrategy = IdGeneratorStrategy.IDENTITY) Key id;
@Persistent String value;
@Persistent Sey<Key> otherEntityRefs;}
DatastoreサービスJDO/保存PersistentManager pm = ... // PersistentManagerをどこかから取得;Parent parent = new Parent();parent.set......List<Child> children = new ArrayList<Child>();children.add(child1);parent.setChildren(children);
try { Transaction transaction = pm.currentTransaction(); transaction.begin(); pm.makePersistent(parent); transaction.commit();} finally { if (transaction.isActive()) transaction.rollback();}
DatastoreサービスJDO/クエリPersistentManager pm = ... // PersistentManagerをどこかから取得;Query query = pm.newQuery(Entity.class);query.setFilter(“name == param”);query.declareParameters(“java.lang.String param”);query.setOrdering(“name asc, key desc”);@SuppressWarnings("unchecked")List<Entity> list = (List<Entity>) query.execute(“hoge”);...
Key key = KeyFactory.createKey(“Entity”, “keyName”);Entity entity = pm.getObjectById(Entity.class, key);
Key key = entity.getOtherEntityKey();OtherEntity otherEntity = pm.getObjectById(OtherEntity.class, key);
DatastoreサービスJDO/特殊なクエリPersistentManager pm = ... // PersistentManagerをどこかから取得;Query query = pm.newQuery(Entity.class);query.setFilter(“name == param”);query.declareParameters(“java.lang.String param”);query.setResult(“count(this)”);@SuppressWarnings("unchecked")Integer count = (Integer) query.execute(“hoge”);...
query.setResult(“key”);List<Entity> list = (List<Entity>) query.execute(“hoge”);
返り値セットに”count(this)”を指定すると、件数が返される。返り値セットに”key”を指定すると、Keyだけの検索が行われる(速い)
DatastoreサービスJDO/その他• オブジェクトの状態に注意する。• transient, hollow, persistent/detached• JDOHelper#getObjectState()で確認できる
• PersistenceManagerが永続化処理を行うタイミングを InstanceLifecycleListener でフックできる。
• PersistenceManager#execute()が返すListオブジェクトはserializeできないので注意
• PersistenceManager#retrieve[All]()で全てのPropertyを強制的にfetchできる。
DatastoreサービスLow-level API/保存Entity entity = new Entity(“Kind”); // Low-level APIのEntityクラスentity.setProperty(“name”, “hoge”);entity.setProperty(“height”, 170.5);
DatastoreService service = DatastoreServiceFactory.getDatastoreService();service.put(entity);
Entity[] entities = ....service.put(entities);
属性と値のマップを格納するEntityオブジェクトを使用する。タイプセーフではなくなってしまうが、シンプルかつ速い。
DatastoreサービスLow-level API/クエリQuery query = new Query(“Entity”);query .addFilter(“name”, FilterOperator.GREATER_THAN_OR_EQUAL, “hoge”) .addSort("name", SortDirection.ASCENDING) .addSort("__key__", SortDirection.ASCENDING);
DatastoreService service = DatastoreServiceFactory.getDatastoreService();List<Entity> entities = service.prepare(query).asList( FetchOptions.Builder.withOffset(0));
Key key = KeyFactory.createKey(“Entity”, “keyName”);Entity entity = service.get(key);
主キーに対応するproperty名は”__key__”と記述する。
DatastoreサービスLow-level API/特殊なクエリQuery query = new Query(“Entity”);query .addFilter(“name”, FilterOperator.GREATER_THAN_OR_EQUAL, “hoge”) .addSort("name", SortDirection.ASCENDING) .addSort("__key__", SortDirection.ASCENDING).setKeysOnly();
DatastoreService service = DatastoreServiceFactory.getDatastoreService();int count = service.prepare(query).countEntities();
Entity entity = service. prepare(query).asSingleResult();
setKeysOnly()で、キーだけの検索が行われる(速い)asSingleResult()は0件ならnull、1件ならそのEntity、2件以上はException
その他のサービスMemcacheService• よく使うデータ等をメモリ上にキャッシュしておく• 最大容量は不明(調査不足かも、ゴメンナサイ)• 有効期限を設定する事ができる
• cacheが存在する、という前提の設計をしてはいけない• 実装には JCache(javax.cache), Low-level APIの2種類が使用できるMemcacheService service = MemcacheServiceFactory.getMemcacheService();service.put("key", list);Object cachedObject = service.get("key");service.delete("key");
その他のサービスURLFetchService• 外部のURLへリクエストする• 実装には標準APIのjava.net.URLConnectionとLow-level
APIの2種類が使用できる。• 内部的にはcommons-httpclientが使用されている模様
URLFetchService service = URLFetchServiceFactory.getURLFetchService();HTTPResponse response = service.fetch(new HTTPRequest(new URL(“”)));if (response.getResponseCode() != 200) throw new RuntimeException();byte[] bytes = response.getContent();
その他のサービスcronジョブの仕組み• 定期なタイミングに、特定の処理を起動する• 起動する処理はURLで指定する。つまりFilter/Servlet。
• 管理コンソールで確認が可能• デフォルトバージョンのみ有効
<?xml version="1.0" encoding="UTF-8"?><cronentries> <cron> <url>/cron/fetch?name=jiemamy-sf</url> <description>fetch the jiemamy SF.jp timeline.</description> <schedule>every 1 minutes</schedule> </cron></cronentries>
その他のサービス他にもありますが詳しい説明は省略• MailService• 実装には JavaMail(javax.mail), Low-level APIの2種類が使用できる• メールの送信ができる
• ImageService• low-level APIのみ提供されている。• 画像を扱う処理...回転とか色々できるんじゃないかな
• 殆ど使った事がありません、ゴメンナサイ
• UserService• Google Accountの認証を行う事ができる。• 基本的にはGoogleが用意したページにリダイレクトするが、これを独自に実装したとか、そういった情報が最近 appengine-java MLに流れていた。
各サービスの実行の仕組みApiProxy• com.google.apphosting.api.ApiProxy• サービスを実行する実装を入れ替え可能にする為のProxy
• com.google.apphosting.api.ApiProxy.Delegate• サービスを実行するためのインターフェース
• com.google.appengine.tools.development.ApiProxyLocal• 上記 Delegate のローカル用(?)の実装
単体テスト時には、ApiProxyLocalを継承した実装を用意する事で各種サービスの実行の前後をフックしたり、データを入れ替えたりする事ができる。
各サービスの実行の仕組みDelegate#makeSyncCall()• public byte[] makeSyncCall(
Environment environment, String packageName, // service名String methodName, // serviceのメソッド名byte[] requestBytes) throws ApiProxyException
byte[]に関しては ProtocolBuffer によりシリアライズされたオブジェクトのbyte配列だと思われる。このあたりを調べると面白い事ができるかもしれない。というのも、SDK内に RemoteApiServlet なるクラスが含まれており、これはクライアント側とhttp経由でmakeSyncCall()できるような仕組みに見えるため、このServletを使ってあれこれ[やってくれ|やっていいYO]!、という事ではないか?と考えられる。
まとめ
• 従来のUI設計、モデル設計の手法をうまく活用する…とか従来に近い方法で設計・実装できるように調整する…のではなく、素直に「手法が変わった」と受けとめる方が素直。
• スケールする(並列動作可能とか)アプリケーション、と考えるとどっちみちアプリケーションの設計手法は変わっていく。Google App Engineの”制限”はそのための良い”養成ギブス”、と前向きに捉えよう。
従来のUI設計、モデル設計、実装、の手法に引きずられてはいけない
ご清聴ありがとうございました!
shin1ogawa@株式会社トップゲート