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という新しい言語を覚える!!』という意気込みで臨んでいきたいと思います。

終わりに

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

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