ラベル GoogleAppEngine編 の投稿を表示しています。 すべての投稿を表示
ラベル GoogleAppEngine編 の投稿を表示しています。 すべての投稿を表示

2015年6月10日水曜日

【GAE】メール送信

株式会社ジェニシス 技術開発事業部の遠藤 太志郎(Tacy)です。

現在はクラウド基盤「Google App Engine(以下、GAE)」の連載中です。

今回のテーマはメール送信です。

実装


GAEのメール送信は簡単です。
まずはソースからどうぞ。

public void send() throws Exception {

        logger.fine("メール送信:開始");

        Properties props = new Properties();
        Session session = Session.getDefaultInstance(props, null);

        MimeMessage msg = new MimeMessage(session);
        msg.setFrom(new InternetAddress("fromアドレス"));
        msg.addRecipient(
            Message.RecipientType.TO,
            new InternetAddress("toアドレス"));

        msg.setSubject("タイトル", Encode.ISO2022JP);
        msg.setText("本文");

        // 送信実行
        Transport.send(msg);

        logger.fine("メール送信:終了");

    }

特に特記事項はありませんね。
Java標準のメール送信ライブラリ「JavaMail」が使用出来ます。

サンプルはネット上に無数に転がっていますのでお好きなサイトを参考にして下さい。

特記事項:FROMアドレス

しかし、やっぱりGAEには特性があるんですね。
それは「FROMアドレス」の制限です。

メールというのは非常に脆弱性の多いツールでして、「スパム」というものがあります。日本語で訳せば迷惑メールです。
GoogleはGAEをスパムとして使われてしまうのを防ぐ為にスパム防止機能が入っています。それがFROMアドレス制限です。

以下の2種類のアドレスしかFROMに使用出来ません。

  • Google App Engine で Application の管理者として指定しているアドレス
  • 現在のログインユーザのアドレス

なので、「メール送信者は送信専用のダミーアドレスにしておきたい」とかは出来ないのです。
ちゃんと使用可能なアドレスをそのプロジェクトの『管理者』として登録する必要があります。ご注意を。

Gmai連動

しかし、不便なことばかりではありません。
GAEから送信したメールは、何とGmailと連動しているのです。

会社のアドレスなので画面キャプチャは載せられませんが、例えば上記の送信パターンの後者である「GAEアカウントでログインしているユーザのアドレス」で送信した場合、
Gmaiから「送信済みメール」を開きますと、GAE上で送信したメールの送信履歴が残っています。

なので、「メールの送信履歴を保存したい」という要望があった場合でも、自分で履歴管理機能を作り込まずとも、Gmailを見れば自動的に出来ているわけです。
これは凄い機能ですね。

業務用としては非常に信頼性を担保してくれるものだと思います。

費用

このようにキラリと光る便利なGAEメール送信機能ですが、大きな落とし穴も。
課金があるんですね。

ズバリ、
  • 最初の100通は無料。
  • 以後は、100通毎に0.01$
このような料金となっています。
メール如きで金を別枠にするなんて……、と思いますが、これもスパム対策なのかもしれませんね。

1通1円くらいの費用感覚ですので、何万通も無数にスパムを送ると赤字になるということにご注意下さい。

終わりに

以上です。

GAEからのメール送信は非常に簡単ではありますが、私としては「課金」がネックだと思います。

メールなんて前時代的なツールは使うな、というのが先進的なんじゃないでしょうか。

要件にも依りますが、どうしてもメールを大量送信しなければならないという都合がある場合、私だったらメール送信サーバだけは別に用意しますね。

メールの文面や宛先情報はGAE上に保存しておいて、メール送信サーバで常駐するメール送信バッチがGAEにアクセスする。
そしてデータだけ取得し、メール送信サーバにてメールを送信する。

こういう手順なら課金上限には悩まされずに済みますよね?

このように、場合によってはGAEの強い部分だけを活用し、弱い部分は潔く別に用意するという柔軟な戦術もGAEには必要なのです。

2015年5月28日木曜日

【GAE】初級実装編4 一意制約(更新パターン)

株式会社ジェニシス 技術開発事業部の遠藤 太志郎(Tacy)です。

現在はクラウド基盤「Google App Engine(以下、GAE)」の連載中です。

今回も前回に引き続き一意制約についてです。

やっぱりGAEに一意制約は無い


前回にもテーマにしましたが、GAEに一意制約はありません。
しかし主キー制約はあります。
よって、主キーのみのテーブルを作って主キー制約を実現するしか無いです。

まずは前回にも記載した新規登録の一意制約ソースを以下に張ります。

public Key insert(Shohin model) throws Exception {

  /*
   * トランザクション開始
   */
  Transaction tx = Datastore.beginTransaction();

  try {

   Key key = null;

   /*
    * 一意制約専用テーブルに登録する。
    */
   if (Datastore.putUniqueValue(Shohin.UNIQUE_SHOHIN_NAME, model.getShohinName())) {

    /*
     * 一意制約専用テーブルに登録が成功した場合
     */
    key = super.put(model);

   } else {

    /*
     * 一意制約エラー
     */
    throw new BusinessCheckException("対象の商品名は既に登録されています。");

   }

   /*
    * 正常な場合はコミットする。
    */
   tx.commit();

   return key;

  } catch (Exception e) {

   /*
    * エラーになったらロールバックする。
    */
   tx.rollback();
   throw e;

  }

 }


フローを手短に表現すれば以下になります。

①一意制約テーブルに新規登録する。
⇒失敗したら処理終了。
⇒成功したら次に進む。
②新規登録する。

でも、これって新規登録ですよね。

更新の時はどうやるの?というのが今回のテーマです。

ガチンコ勝負

実はこのブログは、執筆前にネットサーフィンしてネタをかき集めてきておりまして、
上記のソースも、実は本質的には他サイトのパクリのようなもの。(自分で書き直していますけどね)

上のソースの元となった、新規登録系のサンプルソースはネット上に沢山転がっていました。

しかし、どれだけ探しても「更新系」の話題がサッパリ見つからないのです。

恐らく、「更新系」については、特に便利な機能なんか存在しないのでしょう。
ガチンコでロジックを組み上げる以外に手は無いというのが私の結論です。

気合い入れて行きましょう。

ロジック


正確に言いますと、GAEにあるのは「put」と「delete」ですから、「新規登録/更新」などという区分けは技術的にはありません。
しかし人間の操作感では「新規登録/更新」では別物でしょう。
「put」を「新規登録/更新」で使い分けるには、以下ロジックが必要になります。


・対象データを検索する。
⇒データが存在していなければ新規登録。
⇒データが存在していれば更新。


一回find処理が必要になります。普通のSQLみたいにupdateは存在しません。

その上で、delete&insertで一意制約ロジックを実現することになります。

そのフローはこちら。

①一意制約対象のカラムが更新前と更新後で違うかどうかを判定する。
⇒同じである場合は普通にputして終わり。
⇒違う場合胃は次に進む。
②一意制約テーブルに更新後のカラムを新規登録する。
⇒失敗したら処理終了。
⇒成功したら次に進む。
③putする。
④一意制約テーブルに更新前のカラムを削除する。

一意制約カラム削除のメソッドは「Datastore.deleteUniqueValue」です。

これを使用したソースはこちら。

public Shohin update(Shohin updateModel) throws Exception {

   Transaction tx = Datastore.beginTransaction();

   try {

       /*
        * 既存を取得
        */
       Shohin model = super.get(updateModel.getKey());
       model.setEntityUpdate(updateModel);
       super.put(model);

       /*
        * 一意制約項目が既存と同じであれば、そのまま更新する。
        */
       if (StringUtils.equals(model.getShohinName(), updateModel.getShohinName())) {

           model.setEntityUpdate(updateModel);
           super.put(model);

       } else {

           /*
            * 一意制約項目と異なる場合は、更新を行ってから過去の一意制約照合用レコードを消す。
            */
           if (Datastore.putUniqueValue(Shohin.UNIQUE_SHOHIN_NAME, model.getShohinName())) {

               model.setEntityUpdate(updateModel);
               super.put(model);
               Datastore.deleteUniqueValue(Shohin.UNIQUE_SHOHIN_NAME, updateModel.getShohinName());

           }

       }

       tx.commit();

       return model;

   } catch (Exception e) {

       tx.rollback();
       throw e;

   }

}

普通のSQLであればUPDATE一発で済むのに、GAEではこんなにロジックを作らなければなりません。
超面倒ですね!!

GAEの特性を認識せよ


これにより、GAEには個性があるということがお分かり頂けたかと思います。
要点は二点です。

  • GAEで一意制約を実現するのは大変である。
  • GAEはputであって、insert/updateではない。新規登録と更新を分けるのは大変である。

要件定義する時に、エンジニアがこの辺りの事情に配慮して定義しなければいけないのです。

やっぱり、基本的にGAEの一意制約は、本当に本当に必要な場合以外は適応しないというのが大原則と考えて良さそうです。

終わりに

なかなかGAEの個性に悩まされた要件でした。

次はちょっと変わった実装、「メール送信」についてご紹介します。

2015年5月18日月曜日

【GAE】初級実装編3 一意制約(新規登録パターン)

株式会社ジェニシス 技術開発事業部の遠藤 太志郎(Tacy)です。

現在はクラウド基盤「Google App Engine(以下、GAE)」の連載中です。

今回は一意制約について検証してみます。

GAEに一意制約は無い


一意制約とは何か」は、割愛させて頂きます
(読者ならみんな知っているでしょう)

とにかく、結論から言わせて頂ければ、GAEに一意制約はありません。

GAEのBigTableは、要するにJavaの動きで言う所のMapだからなんでしょうね。

Mapは「key」を基準にvalueを登録しますよね?
だから「keyが重複しているか?」は主キー検索一発でチェック出来ますし、重複も物理的にありえません。

しかし、valueの重複が無いかはmap総当たりでチェックするしか無いのです。
GAEも同じような状況にあるのです。

BigTableで一意制約はありません。不可能です。

しかし、BigTableはデータベースですから、本当に厳密な一意性を確保しなければならないシチュエーションも存在します。

そこで活躍するのが、「一意制約専用テーブルを作って主キーを一意制約みたいに使う」というテクニックです。

GAEライブラリであるslim3にはこのテクニックを支援する機能があります。
今回はそれのご紹介です。


Datastore.putUniqueValue


この機能を実現してくれるメソッドは「Datastore.putUniqueValue」、これです。

まずはソースをご覧下さい。

public Key insert(Shohin model) throws Exception {

  /*
   * トランザクション開始
   */
  Transaction tx = Datastore.beginTransaction();

  try {

   Key key = null;

   /*
    * 一意制約専用テーブルに登録する。
    */
   if (Datastore.putUniqueValue(Shohin.UNIQUE_SHOHIN_NAME, model.getShohinName())) {

    /*
     * 一意制約専用テーブルに登録が成功した場合
     */
    key = super.put(model);

   } else {

    /*
     * 一意制約エラー
     */
    throw new BusinessCheckException("対象の商品名は既に登録されています。");

   }

   /*
    * 正常な場合はコミットする。
    */
   tx.commit();

   return key;

  } catch (Exception e) {

   /*
    * エラーになったらロールバックする。
    */
   tx.rollback();
   throw e;

  }

 }

これは「商品名の一意制約チェック」を想定したものです。

いくつかの新登場の情報が出てきていますね。

トランザクション


まず、GAEのトランザクションはコレです。
Transaction tx = Datastore.beginTransaction();

  • Transaction tx = Datastore.beginTransaction();
  • tx.commit();
  • tx.rollback();

読んで字のごとく。説明不要。

疑似一意制約


そして、今回の主題となるのはココです。

if (Datastore.putUniqueValue(Shohin.UNIQUE_SHOHIN_NAME, model.getShohinName())) {

「Datastore.putUniqueValue」

これで「そのキーだけが主キーのテーブル」が作成され、データが投入されます。
ちなみに、Shohin.UNIQUE_SHOHIN_NAMEは単なる固定値。制約のイメージとして一意に定義しています。

/** 商品名一意性約 */
 public static final String UNIQUE_SHOHIN_NAME = "UniqueShohin_ShohinName";


この状況で商品を登録すると、結果はこちら。



「UniqueShohin_ShohinName」というテーブルが作られて、その値は一意制約指定したキー1コだけですね?

こうすることで、主キー制約を一意制約として使い回すことが出来るわけです。


考察

以上のことから分かりますように、「一意制約一個作るのも面倒臭い!!」というのが結論です。
一意制約の数だけテーブル数も増えていってしまいますので、そう気軽に搭載するわけにはいきません。

よって、「何があっても厳密に一意を維持しなければならない!!」という場合を除いて、基本的に一意制約は付与しない方向が良いと思います。

例:メールアドレス

世の中には「メールアドレスをログインIDとして使う」というシステムがあります。
ログインIDが重複しては大変なので、メールアドレスの一意制約は必須。
こういう場合は手間を惜しまず一意制約を搭載しなければなりません。

例:商品名

一方で、この例で出した商品名はどうか?

「同じ商品のレコードが2つ以上あっては業務に混乱を来すから防ぎたい」

という需要があるとしましょう。
しかしですね、商品名なんて重複してたら更新すれば良いのですよ。

この一意制約チェックは「複数ユーザが同時に同じタイミングにレコードを登録した場合」に威力を発揮するもの。

「別々のユーザが同じタイミングで同じ商品名を入れるなんて、まず滅多に考えられない。商品登録前に普通にDB検索チェックするだけで99.999%は防げる。それでも重複した場合は手動修正して貰えばいいや」

という楽観的対処で十分です。

一意制約に厳密性を求めず、ロジックで制御した楽観的一意制約で済ませる。

手を抜ける所は手を抜くのも、GAE開発の一つです。

終わりに

今回は「新規登録」の一意制約をご紹介しました。
しかし、この一意制約はロジックで作り出している擬似的なものですので、「更新」の一意制約はまた違ったロジックを組まなければなりません。

次回は「更新一意制約」についてご紹介します。

2015年4月22日水曜日

【GAE】初級実装編2 主キー検索 その2

株式会社ジェニシス 技術開発事業部の遠藤 太志郎(Tacy)です。

現在はクラウド基盤「Google App Engine(以下、GAE)」の検索機能を検証中です。
今回は主キー検索を行います。

主キー検索


前回の振り返りになりますが、GoogleAppEngineは主キー検索には以下の2パターンがあります。

  • 自動インクリメント
  • 明示的にセット

このうち、自動インクリメント風なタイプでの検索を今回は実行します。

検索実行


では、「商品の主キー検索」を行うコントローラ「GetController」を作成します。
そのうち、商品の検索を行っている部分は以下です。

protected ShohinJet doResponse() throws Exception {

 ShohinDao dao = new ShohinDao();

 try {

  Key key = Datastore.createKey(Shohin.class, asInteger("id"));
  Shohin shohin = dao.get(key);

  logger.finer("取得結果:" + shohin.toString());

  ShohinJet jet = new ShohinJet(shohin);
  jet.setResponseCode(ResponseCode.SUCCESS);

  return jet;

 } catch (EntityNotFoundRuntimeException e) {

  throw new ErrorMessageException(ResponseCode.RECORD_NOT_FOUND, "商品");

 }
 
}

実は既に機能の独自フレームワーク化を行ってしまっておりまして、
私以外の人間には意味が分からないソースが含まれている件につきましては、申し訳ありませんがご了承下さい。

さて、上記ソースのうち、検索を行っている部分を抜き出しますと、以下になります。

ShohinDao dao = new ShohinDao();

Key key = Datastore.createKey(Shohin.class, asInteger("id"));
Shohin shohin = dao.get(key);

こうして見ると、何となくやり方がお分かりになるかと思います。

「キークラス」を作って検索するのですね。

キークラス検索

例えば、普通のデータベースクラスの場合、「id=4」であれば、以下のようなSQLを発行します。

SELECT *
FROM SHOHIN
WHERE ID = 4

GoogleAppEngineの場合はキークラスで検索するのです。
キークラスは以下のように「Datastore.createKey」を使って生成出来ます。

Key key = Datastore.createKey(Shohin.class, asInteger("id"));

これで「商品テーブルの主キー」というクラスが生成出来たわけですね。
簡単です。

様々な主キー検索


基本的には上記の検索で要件は足りますが、どうやらSlim3&GAEには色々と便利機能も備わっているようです。
Eclipeでメソッドを調べてみると以下が出てきました。


これらの機能も見ていこうかと思います。

get(List<key> keys)


キーをListで複数渡せるようです。 要するに内部でgetを連発しているのでしょうね。
さて、普通のシステム開発の場合、こういう風にSQLを何度も発行すると負荷が高くなってしまってお薦め出来ません。 潔く範囲検索SQLを発行するのが正解でしょう。
しかし、GAEは大量不可に強いシステムですので、多少こんな風に連発処理してしまっても問題にはなりません。 余程件数が多いでもない限り、ありがたく便利機能を使わせて頂きましょう。

getOrNull(Key key)

エンティティを取得するか、nullを返すという意味です。
最初にご紹介した普通の「get」で検索結果が取得出来ない場合、そのままだとnullが返ってくるのではなくて「EntityNotFoundRuntimeException」が発生します。
それを回避してくれるのがgetOrNullなわけです。

  • get:基本的に取得出来ると想定する場合の主キー検索
  • getOrNull:検索出来ない場合を許容する場合の主キー検索

こういう使い分けになるのです。

getAsync(Key key)


Async。非同期検索機能も備わっているようです。
これはJava本体のマルチスレッドの機能ですね。

処理速度のチューニングを突き詰める為の機能です。

例えば、以下みたいな感じに使います。

/*
* DBの検索開始
* バックエンドで検索するが、この時点で取得が完了するわけではない。
*/
Future<shohin> shohinFuture = dao.getAsync(key);

logger.finer("先にエンティティをしない処理を進めておく。");
logger.finer("先にエンティティをしない処理を進めておく。");
logger.finer("先にエンティティをしない処理を進めておく。");

/*
* エンティティの取得。
* この時点でエンティティを取得出来る。
*/
Shohin shohin = shohinFuture.get();

/*
* エンティティを使った処理を行う
*/
ShohinJet jet = new ShohinJet(shohin);

普通の「get」の場合、取得に処理時間が必要になります。(まあ、一瞬ではありますが)
普通のgetでは取得完了までその場で待機しなければなりませんが、「getAsync」であれば待機せずに先に進みます。

そしてエンティティを使用しなくても良い部分の処理を先に実行しておいて、
後から「get();」により取得する。

これによりDB検索の待ち時間を短縮出来るわけです。

まあ、GAEは元々が高速なので1件だけのgetだとやる価値が無いかもしれませんが、大量処理を行う場合は、こういう機能を使ってチューニングしていきたいものです。

終わりに


引き続きGAEについてご紹介します。

2015年1月26日月曜日

【GAE】初級実装編2 主キー検索 その1

株式会社ジェニシス 技術開発事業部の遠藤 太志郎(Tacy)です。

今週からはクラウド基盤「Google App Engine(以下、GAE)」の検索機能についてご紹介したいと思います。
今回は基本中の基本、主キー検索です。

主キーなんてあったっけ?


さて、いきなりですが、ここでGAEの個性が出て来ます。


「いつ主キーなんて定義したんだ?」



これですね。
普通のDBでの開発の場合、先にDB設計で主キーを定義して、Javaではそれを主キーであると認識の元で使うというやり方です。
つまり、「Javaから見たら主キーかどうかなんて分かりゃしないが、プログラマーが主キーとして使うように頑張る」、これが基本です。

(まあ、HibernateとかiBatisとかDBマッピングライブラリを使いますと、Javaの実装上で主キーであることが明示的になるよう制約を入れてくれたりするわけで、普通はそういうライブラリを使う物ですけどね。)

ともかく、私が言いたいのは、「DB設計なんか無いGAEで主キーの定義はいつやるんだ?」ということです。

それは、これ。
Javaのモデル中にベタ書きするんです。

/** キー */
 @Attribute(primaryKey = true)
 private Key key;

「@Attribute(primaryKey = true)」というアノテーションがありますね。
これが主キーであることを指し示します。

そしてその下、「Key」クラス。これが主キー型です。


StringやIntegerを主キーとして使うのでは無く、Keyという主キー型が存在する。


これがGAEの特徴です。


どうやってパラメータをセットするの?


次の疑問です。

「主キーには商品IDをセットしたいんだけど、Key型って何よ?」

そうです。
主キーが「商品ID」なら、普通なら主キーは「String shohinId」でしょう?
でも、GAEの場合は「Key key」ですので、そう単純ではありません。

GAEの場合、主キーは2種類のやり方があるようです。


  • 自動インクリメント
  • 明示的にセット

この2つです。
そして「自動インクリメント」の方がGAEの標準です。
今回は初級編ですし、後者「明示的にセット」の方は後回しとし、自動インクリメントの方から調査していきます。

自動インクリメント

システム開発をやっている過程で、「主キーは数字。1から順にカウントアップする」という設計を見たことはありませんか?
そう、自動インクリメント主キーですね。

その場合、主キーとなるその数字は「ID」というカラム名が振られていることが多いです。


  • ID:自動シーケンス番号。1から順にカウントアップする。
  • shohinId:主キーではなく一意キー。


こういう作りになっているケースです。

はい?

「shohinIdで一意なんだから主キーとして使えよ」って?

ごもっともです。
DB設計としてはそれが一番合理的。
何でわざわざ商品ID以外に別の主キーを用意せねばならんのやら……。

これには実装側の都合があるのですよ。

DB専門のDBスペシャリストがDB設計すると、必ず「主キーはshohinId」になりますよ、絶対。
でも、実装まで考慮するJavaプログラマーがDB設計をやった場合、必ずしもそうはなるとは限りません。


何故なら、主キーってのは「複数個のカラムで一個の主キー」というケースもありますから。

その場合、Javaのソースはこうなります。

Entity entity = dao.get(主キー1,主キー2,主キー3,主キー4,主キー5);

主キー多過ぎでソースがグダグダ。

でも、自動インクリメントのIDを主キーにしたら?

Entity entity = dao.get(id);

スッキリしましたね!!
そう、IDで一発なんですよ。
「テーブルの主キーは全部ID」「主キー検索=ID検索」という制約の元でDB設計することにより、実装効率が高まるのです。

なので、私はこのID使用のDB設計を好みます。
今回の連載とは関係ありませんが、あの有名な「Ruby On Rails」もこのID使用のDB設計を前提とするものです。
プログラマーとしては一考の価値のある思想です。

ともかく、GAEにもID思想は存在しまして、それがGAEの基本です。

自動インクリメントですかあ、特に明示的にセットする必要はありません。
過去にご紹介した「DB登録編」でも、特に主キーについてはアピールが無かったのもその為。

DBに保存した時に、勝手に主キーは入っている

登録時は主キーを気にする必要は無いのです。

ログを見てみましょう。



toStringすることにより「key=Shohin(57)」と、バッチリ主キーのシーケンス番号が入っていることが分かりますね。

この数字のパラメータ名はID、型はlong型です。
以下のやり方で取得出来ます。

long id = shohin.getKey().getId();

検索の時は、このIDで検索してあげれば良いんです。
分かれば簡単ですね。


終わりに

今回で「主キーとは何ぞや」ということが分かりました。

次回は実際にこれを使って検索します

2015年1月15日木曜日

【GAE】初級実装編2 第二ラウンド開始

株式会社ジェニシス 技術開発事業部の遠藤 太志郎(Tacy)です。

さて、先週まで画面レンダリングツール「JsRender」について連載していましたが、
今週からは再びクラウド基盤「Google App Engine(以下、GAE)」の連載に復活しようと思います。

今までのおさらい

GAE連載の最後は以下でした。


GAEのデータベースである「BigTable」にデータを叩き混む所までは出来ているんですね。
しかし、データは入れただけでは何の意味も無く、出力出来なければなりません。

出力は以下の二段構えになります。

  • 検索する。
  • 画面に出す。

このうち、「画面に出す」はGAEの機能ではなく、上述の「JsRender」によりフルAjaxにより実現します。
「JsRender」の連載は「検索→表示」の記事を書く為の前フリだったわけです。

JsRendeの記事は以下にまとめていますので、未読の方はご一読下さい。


第二ラウンド開始

そんなわけでいよいよ本命のGAEの記事に戻るわけですが、かなり期間が空いてしまったので一旦仕切り直し。
ここからは第二ラウンド「初級実装編2」として連載を進めて行こうと思います。

この第二ラウンドで、無事にレコードの「登録⇒検索⇒表示」の一連の流れは完成して、初級実装編は卒業ということになります。

しかしながら、私が思うに、検索の方は登録よりも難しいです……。
以下にサラッと触りを記載します。

登録は簡単

以前の記事の転載になりますが、GAEというのは、「登録・更新」は簡単だと思いますよ。

ShohinDao dao = new ShohinDao();
dao.put(shohin);

これだけですから。

Shohin shohin = new Shohin();
Map map = new HashMap();
map.put("ID_0001",shohin);

これとソックリ。
Mapにデータを叩き込むだけ。

まあ、一意制約チェックとかを実現するには少々ステップを伴うこともありますが、
基本的にはこれだけです。

普通のDBであれば、「大量データを一括登録する為にバッチインサート文を作って……」とか「SELECT INSERTで一括して登録を……」みたいな感じに、
大量データ登録を高速化する為に色々と頭を捻ったりすることも無く、
ただボコボコとJavaのクラスそのまま叩き込んでいくだけのパワー勝負です。

しかし、検索は違います。
検索は非常にテクニカルでエンジニアの力量が試されます。

検索の制約

GAEの検索は制約が非常に多いです。

リレーショナルデータベースではない。

「データベースとはリレーショナルデータベースのことだ!!」くらいの勢いで考えているエンジニアは非常に多いと思いますが、
OracleやPostgreSqlみたいなリレーショナルデータベースではありませんので、その常識の根底が根こそぎ崩れます。

結合出来ない。

「SELECT * FROM table1,table2 WHERE TABLE1.id = TABLE2.id」みたいなselect文での結合はSQLの常識ですが、
リレーショナルデータベースではないGAEはsqlもありませんので、こういう結合は出来ません。

常に「table1ならtabe1だけ」という単表検索です。

早い話がJavaのMapみたいなものなんです。
Mapにmap同士を結合する機能なんかありませんよね?

結合させたい場合は、キーによる一意検索を乱射してロジックで結合させるのです。

インデックス検索

ここが重要。

普通のリレーショナルDBでは「インデックスを利用した検索」というテクニックがあります。
これを理解しているエンジニアとしていないエンジニアでは、SQLの検索速度が段違い!!
軽く数千倍の差が出ることも珍しくありません。

GAEの場合はこれに特化していまして、インデックス検索しか無いのです。

前方一致検索は出来ても後方一致検索は出来ない

「where table1.name lile '%tacy%'」みたいに前後方一致検索は普通のSQLでもありますが、
この場合、インデックスが効いていません。
よって検索は遅いです。

「where table1.name lile 'tacy%'」と、前方一致検索ならばインデックスが効きますので高速です。

これと同じことがGAEでも発生しまして、後者の前方一致検索であればGAEならではの高速検索が作動します。
前者の前後方一致検索は実行不能ですので、全件取得してJavaでフィルタリングする、という荒業を行うハメになります。

大量件数では実現出来ませんね。
このように、インデックスで実現出来ない検索は出来ないというのがGAEの基本です。

count(*)が無い

これが驚きなのですが、count(*)は無いのです!!
単純に検索条件で全部取得して、Javaで「++count」みたいに一件一件数えるしか無いです。

GAEの制約の中でもこれは結構ダメージが大きいかと。
「画面上に総件数を出す」みたいな要件への対応は難しいかと。

ORも無い

OR条件が出来ません。2回検索してJavaでマージする必要があります。




とまあ、ザッと目ぼしい所を挙げるだけでもGAEの検索にはこんなに沢山の制約があります。

これを乗り越える為には、

  • そもそも出来ないことを要件に入れないようにする。
  • テーブル設計を冗長化することにより結合を発生させない。
  • 検索キー専用のカラムを容易する。例えば後方一致検索を行う為に文字列を前後反転させて保存しておくなど

などなど、技術と言うよりサーカスと言った方が良いんじゃないかというような技を駆使することになります。

これを聞いただけでゲンナリする人もいらっしゃるかもしれませんが、いえいえ、諦めてはいけません。
Googleの誇るGoogle検索や、Google+や、このブログ、GMailなど、これらは全部そういう課題を乗り越えて作っているのですから。
頑張ればちゃんと出来るはずなのです。

気合入れて行きましょう!!

終わりに

というわけで、初級実装編2は、実質的には検索編となるでしょう。

来週から実際に色々と要件を決めてサンプルを実装していこうと思います。

2015年1月8日木曜日

【JsRender】GAEとの連携

あけましておめでとう。
株式会社ジェニシス 技術開発事業部の遠藤 太志郎(Tacy)です。

本年もよろしくお願いします。

さて、ここしばらくはJavaScriptによる画面レンダリングツール「JsRender」について連載していましたが、
それも今回で最終回を迎えます。

最終回はGAEとの連携です。

思い出す

過去の記事を掘り返してみたら、GAEの記事を最後に書いたのは2014年9月でした。
もう4ヶ月も経ってしまったのですね……。

そして、そのGAEでの連載で私が何度も主張していたこと。それは。

GAEはjspを使わず、フルAjaxで書いてこそ真価を発揮する。

これでした。
そして、そのGAEの連載を中断して始めたのが、本連載である画面レンダリングツール「JsRender」のご紹介……。

もうお分かりですね。

この一連の連載は、JsRenderをGAEで使用する為の前振りだったのです。

ようやく本懐を遂げること出来ました。
では、さっそくGAEに適用してみましょう。

JSON発行

GAEからJSONを発行する方法は過去の記事で紹介しておりましたね。



昔の記事なので改めて書きますが、
GAEでは「GSON」というライブラリを使ってJavaのオブジェクトをJSON文字列化するやり方をオススメします。

Gson gson = new Gson();
String json = gson.toJson(jet);

その結果、以下のようなJSON文字列が出力されます。

{"shohinList":[{"key":{"kind":"Shohin","id":57},"seqNo":1,"shohinId":"57","shohinName":"%E5%95%86%E5%93%813","stock":345,"deliveryDate":"2015/01/10","createdAt":"2015/01/08 21:45:28","updatedAt":"2015/01/08 21:45:28"},{"key":{"kind":"Shohin","id":56},"seqNo":2,"shohinId":"56","shohinName":"%E5%95%86%E5%93%81%EF%BC%92","stock":234,"deliveryDate":"2015/01/09","createdAt":"2015/01/08 21:45:15","updatedAt":"2015/01/08 21:45:15"},{"key":{"kind":"Shohin","id":55},"seqNo":3,"shohinId":"55","shohinName":"%E5%95%86%E5%93%81%EF%BC%91","stock":123,"deliveryDate":"2015/01/08","createdAt":"2015/01/08 21:44:48","updatedAt":"2015/01/08 21:44:48"}],"responseCode":0}

ゴジャゴジャしていて分かりませんが……。
実は「日本語はURLエンコードしないと文字化けして送受信出来ない」とか色々問題があるのですが、その辺については後日の連載で。

ともかく、この文字列を以下のやり方でレスポンスに乗せてあげれば、Ajaxでレスポンスを取得することが出来ます。
response.getWriter().write(json);

画面出力

文字列さえ出力出来てしまえばこっちのものです。

標準的なJQueryの非同期通信で文字列を取得し、そのままJsRenderに流し込んでしまいます。

function doSearch() {

 $.ajax({
  url : $("#form").attr('action'),
  type : $("#form").attr('method'),
  data : $("#form").serialize(),
  dataType : 'json',
  success : function(json) {

   var items = json.shohinList;

   var length = items.length;
   for (i = 0; i < length; i++) {
    items[i].shohinName = decodeURI(items[i].shohinName);
   }

   $("#shohinList").html($("#shohinListTemplate").render(json));
  }
 });
}
JsRenderのテンプレートはこちら。
<script id="shohinListTemplate" type="text/x-jsrender">

<table class="shohinList">
 <tr>
  <th class="no">NO</th>
  <th class="shohinId">商品ID</th>
  <th class="shohinName">商品名</th>
  <th class="action"></th>
 </tr>
 {{for shohinList}}
 <tr>
  <td class="no">{{>seqNo}}</td>
  <td class="shohinId">{{>shohinId}}</td>
  <td class="shohinName">{{>shohinName}}</td>
  <td class="action">詳細 編集</td>
 </tr>
 {{/for}}
</table>

</script>

>/script<

結果は、ほら。
普通のjspと比較しても遜色無い機能的な検索結果一覧が出力出来ました。


これでGAEの画面出力も安心ですね。

終わりに

ようやくGAEに戻って参りました。

画面出力機能の骨子が出来上がったことですし、
次回からは再びGAEに復帰。

GAEのDBであるBigTableの『検索』についてご紹介して行きたいと思います。

2014年9月10日水曜日

【GAE】初級実装編 インサート実行3 時間差反映

株式会社ジェニシス 技術開発事業部の遠藤 太志郎(Tacy)です。

只今、クラウド基盤「Google App Engine(以下、GAE)」の連載しています。

ここ数回はDBへのインサートについてご紹介していますが、今回がその最後です。

登録成功!! しかし……

前回までの処理で、無事にレコードをDBに登録する「put」が出来るようになりました。

では、さっそく、登録したレコードを取得する「get」をやってみましょう。

ShohinDao dao = new ShohinDao();
Shohin shohin = dao.get(key);

あれ?取れないな。
おかしいぞ。
もう一回!
よいしょ!!
よいしょ!!
お、取れた。

変ですね。
目の錯覚でしょうか?

違うんですよ。
GAEは遅延するんです!!

分散処理の宿命

そう、普通のDBであれば、テーブルに書き込んでコミットした瞬間に処理が完了しますから、遅延なんて起きません。

しかし、GAEは分散処理システムですからそうは行かないのです。
put処理が完了した後でも、その向こう側では非同期で色々と処理が走っており、それら全てが完了した時、初めてgetでレコードが取得出来るようになります。


この現象、ツイッターなんかやっていらっしゃる方だとご理解頂けると思います。

「ツイートするぜ、ポチッ!」とやっても、送信完了の瞬間に自分のツイートが反映されてませんよね?
でも数秒すると出てくるようになる。
アレが正にこの現象です。

即時性を犠牲する代わりに、可用性や耐久性などを優先しているのです。


こういうのを「結果整合性(eventual consistency)」と言います。


処理が多少遅延したとしても、最終的にはDBへの登録はもちろん、レプリケーションへの反映まで一通り一貫性を保って完了すればOKという考え方です。

「最終的にはちゃんとやっておくから、過程についてゴチャゴチャ言うな」と言わんばかりのこの発想。
日本人では出て来ない発想かもしれません。。。

どうすりゃいいの?

さて、問題はこの遅延とやらの所要時間ですね。

遅延するのは分かったけど、どれくらい遅延するのか?

ネットで調べた所、どうも「長くて1秒くらい」という声が見受けられます。
私も実際何度か実験してみましたが、同感ですね。大体こんなもんかと思います。

しかし、更にネットを調べてみますと「反映まで半日かかった」なんて声も見つかりました。

半日の遅延は流石に看過できませんが、要はこういう時は障害が起きているのです。
流石GAEということか、障害が起きても完全ダウンするわけではなく、大幅遅延でも縮退運転で耐える堅牢性を誇ります。
(半日も遅延させるんだったら、いっそのことダウンさせてくれた方がマシな気もしますが)

何が言いたいかと言いますと、結局、何秒遅延するかは分からないということです。

平常運転なら1秒未満で反映されますが、少々混み合っていた場合は数秒くらい要することだってあります。

つまり、『登録⇒即表示』を厳密にやらなきゃいけない仕様はNGと言う事です。

例えば、


  • 登録完了⇒即座に登録結果確認画面へ自動遷移。


なんて仕様は実現不可能です。
また、

  • 商品発注完了⇒確認画面をチェックしたけど何も出てない。⇒もう一回⇒二重発注。

なんて、運用的に確認が必須な業務の場合もキツいものがありますね。
こういう場合は、

「商品を発注しました。発注番号はXXXXXXです。発注履歴の確認は商品発注確認画面をご覧下さい。
システムが混み合っている場合は反映が遅れる場合があります。予めご了承下さい」

みたいに注意書きで逃れる、とかですかね。

「発注しましたッ!!」「発注番号はこれッ!!」という感じに発注完了メッセージにインパクトを持たせて、
「あれ~? 今、俺ってちゃんと操作したっけ? もう一回やってみよう」みたいな不安感を利用者に与えない画面設計が重要になります。

逆に、「二重送信したら後で消せばいいだろ」みたいな水準で十分な要件である場合は知らんぷりを決め込むのもアリ。

インターフェース設計のセンスが問われる所になりますね。

終わりに

これでDBへの登録関連は完了です。

次はDBから取り出した値を画面に表示するわけですが、GAEではjspを使わないのがお約束。

フルAjaxで勝負します。

次回からはAjaxライブラリを使った描画処理についてご紹介です。

2014年9月3日水曜日

【GAE】初級実装編 インサート実行2 一意制約

株式会社ジェニシス 技術開発事業部の遠藤 太志郎(Tacy)です。

只今、クラウド基盤「Google App Engine(以下、GAE)」の連載しています。

さて、前回ではGAEの最大の特徴である「BigTable」へのデータ登録をご紹介しました。

今回はそのデータ登録についての、ちょっと込み入った話、「一意制約」についてご紹介します。

GAEは主キーだけ

Oracle、postgreSQL、MySQLなどメジャー所のデータベースには、大概はDB独自の「制約」を設ける機能があります。


  • 主キー制約
  • 一意制約
  • 外部キー制約
  • null禁止制約


この辺りがよく使う制約だと思いますが、しかし、GAEには一意制約しか存在しないのです!!

何と不便な!!

要するに、GAEみたいなクラウド基盤は分散処理かつ大量データを取り扱っている都合上、負荷軽減とか仕様の問題で細かいチェックを行うことが出来ないのです。
このうち、外部キー制約とnull禁止制約はロジックで頑張れば対処出来ます。

  • 外部キー制約:親テーブルが無くて子テーブルだけある、みたいなことにならないように、トランザクション等も駆使してキッチリJavaのロジックを組む。
  • null禁止制約:登録、更新時にそのカラムがnullにならないようにJavaロジックを組む。

つまるところ、「バグが無いようにきっちり作れッ!!」という話で済むのです。

しかし、「一意制約」ばかりはそうはいかない。
もちろん、DB上でカラムが重複してはいけないようにJavaのチェック機能を入れるのはアリですれども、
Webシステムでの運用という都合上、「同タイミングの二重アクセス」という宿命からは逃れられません。

一意制約だけは、どれだけキッチリJavaロジックを組んでも担保出来ないのです。

しかしながら、GAE専用ライブラリであるSlim3には、主キー制約を上手く使って一意制約を実現してくれる機能があります。
今回はそれをご紹介します。

実装方法

さて、上記のようにGAEには一意制約が無く、主キー制約しかありません。
そこで、Slim3では「一意制約チェック専用のテーブルを作って主キーチェックを利用する」という機能が備わっています。

メソッドは「Datastore.putUniqueValue」。

例えば、ソース的には以下みたいな漢字に組めば良いというわけです。

public Key insert(Shohin model) throws Exception {

  Transaction tx = Datastore.beginTransaction();

  try {

   tx = Datastore.beginTransaction();

   Key key = null;

   if (Datastore.putUniqueValue(Shohin.UNIQUE_SHOHIN_NAME, model.getShohinName())) {
    key = super.put(model);
   } else {
    throw new BusinessCheckException("対象の商品名は既に登録されています。");
   }

   /*
    * 商品IDのあいまい検索用にIDを文字列化して再保存する。
    */
   model.setShohinId(Long.toString(model.getKey().getId()));
   put(model);
   tx.rollback();

   return key;

  } catch (Exception e) {

   tx.rollback();
   throw e;

  }

 }

こうすると、ほら。
下の図みたいに、一意キーチェックを行う為だけのテーブルが作成されます。
こうやって二つのテーブルを駆使することで一意制約を実現するわけですね。



登録する時が、「Datastore.putUniqueValue」。
削除する時が、「Datastore.deleteUniqueValue」。


つまり、


  • 新規登録時は、putするだけ。
  • 更新時は、先にputして後でdelete。
  • 削除時は、deleteするだけ。


こんな漢字にトランザクションを活用しつつロジックを組むことで、一意制約が実現出来るわけですね。
流石はSlim3。
良い機能が備わっています。

注意点

しかしですね、上記の通り、一意制約の度に新しいテーブルが作られてしまいますので、リソース的にも処理速度的にも、一意制約を多用するのは避けた方が良いと思います。

上記は例として「同じ商品名は登録出来ない」という仕様にしましたけれども、ケースによっては「普通のJavaチェックだけで十分。万が一被った場合は消せば良い」程度で済んでしまう程度の重要度であることも十分考えられますよね。
同時アクセスなんて滅多に無いですから、Javaチェックだけで99.9%はブロック出来ますもの。

そういう場合は一意制約はあえて入れないで、万が一問題が起きた時だけ運用回避する、という方針で十分かと思います。

しかし、逆に「絶対に何が何でも、死んでも重複して貰っては困る!!」なんて場合もあるでしょう。
例えば「メールアドレスは一意」などがそうです。
ログインIDの代わりにメールアドレスを使っているサイトなのに、メールアドレスが重複して入ってしまっていたら、これは大問題ですよね!!
万が一にもこんなバグは許されません。

このように、本気の本気で一意制約が必要な重用機能だけチェックを入れて、それ以外はJavaによるチェックのみで楽観的に済ませる。
これくらいの方針が良いでしょう。

終わりに

流石、Slim3は便利な機能が揃っています。事実上の標準フレームワークたるシェアを占めているだけのことはあります。

次回も引き続き、DBへのインサート処理の特記事項をご紹介します。

2014年8月20日水曜日

【GAE】初級実装編 インサート実行1

株式会社ジェニシス 技術開発事業部の遠藤 太志郎(Tacy)です。

只今、クラウド基盤「Google App Engine(以下、GAE)」の連載しています。

今回は前回に引き続き、GAEにおける「インサート」の処理についてご紹介です。

put

ではまず、何も考えずにスポッと値を入れるだけの処理を記載します。

前回の段階で「モデル」と「DAO」は作成済みですので、これを使ってレコードの登録を行います。
そのソースが以下です。

 Shohin shohin = new Shohin();
  ShohinDao dao = new ShohinDao();
  dao.put(shohin);

これだけです。
put一発で終わり。空っぽですが、1レコード増えています。
実に簡単ですね。

とはいえ、余りにシンプル過ぎるが故に、「これってどういう風にデータの辻褄を合わせているんだろう?」という疑問が沸いて当然。
この為、今回はその辺りについての疑問解決がメインテーマになります。

登録更新の区別が無い

まず第一の特徴は、GAEにおけるデータ保存のやり方は「insert」ではなく「put」なんです。
put処理というのは、レコードが存在していたら上書き、無ければ新規保存です。

SQLと違って、「新規登録」と「更新」の区別が無いのです。

これはJavaのMapと全く同じです。

 Shohin shohin = new Shohin();
  Map map = new HashMap();
  map.put("ID_0001",shohin);

これが大量データ処理の秘密の一つなのです。
要はDBがハッシュで管理されているから、レコード数がどれだけ大量に増えても均一の速度を保てているというわけなのです。
(ハッシュを使うと検索速度がレコード数に依存しなくなるという件については、基本情報処理試験等でご理解下さい)

なので、GAEで「新規登録」と「更新」を実現したい場合は、前もって検索して、

  • GigTable上に予定レコードが存在していなかった場合、単純にput。
  • GigTable上に予定レコードが存在していた場合、まずテーブル上の値を丸ごと取得し、更新したいパラメータだけを差し替えてput。

こういうロジックを組んで対応する必要があります。

ここで一つ、GAEの特徴が垣間見えてきましたね。そう、GAEは基本的にロジック対応で乗り切るモノです。

例えばですね、「商品の値段が100円になっているものを、全部150円に一括で値上げしたい」とかいう要求があるとするじゃないですか。

その場合、SQLだったら「update shohin set price = 150 where price = 100;」みたいな感じに一発で全レコード更新すればOKです。
しかし、GAEの場合は「更新」なんか存在しませんので、まずは「値段が100円である商品」で検索を行い、それから1レコードずつJavaでpriceの値を150に書き換えてputするのです。

GAEは1レコード単位でしか登録、更新、削除出来ない!!

要望は殆ど全部Javaで頑張ってロジックを組んで何とかするのです!!



ちなみに、上記の通り、GAE大量レコードの更新をする場合でも1レコードずつの逐次処理になってしまいますので、「一括更新、一括削除」が主力になるシステムには向かないということです。
こういった検索機能についての向き不向きは追々検証していきたいと思いますので、今回の所は触り程度にご認識頂ければと思います。

主キー

次に疑問に思うのは、「主キー」です。

上記にある「無ければ登録、あれば上書き」という挙動は「主キー検索して、ある/無い」という話です。
その「主キー」とはどこにあるかと言いますと、商品モデルの中の「Key」というフィールドです。

 /** キー */
 @Attribute(primaryKey = true)
 private Key key;

このKeyはちょっと独特な動きをしまして、「自動モード」と「手動モード」があります。

自動モード

まず、上記でご紹介したように「何もせずにput」を行うと、勝手に上記「key」にシーケンス番号がセットされます。
PostgreSql等で「serial」という型を主キーに定義しておくと、空で保存した時に勝手にシーケンス番号で値が入っていきますが、あれとそっくりな動きです。


この「主キーがシリアル」というのは、ちょっとしたDB設計のポリシーが入ってくる部分ですので、ちょっと込み入った話を。


例えば、商品を管理するようにあシステムの場合、「商品毎に商品IDを明示的に決めて、それを主キーにする」という設計になることが多いと思います。
「商品IDで一意」と決まっている以上は、それを主キーにするのが最も合理的ですよね?

しかし、昨今のシステム業界を見ますと、「商品IDは一意キーとして別途制約を入れて、主キーはシリアルにする」という思想が出て来ています。
Ruby On Railsはこの思想です。

このやり方の場合、DB的には冗長になりますが、「プログラミングし易い」「ソース粒度が均質化する」「マッピングし易い」などの利点があるのです。

私もこのやり方に賛同する所がありまして、多少ファイルスペースを無駄遣いしてもシリアルを使わせて貰っちゃったりするのが最近のブームです。

GAEもこれに近い思想で構築されておりまして、「主キーはシリアル」がGAEの標準です。

特に拘りが無ければ、この標準の自動シリアルモードで作って行くのが良いのではないでしょうか?

手動モード

一方で、「主キーは商品IDを明示的にセットする」みたいな手動モードが必要になる場合もあります。

その場合は、「KeyFactory.createKey」というツールでキーを生成しなければなりません。

 Shohin shohin = new Shohin();
  Key key = KeyFactory.createKey("Shohin", "49033011234567");
  shohin.setKey(key);
  ShohinDao dao = new ShohinDao();
  dao.put(shohin);

「KeyFactory.createKey("Shohin", "49033011234567");」のうち、「Shohin」がテーブル名を意味して、「49033011234567」が商品IDを意味します。

こんな感じで任意パラメータを主キーにすることが出来るのです。

ただ、やっぱ作りとしては汚くなるという印象がありますね。
やはり基本は自動モードで実装した方が合理的で、もし一意に保ちたいカラムがある場合は、別途「一意制約」を設ける方がシステムとして整合性が取れると思います。

続く

しかしですね、上記に「一意キー」とありますが、実は、GAEに一意キーは無いのです。

GAEのDB制約は「主キー制約」しかありませんので、「主キー制約を上手く使って一意制約を入れる」というテクニックが必要になります。

次回はインサート実行後編、「一意制約の作り方」をご紹介します。

2014年8月17日日曜日

【GAE】初級実装編 Slim3モデルクラス作成

株式会社ジェニシス 技術開発事業部の遠藤 太志郎(Tacy)です。

只今、クラウド基盤「Google App Engine(以下、GAE)」の連載しています。

では、今回よりGAEの目玉である「BigTable」の使い方をご紹介してきます。

ただし、GAEには標準でBigTableを利用する手段が用意されていますが、今回のサンプルアプリではSlim3を導入していますので、Slim3経由でBigTableを使用する方法として、ご紹介致します。

テーブル作成

さて、まずSlim3では「データモデル型」にてデータをやりとりし、それがそのままDBの定義になります。

普通のJavaシステムの場合、まず最初にDBのデータモデル定義があり、後でそれとピッタリに合わせたJavaモデルクラスを作成しますよね?

まあ、先にJavaクラスを作って、後でDBをリバース作成するライブラリもありますが、とにかく普通のシステムの場合は、
テーブルを作る時はJavaのモデル作成とは別途「create table」を発行しています。

Slim3の場合は違いまして、「Javaクラスがそのまま勝手にテーブルになる」という構成です。

Testという名前のクラスを作って保存処理を実行したら、自動的にTestというテーブルが出来て保存されるというカラクリです。

もちろん、テキトーにクラスを作れば良いという話ではなく、ちゃんと指定されたインターフェースを実装したり、アノテーションを宣言したりが必要となりますが、
その辺りはSlim3の自動機能にお任せです。

ビルド

では、さっそく作ってみましょう。

Slim3でプロジェクトを作成すると、以下のような構成になっているはずです。


一番下に「build.xml」がありますよね?
コイツを右クリックから実行します。

すると、以下のような画面が出て来ます。


親切にも色々と作ってくれる機能が揃っておりますが、今回の所は「gen-model-with-dao」を選択します。

MODELクラスとDAOクラスを1:1のセットで作成してくれる機能です。

その次にクラス名を指定する画面が出ますので、Shohinとでもセットします。



これにより、以下のようなShohinモデルのクラスが出来上がりました。
簡単ですね。

@Model(schemaVersion = 1)
public class Shohin implements Serializable {

 private static final long serialVersionUID = 1L;

 @Attribute(primaryKey = true)
 private Key key;

 @Attribute(version = true)
 private Long version;

 /**
  * Returns the key.
  * 
  * @return the key
  */
 public Key getKey() {
  return key;
 }

 /**
  * Sets the key.
  * 
  * @param key
  *            the key
  */
 public void setKey(Key key) {
  this.key = key;
 }

 /**
  * Returns the version.
  * 
  * @return the version
  */
 public Long getVersion() {
  return version;
 }

 /**
  * Sets the version.
  * 
  * @param version
  *            the version
  */
 public void setVersion(Long version) {
  this.version = version;
 }

 @Override
 public int hashCode() {
  final int prime = 31;
  int result = 1;
  result = prime * result + ((key == null) ? 0 : key.hashCode());
  return result;
 }

 @Override
 public boolean equals(Object obj) {
  if (this == obj) {
   return true;
  }
  if (obj == null) {
   return false;
  }
  if (getClass() != obj.getClass()) {
   return false;
  }
  Shohin other = (Shohin) obj;
  if (key == null) {
   if (other.key != null) {
    return false;
   }
  } else if (!key.equals(other.key)) {
   return false;
  }
  return true;
 }
}


フィールド設定

次に、モデルクラスにフィールド変数を定義してテーブルカラムを作成します。

と言っても、「変数名が勝手にDBのカラムになる」というだけですので、普通に「private String shohinId;」とか定義していけばOKです。

その結果がこちら。

/**
 * 商品モデル。
 * 
 * @author Tashiro Endo
 * 
 */
@Model(schemaVersion = 1)
public class Shohin implements Serializable {

 /** シリアルバージョン */
 private static final long serialVersionUID = 1L;

 /** キー */
 @Attribute(primaryKey = true)
 private Key key;

 /** バージョン */
 @Attribute(version = true)
 private Long version;

 /** 商品ID(keyのIDの文字列化) */
 private String shohinId;

 /** 商品名 */
 private String shohinName;

 /** 在庫数 */
 private Integer stock;

 /** 出庫可能日 */
 private Date deliveryDate;

 /** 登録日時 */
 @Attribute(listener = CreationDate.class)
 private Date createdAt;

 /** 更新日時 */
 @Attribute(listener = ModificationDate.class)
 private Date updatedAt;

 @Override
 public int hashCode() {
  final int prime = 31;
  int result = 1;
  result = prime * result + ((key == null) ? 0 : key.hashCode());
  return result;
 }

 @Override
 public boolean equals(Object obj) {
  if (this == obj) {
   return true;
  }
  if (obj == null) {
   return false;
  }
  if (getClass() != obj.getClass()) {
   return false;
  }
  Shohin other = (Shohin) obj;
  if (key == null) {
   if (other.key != null) {
    return false;
   }
  } else if (!key.equals(other.key)) {
   return false;
  }
  return true;
 }

 /**
  * キーを取得します。
  * 
  * @return キー
  */
 public Key getKey() {
  return key;
 }

 /**
  * キーを設定します。
  * 
  * @param key キー
  */
 public void setKey(Key key) {
  this.key = key;
 }

 /**
  * バージョンを取得します。
  * 
  * @return バージョン
  */
 public Long getVersion() {
  return version;
 }

 /**
  * バージョンを設定します。
  * 
  * @param version バージョン
  */
 public void setVersion(Long version) {
  this.version = version;
 }

 /**
  * 商品ID(keyのIDの文字列化)を取得します。
  * 
  * @return 商品ID(keyのIDの文字列化)
  */
 public String getShohinId() {
  return shohinId;
 }

 /**
  * 商品ID(keyのIDの文字列化)を設定します。
  * 
  * @param shohinId 商品ID(keyのIDの文字列化)
  */
 public void setShohinId(String shohinId) {
  this.shohinId = shohinId;
 }

 /**
  * @inheritDoc
  */
 @Override
 public String toString() {

  StringBuilder bul = new StringBuilder();

  bul.append("key=").append(key).append(",");
  bul.append("version=").append(version).append(",");
  bul.append("shohinId=").append(shohinId).append(",");
  bul.append("shohinName=").append(shohinName).append(",");
  bul.append("stock=").append(stock).append(",");
  bul.append("deliveryDate=").append(deliveryDate).append(",");
  bul.append("createdAt=").append(createdAt).append(",");
  bul.append("updatedAt=").append(updatedAt);

  return bul.toString();
 }

 /**
  * 商品名を取得します。
  * 
  * @return 商品名
  */
 public String getShohinName() {
  return shohinName;
 }

 /**
  * 商品名を設定します。
  * 
  * @param shohinName 商品名
  */
 public void setShohinName(String shohinName) {
  this.shohinName = shohinName;
 }

 /**
  * 在庫数を取得します。
  * 
  * @return 在庫数
  */
 public Integer getStock() {
  return stock;
 }

 /**
  * 在庫数を設定します。
  * 
  * @param stock 在庫数
  */
 public void setStock(Integer stock) {
  this.stock = stock;
 }

 /**
  * 出庫可能日を取得します。
  * 
  * @return 出庫可能日
  */
 public Date getDeliveryDate() {
  return deliveryDate;
 }

 /**
  * 出庫可能日を設定します。
  * 
  * @param deliveryDate 出庫可能日
  */
 public void setDeliveryDate(Date deliveryDate) {
  this.deliveryDate = deliveryDate;
 }

 /**
  * 登録日時を取得します。
  * 
  * @return 登録日時
  */
 public Date getCreatedAt() {
  return createdAt;
 }

 /**
  * 登録日時を設定します。
  * 
  * @param createdAt 登録日時
  */
 public void setCreatedAt(Date createdAt) {
  this.createdAt = createdAt;
 }

 /**
  * 更新日時を取得します。
  * 
  * @return 更新日時
  */
 public Date getUpdatedAt() {
  return updatedAt;
 }

 /**
  * 更新日時を設定します。
  * 
  * @param updatedAt 更新日時
  */
 public void setUpdatedAt(Date updatedAt) {
  this.updatedAt = updatedAt;
 }
}

単純にフィールド変数を追加して、getter/setterを自動出力すればOKです。
getter/setterは必須ですので、ご注意を。

なお、上記では「String」「Integer」が追加されていますが、これは何でもアリというわけではなく、限られた一部のクラスしかフィールドとして定義してはいけません。


  • 500文字以下の短い文字列の場合は、String
  • 整数なら、Ingeter
  • Ingeterで収まらない大きい数字の場合は、Long
  • 日付の場合は、Date


こんな感じに決まっています。
直感で大体分かると思いますが、公式サイトにちゃんとした一覧がありますので、必読です。


アノテーション「@Attribute」の「CreationDate.class」と「ModificationDate.class」

上のソースをご覧になると気付かれたかと思いますが、モデルクラスには「@Attribute」というアノテーションがフィールドに付与されているものがありまして、
これがモデルクラス特有の役割を持ちます。

「(primaryKey = true)」とか大事な機能につきましては、次回以降について記事にするとしまして、
今回の所は小ネタ機能である「CreationDate.class」と「ModificationDate.class」についてご紹介します。

これは、「登録日と更新日を自動セットする」という機能です。
これについても上記URLに記載があります。

「登録日と更新日」は、まあこれから作るシステム上、特に要件には考慮されていないものですけれども、保存しておいて損は無い情報ですよね?
なので、私は全モデルクラスに、この登録日と更新日は持たせるということでルールを統一して作っています。

この記事ではソースを張る都合でShohinクラスに実装していますが、実際には「AbstractModel」というクラスを自分で作って、
そこに「CreationDate.class」と「ModificationDate.class」を定義しています。

モデルクラスは継承も出来ますので、共通で使うフィールドは継承してOK!!


このように、Slim3は探してみるとチョコチョコ便利な機能が色々揃っていたりしてくれていますので、
公式サイトを見ながらあれこれ実験してみると面白いですね。


終わりに

以上で、通常システムで言う所の「create table」が完了した状態にあります。

次回は、実際にレコードを登録する「insert」に該当する機能の実装を行います。

2014年7月31日木曜日

【GAE】初級実装編 BigTable導入

株式会社ジェニシス 技術開発事業部の遠藤 太志郎(Tacy)です。

只今、クラウド基盤「Google App Engine(以下、GAE)」の連載しています。

前回でシステムへのログインに成功しましたので、今度は主立った機能を作って行きます。

しかし、ここで問題となるのが、BigTable。
GAE実装の上での最大の要所です。

機能概要

さて、とりあえず今から時間をかけて作っていく機能は、以下4機能です。

  • 商品の登録
  • 商品の更新
  • 商品の削除
  • 商品の検索

手始めに、代表的なCRUD機能を作って使い方をマスターしていくという作戦なわけです。

RDBMSではない

GAEで使用するBigTableの特徴、それは「RDBMSではない」と言うことです。

「RDBMS」という用語がピンと来ない方もいらっしゃるかもしれませんので軽くご説明しますと、
要はRDBMSとは普通のDBのことです。

「SELECT * FROM aaa,bbb WHERE aaa.id = bbb.id and ……」みたいにSQLを発行して結果を取得出来るアレです。

Oracle、PostgreSql、MySQL、これら代表的は全部RDBMSです。
実際の所、この業界のDBとは殆どRDBMSですので、「DBとはRDBMSのことだ!!」くらいの勢いで考えていても一生困らない人が大半かと思います。

しかし、GAEで採用しているDB「BigTable」はRDBMSの範疇に入りません。

Bigtableは「列指向データベースマネジメントシステム」というカテゴリに入るDBです。

しかし、RDBMSではありませんので、RDBMSの常識がこちらでは通用せず、カルチャーショックを受けることになります。
細かい話は順次判明次第調査していくとしまして、今回はかじり程度にご紹介したいと思います。

BigTableの長所

BigTableの長所は、「大量データOK」と「超高速」の2つです。
何せ、世界中で使われているGoogleの分散システムを支える技術の中枢ですからね、名実共に世界最強の超高速DBだと思います。

具体的には「レコード件数が増えても検索速度は変わらない」という特徴があります。

普通のRDBMSの場合、100レコード中の10レコードを検索するのと、1億レコード中の10レコードを検索するのではパフォーマンスに差が出て来ます。
もちろん大量レコードの中から検索する方が遅い。
なのでDBプログラマーはインデックスを張ったり、テーブル自体を分割したりと、何とかその検索を高速で行おうと日夜チューニングを必死で行っているわけです。

しかし、GAEはそういう心配がありません。
総レコード数が100だろうが100億だろうが、検索速度は常に一定です。
そして速い。

「大量データ、大量アクセスに無敵の強さを発揮する」と言うのがGAEの強みで、そのDBであるBigTableも同様の性質を有しているのです。

BigTableの短所

一方で短所もあるって言うか、RDBMSに慣れている身としては、「大量データと超高速以外は短所しか無い」くらいの気分ですけどね。

一例を挙げると「joinが無い」があります。
同時に2つ以上のテーブルを検索出来ません。

イメージとしては、エクセル表なのですよ。


まあ、縦列と横列の2次元であるという点ではRDBMSもエクセルも同じですが、RDBMSはSQLがあります。
BigTableの場合はフィルターです。


エクセルのフィルター機能だけで検索するようなイメージ。
それがBigTableです。

なので、RDBMSと違って、


  • 複数のテーブルを結合したり出来ません。
  • 「group byでグルーピングして最大値を取得する」とかも出来ません。
  • 「count(*)」も出来ません。
  • 曖昧検索も制限あり。


「え~ッ1? こんな事も出来ないの?」と言うくらいのカルチャーショックを受けます。

もし上記のような機能を実現したい場合は、まずテーブルのデータを全部ガボッと取得してJavaのロジックで算出するとか、そういう泥臭いやり方になります。
もちろん大量データの場合はガボッと全部取得とか出来ませんから、別途集計テーブルを用意しておいて都度更新していくなど、やり方を一考する必要が出て来ます。



「こんなんじゃ開発やってらんないよ!!」



みたいな気分になる事も多いですが、いやいや、代わりに「安い」ですから。
開発が大変になる代わりに圧倒的低コスト運用を実現出来るという可能性を秘めたインフラなので、何とか開発者の方は頑張ったって下さい。

世界に誇るGoogleのGoogle検索、Google画像検索、Googleマップ、Google+、youtube、このブログ。

あれらは全部BigTableで作っているのです。
我々があれらを全部タダみたいなコストで使わせて貰っているのは、開発者の方が知恵を使って頑張って開発してくれたからなのです。

開発者にはかなりの力量を要求されますが、代わりに成功すれば大きなメリットを会社にもたらしてくれるでしょう。

終わりに

ひとまず、BigTableのご挨拶という程度ではこの辺で終了します。

詳しく書いていくと書籍になってしまいますので、このブログでは開発過程で出て来たポイントを都度ご紹介、という形になるでしょう。

次回からは、一先ず簡単な「レコード1件のcreate」から初めていきたいと思います。

2014年7月21日月曜日

【GAE】初級実装編 シングルサインオン

株式会社ジェニシス 技術開発事業部の遠藤 太志郎(Tacy)です。

只今、クラウド基盤「Google App Engine(以下、GAE)」の連載しています。

今回はシングルサインオンでログイン機能を作っていきます。

画面レイアウト

ログイン画面は以下のレイアウトにしました。


お馴染み「ユーザID」と「パスワード」を入力する欄がありませんね?

はい。
このシステムでは「シングルサインオン」を使ってログイン機能を実装したいと思います。

シングルサインオン

シングルサインオンとは、「一回のログイン認証によって、複数のシステムへ同時にログイン出来る機能」のことです。

シングルサインオンを使用する場合、ログインIDとパスワードは利用先に丸投げすることになるので、
自社でログインIDとパスワードを保持することはありません。

昨今問題になっている「個人情報流出」のリスクも、自社で持たないことで軽減することが可能なのです。


「何も持たないことが長所」


クラウド時代におけるスマートな姿勢の一つですね。

身の回りでよく使用されている例と言えば……、ちょっと「一般的」と言える程普及はしていないかも。

探せばあるんですけどね。
僕がシングルサインオン界で一番幅を利かせていると思うのは、FaceBookでしょうか。
「このシステムはFaceBookのIDでログイン出来ます」というFaceBookのシングルサインオンが最も良く登場するイメージです。

しかし、「常識」と呼べる程は完全普及していないと思います。


私はこの状況は改善されるべきものであると考えています。

処理効率を考えれば、「全人類が共通で一つのアカウントを持っていて、どのシステムでもそれを使ってログイン出来る」という状況がベストです。
世界的な標準化機構が主導して世界統一アカウントを作るくらいの動きがあって然るべきと考えています。

まあ、色々と難しいハードルがあるみたいで、現状、世界統一アカウントと呼べるものは存在しません。

しかし、世界統一アカウントに準ずる存在と言えるものはあると思います。
それが「Googleアカウント」と「AppleID」です。

AndroidスマホとiPhoneを持っている人はみんなこれらのアカウントを持っているわけですから、
現実のシェアを考えると、この2つが世界統一アカウントに最も近いものだと思います。

現在、私が作っているアプリはGAEのアプリですので、ここは「Googleアカウント」に便乗させて頂こうと思います。

「俺のシステムを使いたいならGoogleアカウントを持って来い!!」というスタイルです。

この辺りはアプリの性質を鑑みての判断になるでしょうね。

「いや、このアプリはGoogleIDを持っていない人も使うものだし……」みたいな懸念があるなら普通にログインIDとパスワードをDBに保有すれば良いです。
(代わりに流出リスクという業を背負うことになりますが)

ただ、GAEはGoogleアカウントを使うことで解禁される機能が存在しますので、
GAEシステムの利用はGoogleアカウント保有者に限定し、持っていない人には新規作成するようガイダンスする、という方向で努力した方が合理的だと思います。

開発

では、自分のGAEアプリの中でGAEにログインする方法を解説します。

まずは現状ログインチェックです。

protected void loginCheck() throws ValidationCheckException {

 UserService userService = UserServiceFactory.getUserService();
 User user = userService.getCurrentUser();

 if (user == null) {
  logger.fine("このユーザはログイン認証を行っていません。");
  throw new ValidationCheckException(ResponseCode.NOT_LOGIN);
 }

};

「UserService」クラスの「getCurrentUser()」の結果がnullだったら未ログイン。
nullでなかったらログイン済みです。

そして、nullだった場合は、Googleのログイン画面に飛ばします。

そこから先はGoogleユーザならお馴染みのログイン画面です。
流石に顔を恥ずかしいのでマスクした画像を載せますが、別に正体を隠さねばならない理由はありません。(;^_^)



ここがポイントです。

自分でログイン画面を作るのではなく、Google画面にワープさせて、ログインさせてから戻ってきて貰う。

これによって、自社でパスワード認証をすることは無いので、自社の責任で流出することは絶対に無いのです。

次に問題となるのは、どうやってGoogle画面にワープさせるか、です。その方法は以下になります。

protected LoginOutUrlJet doResponse() throws Exception {

  LoginOutUrlJet jet = new LoginOutUrlJet();

  UserService userService = UserServiceFactory.getUserService();

  jet.setUrl(userService.createLoginURL("戻ってきて欲しいパス"));

  jet.setResponseCode(ResponseCode.SUCCESS);

  return jet;
 }

「UserService」クラスの「createLoginURL()」メソッドで、Google画面のURLを取得出来ますので、
後はこのURLの先にJavaScript等で強制遷移させれば良いというワケです。

ちなみに、このURLは「ログインさせてから戻ってきて貰う」の機能も内包しているものです。

「userService.createLoginURL("戻ってきて欲しいパス")」

こうすることで、ログイン後に自動的に指定したパスのURLに戻ってきてくれるよう、Google側で制御してくれます。

例えば、ログイン後は常にトップ画面に戻ってきて欲しい場合は、

「jet.setUrl(userService.createLoginURL("/"));」と書けばOKです。

分かり易いですね。

終わりに

今回のシングルサインオンは、開発者に「持たないメリット」を理解する手助けになると思います。

昨今は個人情報保護法など色々とセキュリティ面で厳しい世の中になってきました。
その厳しいセキュリティ水準に耐えられるシステムを作るのは……、ハッキリ言って至難です。

「一応ちゃんとやってるつもりだけど、スーパーハッカーに襲われたら知らんし……」
「っていうか、このシステム、品質ボロボロでセキュリティなんてザルみたいなもんだし……」

こんな有様のシステムも少なく無い現状です。
こういう面倒さ、難しさををGoogleに丸投げ出来るのもGAEのメリットなのです。

引き続き、GAE開発を続けて行きます。

2014年7月10日木曜日

【GAE】初級実装編 疎通確認 後半 ~レスポンス送信2~

株式会社ジェニシス 技術開発事業部の遠藤 太志郎(Tacy)です。

只今、クラウド基盤「Google App Engine(以下、GAE)」の連載しています。

前回でレスポンスパラメータを格納するDTO、「JETクラス」を作成しましたので、
次はそれをJSON変換します。

JSON

JSONは正式名称を「JavaScript Object Notation」と言う、軽量なデータ記述言語です。
JavaScriptという名称が入っていますが、別にJavaScript専用というわけではありません。(変なネーミングですね)

同じデータ記述言語の兄弟分にはXMLがありますが、XMLの方が詳細な情報が定義出来る一方、処理が重くなります。
JSONは余り複雑なデータ表現には不向きですが、軽量という利点があります。

Ajax処理で使用するデータ程度ならJSONで十分対処出来るかと思いますので、基本はJSONを使っていくことになるでしょう。

JSON変換ライブラリ

さて、JavaのBEANクラスをJSON文字列に変換するのは、手動で行う必要はありません。
便利なJSONライブラリがありますので、これを使用しましょう。

  • Jackson
  • JSONIC
  • GSON

ザッと有名所ではこの3つがあります。
世界中でベンチマークが行われており、この中で最も高性能なのはJacksonです。

ですが。

今回はGAEですので、Google製ライブラリであるGSONを使うことにしようと思います。

以下からGSONライブラリをダウンロードして下さい。




しかし、ここで注意事項です。
どうも、最新型であるGSON2系を使用すると、GAE本番環境デプロイ時に「java.lang.VerifyError」が発生するようなのですよ。

どうも不安定らしく、発生したり、しなかったりする困った事象です。

「GAE GSON java.lang.VerifyError」で検索しますと同様の事象が数多く報告されていることが確認出来ますが、解決策は見つかっていないようです。
私も以前にこの問題にぶつかりましたが、その時はGSONのバージョンをGSON1系に下げることで解決出来ました。

この為、私はあえて古いバージョンであるGSON1系を導入することをオススメします。

まあ、VerifyErrorが出る問題につきましても、GAEのバージョンが進んだら発生しなくなるかもしれませんし、
今の所は現場の知恵ということで小耳に挟んでおく程度が良いかと思います。

変換実行

GSONライブラリの導入が終わりましたら、次は実際に実装してみましょう。

JavaのBEANクラスをGSONを使ってJSON文字列に変換するサンプルは、こちら!!


Gson gson = new Gson();
String json = gson.toJson(jet);

これだけ。超簡単なのです!!

そう、私がGSONをオススメするもう一つの理由は、実装が超簡単な点です。

Jacksonだと「Factoryクラス」「マッピングクラス」とか何とか色々ゴニョゴニョしてきますが、GSONは超簡単です。

開発が楽になるという点においても、ここはGSONの採用をオススメしたい所です。

この結果、例としては以下のような文字列が出力されることになります。
これがJSON文字列です。

{"shohinList":[{"key":{"kind":"Shohin","id":3},"seqNo":1,"shohinId":"3","shohinName":"Tacy%E3%83%86%E3%82%B9%E3%83%883","stock":3333,"deliveryDate":"2014/07/04","createdAt":"2014/07/03 14:36:11","updatedAt":"2014/07/03 14:36:12"},{"key":{"kind":"Shohin","id":2},"seqNo":2,"shohinId":"2","shohinName":"Tacy%E3%83%86%E3%82%B9%E3%83%882","stock":1111,"deliveryDate":"2014/07/03","createdAt":"2014/07/03 14:36:01","updatedAt":"2014/07/03 14:36:01"},{"key":{"kind":"Shohin","id":1},"seqNo":3,"shohinId":"1","shohinName":"Tacy%E3%83%86%E3%82%B9%E3%83%88","stock":1234,"deliveryDate":"2014/07/04","createdAt":"2014/07/03 14:35:30","updatedAt":"2014/07/03 14:35:30"}],"responseCode":0}

レスポンス返却

最後に、出力した文字列をHttpServletResponseに乗せて返却すれば一連の処理は完了です。

response.getWriter().write(json);

HttpServletResponseで返却するのはGAEならではの話ではなく、Java Webシステム全般で共通の手法です。
Ajaxを使う時は必ず出て来ますので、覚えておくときっと良いことがあるでしょう。


終わりに

今回は楽勝な内容でしたね。

これでGAE開発の最も根幹部分であるAjax通信は完成です。
後はこれを経由して各種機能をガンガン作って行くだけです。

次回はログイン機能を作ってみようと思います。

2014年6月27日金曜日

【GAE】初級実装編 疎通確認 後半 ~レスポンス送信1~

株式会社ジェニシス 技術開発事業部の遠藤 太志郎(Tacy)です。

只今、クラウド基盤「Google App Engine(以下、GAE)」の連載しています。

本日から「初級実装編」と称しまして、実際にモノを作っている真っ最中です。
前回でクライアントからGAEにリクエストを送信する機能を作りましたので、今回はGAEからクライアントにレスポンスする機能を作りましょう。

懐かしのFORM作戦

さて、今回のGAEではWebAPIの形式を取っており、レスポンスにはJSON文字列を返却します。
JSON文字列の作成は主導ではなく、「JavaのクラスをJSONに変換するライブラリ」を使用しますので、JSONに変換する専用の型を用意しようと思います。

Struts1で言う所のFORMクラスです。

このFORMという設計発想は、私はちょっと冗長な部分があると考えています。
それは、DBから取得したエンティティを、一回FORMに置き換えなければいけないからです。

図でご説明しましょう。
通常のWebシステムにおいて、最も望ましいのはDBをそのまま画面に表示するような正規化された画面設計です。


DBから取り出した方は、DBと1:1で対応するモデル型に格納して、そのまま画面に出力します。

これが最も合理的でして、この思想が最も活かされているのは、あの有名なRuby On Railsです。

対して、FORM設計というのは、モデル型から一回置き換えを行わなければなりません。


そして、「モデル型」と「FORM型」は形がそっくりなケースが多いんですよね。
冗長なのです。

また、画面に1コずつFORMがあるので、画面に表示するテキストボックスが1コだけなのにわざわざFORMを作ってFORMが溢れかえっていくFORM爆発という事象が発生したり。
このように、FORMを採用したクラス設計は、ソースを汚くする罠が仕込まれていると考えるべきです。

だから、本来はRORのように、DBと画面の両側面を擦り合わせる設計をすべきというのが最も理想的。

しかしですね、GAEの場合は、あえてFORM作戦を採用する必要があると私は考えました。

と言うのもですね、作っているとどうしても不純物が混ざり込んで来るんです。

例えば、GAEは標準では「yyyy-MM-dd」形式ですが、画面には「yyyy/MM/dd」で表示したい、とか。
DBのレコードだけではなくて、WebAPIとして使い易い様に追加でレスポンスコードを出力したい、とか。

ちょこちょこちょこちょこ不純物のような要件が混ざり込んできて、とてもモデル型そのままでの出力は出来ないのですよ。


だから、その不純物を吸収するステップであるFORM型が必要になってきます。


GAEは多少泥臭い設計で丁度良い。


これ、凄く大事な概念です。
GAEは通常開発には無い制約がありますので、その制約をJavaコーディングで自力解決する必要が出て来ます。

そういったイレギュラーパターンに対応する為に、あえて冗長化したクラス設計を行う。

ベストの追求よりもリスク回避。


クラス設計担当者の匙加減が要求される部分です。
やっぱりGAEは通常開発より難しい印象ですね。

名付けてJET

しかし、StrutsのFORMというのは、HTMLのFORMタグと同一という意味を込めたネーミングです。

GAEのWebAPIは別にFORMに限ったことではないので、クラスにFORMと命名するのは本来の趣旨から外れてしまいますね。

役割はFORMと同じですが、ネーミングは変えた方が良いと思いました。

所謂、DTOシリーズの新型の登場です。


DTOというのは、「Data Transfer Object」という意味で、単純にデータを持っているだけの型ということです。
これを機能毎にネーミングを変えるというのが良くあるパターンです。


  • Struts1で画面とマッピングするDTO:FORM
  • DBから取得したレコードとマッピングするDTO:MODEL or entity
  • 実装の都合で用意する便利クラス:DTO


こんな感じです。
全部DTOに過ぎないのですが、用途に応じてネーミングを分けることで可読性を高める。

これもクラス設計には大事な要件です。

さて、今回作成するDTOは「JSONレスポンスに対応するDTO」という明確な役割付けがありますので、それに相応しいネーミングをしようと思います。

名付けて、「Json Engine Transfer Object」


Jet型です。


このJet型にセットしたJava型をJSON文字列に変換してレスポンスする。
このクラス設計で行きます!!


終わりに


これでかなり手堅い設計になったと思います。
後はJET型が増えすぎてJET爆発が起きないようにコントロールする必要がありますが、ここはちゃんと良く見るしか無いですね。

昔のStruts1の時代は、まだ開発セオリーが未熟だったこともあってFORM爆発が頻発していましたが、
今の時代のエンジニアならばそういうことが起きないように十分コントロールしていくことが出来ると思います。

次回は、上記の図にある「変換ライブラリで自動変換」

このライブラリの使い方についてご紹介します。

2014年6月18日水曜日

【GAE】初級実装編 疎通確認 前半 ~リクエスト送信~

株式会社ジェニシス 技術開発事業部の遠藤 太志郎(Tacy)です。

只今、クラウド基盤「Google App Engine(以下、GAE)」の連載しています。

本日から「初級実装編」と称しまして、実際にモノを作っている真っ最中です。
今回は疎通確認まで行ってみます。

疎通確認

過去記事の復習になりますが、GAE開発はフルAjax開発です。

この為、GAE開発の最も土台となる部分は非同期通信です。
今回はその非同期通信関連をテーマにお送りします。

非同期通信

私が社会人1年目であった2007年当時、まだAjaxという言葉は聡明期にありました。

この頃はGoogleがAjax機能を用いた「Googleマップ」「Googleサジェスト」を公開してAjaxの知名度が一気に世界的に広まった時期と重なりまして、
私は当時、このAjax機能について熱心に研究を重ねていたものです。

Ajaxというのは主にユーザインターフェースを快適にする為の技術ですので、TwitterやFacebookのようなSNS系のサイトでは多用される一方、
インターフェースよりも機能そのものに重点が置かれる傾向にあるビジネス用アプリでは出番が少な目な傾向にあると思います。

その点、私はビジネス系のSEでありながらAjaxが特技という、ちょっと変わったタイプかもしれませんね。

さて、GAE開発では通常のJavaWebシステム開発とは異なりフルAjax開発となりますので、
最も重要なのは非同期通信のロジックです。

非同期通信を実現する方法はいくつもありますが、
やはり今となっは事実上の業界標準と言って差し障りないjQueryを使うのが最適でしょう。

jQueryの解説サイトは山ほどありますので詳しくは省略しますが、「$.ajax」というメソッドを使うことでサーバと通信が可能です。

function loadMonthlyReport(){

     $.ajax({
          type: 'GET',
          url: '/webapi/develop/responsePing',
          data: 'message=' + "testMessage",
          dataType: 'json',
          success: function(json){

          alert(json.message);
          }
     });

}

ここでポイントとなるのは、「dataType: 'json'」、ここです。

この連載における非同期通信では、リクエストの送信はGET/POSTのような通常のHTTPリクエスト送信で行い、
そのレスポンスはサーバよりJSON形式にて取得致します。

JSONにつきましては次回に先送りし、今回はリクエスト送信に的を絞って記述します。

クルクル画像

過去の記事の復習になりますが、GAEにはスピンアップ問題という宿業がありまして、普通にサーバにアクセスすると、運悪くスピナップのタイミングを引いた人はサーバからのリクエストを何秒か待たされてしまうことになるのです。

もし普通のJavaWebシステム開発と同じくjspで開発したら、画面が真っ白のまま10秒くらい固まってしまうことになります。

これを回避というか、誤魔化す為のテクニックとしてAjaxを使うのです。

Ajax作戦はスピンアップを回避する為のものだという目的意識をハッキリと持たねばなりません。

つまり、上記の非同期通信を投げて、スピンアップして、戻ってくる10秒弱の間、利用者にストレスを与えないよう「ロード中画像」を表示するというのは、全く当然の帰結なわけです。

ロード中画像とは以下のような画像を意味します。


これが一番オーソドックスで手堅い戦術ですが、よりよりインターフェースの為に、更なる細かい工夫も考えて行きたい所ですね。

受信

さて、リクエスト送信結果をJava側にて取得します。

導入しているライブラリ「Slim3」は標準ではパッケージ構成とURLがマッピングしますので、
送信先URLが上記のような「/webapi/develop/responsePing」であった場合、
Javaの方では「jp.co.net.genesis.controller.webapi.develop.ResponsePingController.java」が呼ばれるという、
直感ですぐ分かる便利な構造になっています。

ここまで来れば、後は普通のWebシステムと同じく、HttpServletRequestからパラメータを取得するだけです。

試しにアクセスしてみて、ログを出力してみましょう。

protected void requestLog() {

  StringBuilder bul = new StringBuilder();

  Map requestMap = request.getParameterMap();
  for (String key : requestMap.keySet()) {
   bul.append(System.getProperty("line.separator"));
   bul.append(key);
   bul.append("=");
   bul.append(asString(key));
  }

  if (bul.length() > 0) {
   bul.insert(0, "===リクエストメッセージ:START===");
   bul.insert(0, System.getProperty("line.separator"));
   bul.append(System.getProperty("line.separator"));
   bul.append("===リクエストメッセージ:END===");
   logger.info(bul.toString());
  }

 }

結果は以下のような感じです。


普通に出力されましたね。
これで疎通確認の片方、リクエスト送信は終了です。

まあ、何てこと無い簡単な作業でした。

終わりに

「画面⇒サーバ」のリクエスト送信は全く標準的なものでして、特に目新しいことも無く簡単に実現できるかと思います。

一方、「サーバ⇒画面」のレスポンス。
これについてはGAEの特徴を考慮してクラス設計をする必要があり、ちょっと頭を捻る必要があります。

次回は「疎通確認 後半 ~レスポンス送信~」をお送りします。

2014年6月11日水曜日

【GAE】初級実装編 ログハンドリング

株式会社ジェニシス 技術開発事業部の遠藤 太志郎(Tacy)です。

只今、クラウド基盤「Google App Engine(以下、GAE)」の連載しています。

本日から「初級実装編」と称しまして、実際にモノを作って行きましょう。
机上で調査するより、実際に作った方がずっと高精度な解析が出来ますからね。

アカウント作成


さて、根本的に「GAEってどうやって始めるの?」という点につきましては、過去の連載をご覧下さい。

  1. 【GAE】Google App Engine 始めに
  2. 【GAE】スタート編1 アカウント作成
  3. 【GAE】スタート編2 アプリケーション作成
  4. 【GAE】スタート編3 Googleプラグイン&SDK取得
  5. 【GAE】スタート編4 Slim3導入

初級実装編では実装から入ります。

最初はログから

GAE開発に限らず、プロジェクトというのは最初が一番肝心です。

何にも無いクリーンな所から色々なライブラリを導入して、クラス設計して、DBのコネクションやトランザクション……など、
最初はやることが多過ぎてどこから着手したら良いか迷う所です。

その点、私の場合は習慣的にログハンドリングを最初に行うことが多いです。

今回はGAEのログ出力機能を確認してみましょう。

java.util.logging.Logger VS log4j

Javaのログハンドリングライブラリは、世の中はlog4jが主流になっていると思いますが、
GAEはJava標準のログハンドリング機能である「java.util.logging.Logger」を使用します。

ここで早くもGAEの特性が出現しました。

私は今まで、いくつかの現場を回っておりますものの、
ログハンドリングはほぼ100%に近い確率でlog4jを使用していました。

ふと気になって調べてみたいのですが、このlog4jの支配力の正体は、どうも純然たる伝統というのが真相のようです。

log4jというのは私が社会人になる前から存在している非常に古い伝統のライブラリで、
それに対してJava標準のjava.util.loggingは後になって作られたそうです。

機能的にもlog4jの方が高機能ですし、欠点と言えばjarファイルをimportしなければいけないということくらい。

あえて後発のjava.util.loggingに乗り換える必要も無いので、大概はlog4jを使っているのでしょう。
私も相当に長い間、「ログとはlog4jのことだ」くらいの気分でいました。

それくらい強力なシェアを誇るlog4jですが、GAEの場合はJava標準に準拠したのでしょうね。
java.util.loggingを使用するのが基本です。

一応、log4jに拘りのある人はlog4jをimportして使用することも出来るのですが、それをやるとGAEの機能をフル活用出来ません。
詳細については以下に続きます。

ログを出力してみる

では、さっそくログを出してみましょう。

まず、slim3プロジェクトを作成すると最初から「logging.properties」というファイルがあります。
これがログレベルを設定するファイルですので、以下のようにログ全出力にします。

.level = ALL

次に、以下のようなログ出力だけの機能を作りまして、これをデプロイし、アクセスしてみます。

public class IndexController extends Controller {

    /** logger */
    private Logger logger = Logger.getLogger(IndexController.class.getName());

    @Override
    public Navigation run() throws Exception {

        logger.finest("finest");
        logger.finer("finer");
        logger.fine("fine");
        logger.info("info");
        logger.warning("warning");
        logger.severe("severe");


        return forward("index.jsp");
    }
}

出力されたログを確認します。
ログと言えば普通はテキストファイルですが、GAEの場合は管理コンソールから閲覧出来ます。

その画像が以下。


なるほど。
つまり、GAEは以下4段階でログレベルを設定してくれるわけです。


  • D:デバッグ。fine、finer、finestの3つ
  • I:インフォ:infoに相当
  • W:ワーニング。warningに相当
  • E:エラー。severeに相当。


管理コンソールの機能により、「エラーだけを表示」のようなフィルタリングも可能です。

ちょっと戸惑うのが、「fine、finer、finest」は全部デバッグレベルとして一纏めにされる点ですが、
これも少し落ち着いて整理すれば簡単な話です。

GAE管理レベルとしては、確かに「fine、finer、finest」は全部同じデバッグレベルです。

しかし、「logging.properties」の設定では、これら3種も切り分けられますので、
「fineは見たいけど、finer、finestは不要」という場合は、logging.propertiesにfineと書いてデプロイすれば良いのです。

この辺りを細かく制御して管理するか、「いっそのことfiner、finestは封印する」かは、その時の判断ですね。

簡単なアプリであれば封印策を取った方がシンプルで良いかと思います。

そして、ここでlog4jの欠点が。
log4jを無理に導入すると、全部INFOログとして扱われます。

せっかくGAEに備わっているログレベルフィルタリング機能が使えなくなってしまいますので、
ここは素直にjava.util.logging.Loggerを使用して下さい。

フォーマット

ログのフォーマットですが、ローカル環境では「フォーマッター」を指定することで自由なフォーマットでログを出力出来ますが、
GAE環境で動かす時はGAEに搭載されたフォーマットしか使用することは出来ません。

なので、必ず上記のようなデザインのログが出力されます。
日付のフォーマットをちょっと変えたいとか、そういうことは出来ないのです。

一方、開発用のローカル環境のログは自分でフォーマットしないと読み辛いですので、
以下のように自分でフォーマットロジックを書いて対応します。

logging.propertiesに以下記述を追加して、フォーマッタークラスを指定します。

java.util.logging.ConsoleHandler.formatter=jp.co.net.genesis.common.LogFormatter

フォーマッタークラスは以下のように記述。

public class LogFormatter extends Formatter {

    /** 時間のフォーマット */
    private static final SimpleDateFormat sdf = new SimpleDateFormat(
        "yyyy-MM-dd HH:mm:ss.SSS");

    /*
     * (非 Javadoc)
     *
     * @see java.util.logging.Formatter#format(java.util.logging.LogRecord)
     */
    @Override
    public String format(LogRecord argLogRecord) {

        StringBuffer bul = new StringBuffer();

        bul.append(sdf.format(new Date(argLogRecord.getMillis())));
        bul.append(" ");

        if (argLogRecord.getLevel() == Level.FINEST) {
            bul.append("FINEST");
        } else if (argLogRecord.getLevel() == Level.FINER) {
            bul.append("FINER ");
        } else if (argLogRecord.getLevel() == Level.FINE) {
            bul.append("FINE  ");
        } else if (argLogRecord.getLevel() == Level.CONFIG) {
            bul.append("CONFIG");
        } else if (argLogRecord.getLevel() == Level.INFO) {
            bul.append("INFO  ");
        } else if (argLogRecord.getLevel() == Level.WARNING) {
            bul.append("WARN  ");
        } else if (argLogRecord.getLevel() == Level.SEVERE) {
            bul.append("SEVERE");
        } else {
            bul.append(Integer.toString(argLogRecord.getLevel().intValue()));
            bul.append("   ");
        }

        bul.append(" ");
        bul.append(argLogRecord.getLoggerName());
        bul.append(" - ");
        bul.append(argLogRecord.getMessage());
        bul.append("\n");

        // 例外がある場合に出力
        Throwable e = argLogRecord.getThrown();
        if (e != null) {
            bul.append(e.getClass().getName());
            bul.append(": ");
            bul.append(e.getMessage());
            bul.append("\n");
            StackTraceElement[] errs = e.getStackTrace();
            for (StackTraceElement s : errs) {
                bul.append("\t");
                bul.append(s.toString());
                bul.append("\n");
            }
        }

        return bul.toString();
    }

}

ローカル環境での挙動は普通のjava.util.logging.Loggerと大差ありません。
標準的なスキルで実現可能ですので、開発者の好みで事由にログフォーマットを記述して下さい。


終わりに

これにてログハンドリングは完了です。

しかし、「ネット上でログを見る」というのは便利な機能ですね。
いつでも自由にログをチェック出来ますから、保守を非常に効率的に行えます。

引き続き、アプリの構築を継続していきます。

2014年6月4日水曜日

【GAE】初級実装編 初めに

株式会社ジェニシス 技術開発事業部の遠藤 太志郎(Tacy)です。

只今、クラウド基盤「Google App Engine(以下、GAE)」の連載しています。

調査はここまで

今までGAEの連載の中で、一先ずGAEがどんな特性を有するモノであるのか、という事が概ね見えてきました。

しかしですね、すでに13回も連載していますが、GAEというのは非常に広大なテーマにつき、まだ実装関連については全く記載していません。

この調子で書き進めて行ったのでは10年経っても終わらないんじゃないか、という気がしてきました。

そこで、今までの13回の連載は「事前調査編」という扱いにして、ここから先はもう実装に入ってしまおうかと思います。

実装しながら随時、その時使っている技術をご紹介していきます。

やること

さて、実装編と言っても、何を作るかがポイントですよね。

実は、我が社には「商品在庫入出庫システム」という新人研修用の基本的なスキルアップカリキュラムがあるのです。
本来はピュアなJavaで作るカリキュラムですが、これと同じようなモノをGAEで作って行こうと思います。

ポイントは「同じようなモノ」であって、「同じモノ」では無いということです。

これは過去の連載でも何度も記述していますが、GAEには個性があるのです。

「今あるシステムを同じ仕様で移植すれば良いよね♪」

という発想は愚の骨頂
100%、無駄に苦労しただけで不便なシステムが出来上がります。

GAEで作る以上は、GAE最適化を行わなければなりません。

「単純なJavaシステムをGAEで作るとこうなっちゃうんだ」というbeforeAfterがこの連載の目玉になります。

機能詳細

具体的には以下のような処理の実装方法の紹介になります。

  • ログイン
  • データベースへの検索、登録、更新、削除
  • 複数テーブルのJOIN
  • トランザクションの実装
  • JUnitでテスト

まあ、普通のJavaで作る話であれば、特に語ることはありませんよね。
Javaの経験者であれば、どのようなロジックにてこれら機能が実現するかは暗黙の了解で通るレベルの基本的機能です。

しかし、GAEで作るとなると、そうは行きません。

例えば、GAEでは基本的にjspは使用しないのがセオリーです。


「商品検索結果一覧をjspを使わないで作れ」


なんて言われたら、もうそれは基本スキルではありません。
答えはAjaxを使用するわけですが、ならば「Ajaxライブラリはどれにする?」という話になりますよね?

ちょっとの機能でもどんどん話が膨らんでいってしまうのです。


GAE開発は大変なのです。


しかし一方で、「一通りのスキルセットを習得すれば、後は案外すんなり行くものだ」というのが私の所感です。

ここは一つ、自分も新人になった気分で、『GAEという新しい言語を覚える!!』という意気込みで臨んでいきたいと思います。

終わりに

次回よりプロジェクト開始です。

導入するライブラリを一通り揃える所から始まります。

2014年5月30日金曜日

【GAE】システム構成図案

株式会社ジェニシス 技術開発事業部の遠藤 太志郎(Tacy)です。

只今、クラウド基盤「Google App Engine(以下、GAE)」の連載しています。

本質を知ろう

さて、今までの記事で巨大なGAEの一部を検証して参りました。

それについて見えてきたのは、「GAEには普通のシステムとは異なる個性がある」ということです。

この個性とは、GAEの本質と言い換えることも出来ます。

ここで一つ、大事な事を明言しておきましょう。


  • IT技術は、その本質を理解している者でなければ使用出来ない。


よくあるんですよ。



XXという技術が便利で凄いという噂を聞いた。⇒ザックリ調査⇒使えるみたいだし、早速使おう!!⇒爆死



GAEに限らず、IT技術には「限られた局面でのみ力を発揮する」という局地戦特化の性質を持つものがあるのです。


そのIT技術を使用するに相応しい要件、シチュエーション。向き不向き。それが本質です。

本質を理解しないままIT技術を導入しますと、高確率で要件とのミスマッチが発生して、もしくはその技術の真価を発揮させることが出来なくて、最終的には爆死することになります。

特にGAEの場合、その個性がかなり強い方である上に、基幹技術ですからね。

ちょっとライブラリを導入するとか、そんなレベルではなく、ここの本質を見誤ると爆死も爆死。
物理的にゴールに辿り着けなくなる恐れさえあります。

ここから更にGAEについて掘り下げていく前に、この辺りで一回、本質について整理してみましょう。

振り返り+少々

スピンアップ

GAEの強烈な個性の一つは「スピンアップ」です。

ここ数回の記事で長々とスピンアップについて検証してきましたが、

  • GAEは使用していない時間帯にインスタンスが落ちる。
  • インスタンスが起動するタイミングで数秒~10秒くらい待たされる。
  • この為、フルAjaxを使用せざるを得ない。(jspには不向き)

この辺りがGAEの特徴の一つ「画面インターフェースの制約」です。

DBが特殊

DBについてはまだ記事にしていませんが、内々に検証しておりますので、フライングで少々記載します。

Google App Engineは、DBが普通のリレーショナルデータベースではなくて、Bigtableという特殊なDBなのです。

これに伴い、普通のDBなら出来て当然のことが出来ません。

例えば、count(*)とかは出来ません。
やるなら全レコードを検索して、Javaで行数をカウントすることになりますが、そんなことやってたら超遅いですよね?
なので、レコード件数をカウントする機能が重要な要件には向きません。

他にはjoinが無いとか。

joinを実現したい場合は、Javaで複数回クエリを投げてマッピングを取るなど、コーディング実装のレベルで作り込む必要があります。

つまり、複雑なクエリが必要になる機能は実現出来ません。
GAEはシンプルな要件にしか適応出来ないのです。

安定性、高可用性

一方で、大量データの取り扱いなど、安定性、高可用性には無類の強さを発揮します。
何せ、GmailなどGoogleのアプリは全部コレで捌いているわけですから。

「アクセス殺到によりダウン」ということはまず無いと言い切ってしまって良いかと。

障害は偶に発生しているような未確認情報も入りますが、それでも自社でサーバを持つよりはよっぽど安定していると思います。

サーバ自体の信頼性はかなり高度な域にあると考えて良いです。


システム構成図

他にも色々とGAEには特徴がありますが、その中でも「本質」と呼べるような特記事項中の特記事項は以上の辺りかと思います。

この辺りを考慮に入れて、私はGAEの力をフルに発揮するシステム構成図案を考えてみました。

それが以下、「GAEによるWebAPI戦略」です。



端末はスマートフォンも視野

最近はAndroidやiPhoneなどの、スマートフォン、タブレットの普及が著しいですよね。
しかし、あれらは携帯端末というハードウェアの性能の都合上、重たい処理を端末側に持たせることが出来ません。

この為、重たい処理や大量データの確保が必要な場合は別途サーバを立てて、そこからHTTP通信によりXMLやJSONの形式でデータを取得する手段が多く取られます。

元々、GAEサービスのフルAjax化しなければなりませんので、サーバ本体は必ずWebAPIとしての役割を担うことになります。

このついでに、PCだけでなくスマホアプリまで視野に入れるというのは一石二鳥で合理的なのです。

圧倒的に頑強な本体

WebAPIアプリの性質上、中枢である本体サーバがダウンしてしまっては話になりません。

そこで、GAEの圧倒的な頑強さが力を発揮するのです。
GAEを本体とすることは、自社のサーバを持つよりも圧倒的に安心です。

また、上記のスマホアプリ計画と連動しますが、アプリに人気が出てアクセスが殺到することも考慮したいケースもありますよね?

自社でサーバを持っていた場合は人気が出始めてからサーバを増強しなければいけませんが、
GAEは自動的にスケールアップしていきますので、そのような配慮は無用!!

将来の大人気アプリ、大人気サービスを目指すからこそ、GAEの力が真価を発揮出来るのです。

機能間の独立

これは技術的というより、体制的、政治的な配慮になるかもしれません。

ハッキリ言って、「GAEに精通していて、Ajaxも出来て、AndroidアプリもAppleアプリも開発出来るエンジニア」なんていませんよ。

普通のWebシステム開発って、一人の人間がHTMLを書いて、Javaも書いて、SQLも書いて、一連の機能を実現しますよね?
でも、GAE開発はそんなの絶対無理です。


  • Ajaxは書けるけど、GAEは知らない。
  • Androidは知ってるけど、GAEは知らない。
  • GAEは覚えたけど、他のスキルまで習得している余裕が無い。


こんな調子になって、開発メンバーを招集出来ないに決まっています。
しかし、このWebAPI体制を取ることで、「GAE本体担当」「HTML担当」「Android担当」「Apple担当」とスキル毎に明確に独立することが出来るのです。

その技術単品だけ分かっていればOK!!

これなら技術者も集められます。

機能制約の確保

上記に繋がりますが、GAEには実現出来ない要件というのもあります。

何が出来て何が出来ないのかはそのプロジェクト毎に精査しなければなりませんが、確実に言えることは一つ。

「WebAPIインターフェースで提供していない機能は、実現出来ないという意味だ!!」
「WebAPIインターフェースで提供している機能の範囲内でアプリを実現するべき!!」

という、ポリシーの確保が必要だと言うことです。

これも「GAE本体担当」という専任者を置くことで、かなりの精度でポリシーを守り抜くことが出来るはずなのです。

終わりに

以上が、私の考えているGAEのシステム構想です。

我ながら合理的な作りになっているのではないかと思います。

しかし残念なことに、「Ajax」「Android」「Apple」、これら全部を開発するようなビッグな案件が私の身近に無いのですよね。。。

まあ、将来的な目標として温めておくとしまして、しばらくはAjaxベースで検証を進めていきたいと思います。


次回以降は、GAEの個性的なデータベース「ビッグテーブル」について連載していきたいと思います。

2014年4月8日火曜日

【GAE】スピンアップ問題4~Ajax作戦~

株式会社ジェニシス 技術開発事業部の遠藤 太志郎(Tacy)です。

只今、クラウド基盤「Google App Engine(以下、GAE)」の連載しています。

現在はGAE界最大の敵である「スピンアップ」のシリーズです。

本日はスピンアップ編の最終章、Ajax作戦についてです。

重いものは重い

さて、前回のおさらいからですが、GAEのスピンアップは重いものです。
限界までチューニングした所で数秒、現実的には7~8秒は必要とすると見込んで良いでしょう。

つまり、


  • 最初に表示した時、画面が10秒近くホワイトアウトしている。


こういう現象に陥ってしまうわけです。
今回はこの現象に対する対抗策に関する内容です。

我慢作戦

まあ、「スピンアップ」というのはインスタンスが起動する時だけ重いものですから、
最初の一回だけ我慢すれば、後は速いものですよ。

私は過去に負荷テストを行っていますが、
その時の実績によると、20~25人がF5アタックしているような状態でも1インスタンスで捌けてしまいます。

つまり、普通の人が普通にアクセスするペースだと、その10~20倍の250~500人程度は、スピンアップが最初の1回以外は発生しない見込みになります。

何が言いたいかと言いますと、

  • ホワイトアウトなんて滅多に起きないんだから我慢しろ。

というスタンスも十分アリってことですよ。

2回目にアクセスした時は速くなっているわけですから、ネット回線の一時的な不調と切り分け出来ないレベルだと思います。

しかし、我慢作戦では元も子もありませんから、真面目に技術的対処方法をご紹介します。
それは、Ajaxを使用することです。

Ajax作戦のカラクリ

GAEのスピンアップ問題から逃れることは出来ません。
そこでAjaxが活躍するのです。

  1. 先にHTMLファイルや画像などの静的コンテンツを表示する。
  2. 表示直後、OnLoadでAjax起動。くるくるダイアログを表示しつつ、非同期通信で動的コンテンツを取得。
  3. Ajaxにより動的コンテンツを表示。

この三段構えで表示することで、体感的に速く表示されるようになります。

1.静的コンテンツ表示

実はですね、GAEというのは、「Javaファイルが置かれているサーバ」と「HTMLや画像など静的ファイルが置かれているサーバ」は別々なのです。

そして、スピンアップが発生するのはJavaが置かれているサーバのみ。
HTML、CSS、JS、画像ファイルなど、静的ファイルの表示だけであればスピンアップは発生しないのです。

よって、①は常に超高速で表示されます。

これがAjax作戦の最大の秘訣です。

2.非同期通信

Ajaxの非同期通信によって、Javaにアクセスして内容を動的に表示します。
ここでスピンアップが起きるので、くるくるダイアログが10秒近く表示されていることになります。

つまり、トータルで待たされている時間は全く変わらないわけです。

ただ、①の時点で枠組み部分は表示済みになっており、処理中を意味する「くるくるダイアログ」も表示されていますので、
ホワイトアウト状態と比較すれば、待たされている人のストレスがは劇的に軽減されます。

3.描画処理

バックエンドの非同期通信でデータを取得しましたら、画面に出力します。


これにて画面が無事に表示出来ました。
これがGAE界で主流になっているAjax戦術の概要です。


Ajaxスキル必須


はい、ここでお分かりになられたかと思います。

Ajax作戦では、初期表示の際には静的ファイルであるHTMLを使用して表示します。
よって、jspは使用出来ません。
システム全体がAjaxに完全対応しなければならないのです。

上記には「我慢作戦という選択肢もある」と書きましたが、実際の所、GAE界ではこのAjax作戦が支配的なシェアを持っています。
GAE開発では、Ajax作戦が最適解であると言い切ってしまって良いでしょう。

つまり、開発者には「Javaスキル」と「Ajaxスキル」の両方が求められるわけです。

  • GAE開発は、JavaとAjaxの両方のスキルを持っていることが必須!!

プルダウンの中身を差し替えるだけみたいな粗末なAjaxではありません。
画面全体がAjax状態。
技術者はフルAjaxを実現するスキルを持っていなければなりません。


私はこの、「技術者の要求するスキルハードルの高さ」がGAE最大のネックだと思っています。
JavaとAjax、両方に精通している技術者というのは中々いません。


一般に、Javaというのは業務用アプリで多く使われる傾向にあります。
業務用アプリではAjaxは出番が少ないのです。

逆にAjaxが多く使用されるのはエンドユーザ向けシステム、例えばSNS辺りでしょうが、ああいうのはサイクルの速いスクリプト言語で書かれている事が常です。

つまり、GAE開発に必要とする「Java」と「Ajax」を両方揃えている技術者というのはレアケースということになります。


一応、「Android + HTML5のハイブリッドアプリ」の開発経験者なら期待出来る所ですが、それも技術者全体から見れば少数派でしょうし……。


「Java × Ajax」、これは意外にレアなのです。


終わりに

GAE最大の問題である「スピンアップ」について検証を重ねるうちに、
徐々にGAEの癖というものが分かってきましたね。

GAEは、普通のJavaWebシステム開発とは明らかに違う点が、確かにあります。

この為、GAE開発を行っている会社というのは、GAE専門特化型になっているケースが多いように見受けられますね。
スペシャリスト型でなければキツいのでしょう。

そんな、気軽に使うにはちょっとハードルの高いGAEですが、
次回はそれの有効活用法について検討したいと思います。