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の黒魔術ということでご了承下さい。

終わりに

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

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