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年5月19日月曜日

【追加】データベースのテスト支援ツール DbUnit その5 リプレース機能編

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

只今、私の現場ではこのブログを参考資料としてJUnit開発を進行中です。

現場で使っているうちに、もっと便利な機能も見つかってきましたので、その補強連載中です。

固定じゃ対応出来ないことも

さて、この連載のテーマである「DbUnit」は、DBのレコードと位置づける定義XML「データセット」を前もって用意しておきます。

その「データセット」の内容をDBに初期登録して環境をセットアップする。
もしくは、検索結果がその「データセット」と同じである事を以て、正常を確認する。

そういう使い方なわけです。

この「データセット」は、基本は固定なのです。
固定値を「データセット」として定義しておくというのが基本的な使い方です。

しかし、場合によっては「固定値」では都合が悪いことも。。。

例えば、「本日から7日以内に作成されたレコードを検索する」のテストを行いたい場合、
もちろん「テストを実行する本日から7日以内」のレコードをセットアップしなければなりません。

でも、「本日」の定義は毎日変わっていってしまいますよね?
だから、投入するデータセットは固定では対応出来ません。
「本日から7日以内」に相当する時間部分のカラムだけは動的にセットする必要がある。

こういう需要なわけです。

他の需要を言いますと、「テスト毎にちょっとしか定義に差が無いのに、毎回似たようなデータセットを作るのが面倒!!」というケースでしょうか?

これは気持ちは理解できますが、積極的にはオススメ出来ないですね。
データセットを動的にしてしまいますと、テストケース自体にバグが潜在してしまうリスクが発生してしまいますから。

余りにデータセットが増えすぎてゴジャゴジャになってしまうという場合は一考するのもありですが、基本は固定データセットで行うべきかと思います。

ReplacementDataset

では本題の動的対応の解説です。

この機能は、DbUnitでは「ReplacementDataset」というクラスで実現出来ます。

「Replace」という名前の通り、この機能は置換です。


前もってダミーの値をデータセットにセットしておいて、後で置換する。


こういう戦術なわけです。

まず、普通のデータセットを見てみましょう。

<dataset>
   <shohin id="111" shohin_name="テスト商品" create_at="2013-01-20" />
</dataset>

これをセットアップするJavaソースはこちら。

// データセットの取得
IDataSet dataset = new FlatXmlDataSetBuilder().build(new FileInputStream("src/test/resource/jp/co/net/genesis/entity/商品_登録数3.xml"));
// セットアップ実行
DatabaseOperation.CLEAN_INSERT.execute(connection, dataset);

この基本セットアップ機能自体の解説については、過去の連載をご確認下さい。

さて、今回は、この「create_at」の時間が固定では困る、という話です。
ここを何でもいいのでダミーに差し替えます。

<dataset>
   <shohin id="111" shohin_name="テスト商品" create_at="[-7day]" />
</dataset>

[-7day]の部分がダミー値です。

「文字列置換」ですから、特にコーディング規約はありません。分かればOKです。
ただ、分かり易いように、必ず本日にしたいカラムは[TODAY]、必ず一週間前にしたいカラムは[-7day]といった自主ルールを作っておくと
可読性が高まって便利かと思います。

そして、これをセットアップするJavaソースはこちら。

// データセットの取得
IDataSet dataset = new FlatXmlDataSetBuilder().build(new FileInputStream("src/test/resource/jp/co/net/genesis/entity/商品_登録数3.xml"));
// リプレース
ReplacementDataSet expectedDataSet = new ReplacementDataSet(dataset);
expectedDataSet.addReplacementObject("[-7day]","2014-05-11" );
// セットアップ実行
DatabaseOperation.CLEAN_INSERT.execute(connection, expectedDataSet);

以下の部分が置換実行の箇所です。


  • expectedDataSet.addReplacementObject("[-7day]","2014-05-11" );

まあ、何てことは無い、普通の置換ですね。
このサンプルでは分かり易いように「"2014-05-11"」と固定値で置換していますが、
実際の現場では、この日付文字列を動的にセットするようにロジックを組めばいいわけです。

くれぐれも「日付文字列を動的にセットするロジックがバグっていた」というオチが無いようにお気をつけを。


ちなみに、上のサンプルは「セットアップレコードの置換」の内容になっていますが、
「SQLで登録結果を確認する為のデータセットの置換」も全く同じやり方でOKです。

テスト対象のSQLでレコードを作成して、そのレコードの「作成日付が本日日付になっていることの確認」みたいな時に使うと良いでしょう。

終わりに

ひとまず、現場で新しく上がったJUnit関連の要望はこの辺りでフォロー完了です。

また何か新情報を見つけたら追加記載します。

2014年5月8日木曜日

【追加】最強モックツール JMockit その13 DI(Spring)対応

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

只今、私の現場ではこのブログを参考資料としてJUnit開発を進行中です。

その現場にて必要になった新情報を補強連載しております。

DI

今回の記事で重要になるキーワード、それは「DI」です。
依存性の注入(Dependency injection)という意味でして、Javaのクラス宣言を普通に「new ○○」とやるのではなく、外部XML等に宣言して
クラス間の結合を疎にするというソフトウェアパターンです。

このDIの機能を提供してくれるフレームワークをDIコンテナと言います。

DIコンテナの代表格と言えば、やっぱり「Spring」ではないでしょうか?

老舗にして大御所。
Javaを使っている人は、いつかどこかでお世話になるフレームワークかと思います。

DIの利便性

DIを使うことの利点、それは「クラス間が疎結合になる」ことです。

と言いましても、ピンと来ない人も多いかもしれませんね。


「クラス間が疎結合になったから何だって言うんだ?」


全く普通の感想かと思います。

DIコンテナを使うと、クラスを生成する際に普通に「new ○○」とは書けなくなって、
XMLファイルに色々宣言したりと手間が掛かるようになります。

私もいくつかの現場でSpringを使った事がありますが、振り返って考えてみると、
「クラス間が疎結合になり、ただ面倒になっただけ」という失敗ソースが多かったですね。

Springは結構難しいフレームワークなので、深く理解してからクラス設計しないと大失敗に陥ってしまいます。

DIコンテナについて記述していくと一回の記事で終わりませんので手短に行かせて貰いますが、
DIコンテナの利便性の一つ、それは「スタブが使い易い」ということです。

DIによるスタプ

まずはDIを使った簡単なサンプルを以下に記述します。

public class Sample01 {
 
 /** DIコンテナで自動セットするDAO */
 @Autowired
 private SampleDao sampleDao;
 
 public void test() {
  
  //DB検索実行
  sampleDao.findAll();
  
 }

}

このソース、「new SampleDao()」なんてやってる箇所が無いですよね?
SampleDaoはDIによってクラス生成時に自動セットされますので、自分でnewする必要は無いのです。
「@Autowired」のアサーションが、自動セットするフィールドであることを示すマークです。

これは分かっている人にとってはソースがスッキリして良いのですが、分からない人にとっては混乱するだけという、ちょっとハードルのある機能です。

さて、この「SampleDao」は、実はインターフェースでして、実クラスではありません。
実クラスが何なのかはソース中ではなく外部クラスに定義されています。


DIの特徴:ソース中に出てくるのはインターフェースだけ。実クラスは出てこない。


では、実クラスはどうやってセットされるのか?
それを外部のXMLファイルに記述するわけです。

<bean id="target" class="jp.co.net.genesis.sample.Sample01Impl" / >

こんな感じです。

インターフェースの中身は外部定義ファイルに定義されます。
これがDIコンテナの最大の特徴。


  • 本番時は本番用の外部定義ファイルを読み込む。⇒本物のクラスを定義
  • テスト時はテスト用の外部定義ファイルを読み込む。⇒テスト用スタブを定義


こうすることで、ソースの中身に手を加えることなく本番モードとテストモードを切り替えることが出来るわけです。

この機能は大規模開発で役に立つことが多いですね。
複数のチームで一つのシステムを作るような大規模開発の場合、一つのチームの進捗遅れや手違いで全チームが影響を受けることもしばしば。

しかし、DIコンテナを使うことで、
「本番用ソースは別チームが開発中」、「こっちは先にスタブでテストを済ませておく」みたいな戦術が使えるようになり、
チーム毎やクラス毎の独立性が確保されるようになるわけです。

DIのモック化

しかしですね、上記の場合、「わざわざ本番用とスタブの2コのクラスを作らなければならない」という手間もあるわけですよ。


スタブを作るのが面倒臭い!!


結構あるんですよ。
ちょっとの機能なのに一々スタブを作るなんて面倒でやってられない、とか。

だったら最初からDIコンテナなんて使うな、と言いたい所なのですが、現場の事情によることもありますので、
JMockitにて柔軟に対応していきましょう。

public class Sample01Test extends ContextManager{

 private Sample01 mockSample01;

 /** DIのモック  */
 @Mocked
 private SampleDao sampleDao;

 @Test
 public void testDoTest() {

                mockSample01 = getBean(Sample01.class)

  new NonStrictExpectations() {{
   Deencapsulation.setField(mockSample01, sampleDao);
        }};

        mockSample01.test();

 }

}

さて、JMockitは内部で「new」しているクラスも勝手にモック化する超強力ツールであることは第4回でご紹介しましたが、
DIコンテナの場合は「@Autowired」のアサーションで自動セットしており、内部でnewしているわけでは無い為か、普通にやってもモック化されません。

厄介ですね。

こういう場合は、荒技を使います。


  1. とりあえず普通にコンテナ経由でクラスを生成して、@Autowiredにより自動セットして貰う。
  2. 自動セットされた後でモックに上書き!!

privateフィールドを上書きするという超荒技です。


「Deencapsulation.setField(mockSample01, sampleDao);」というソースが、その上書きを行っている箇所です。


簡単に使えますね。
Javaクラス設計の根底をひっくり返す裏技ですが、そこはJMockitの黒魔術ということでご了承下さい。

終わりに

引き続き、現場からのフィードバック記事を連載します。

リクエストがあればこのような形で順次お答えしていきたいと思います。