2014年12月23日火曜日

【JsRender】#parent

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

現在はJavaScriptによる画面レンダリングツール「JsRender」について連載しています。

今回も公式サイトに乗っていない秘密機能「parent」についてのご紹介です。

必要になるシチュエーション

#parentはfor文の真っ最中などで、親クラスを遡ってパラメータを取得する機能です。

例えば、私は「技術開発事業部」の所属ですが、同じ部にはメンバーが何人もいます。

  • 技術開発事業部:Aさん
  • 技術開発事業部:Bさん
  • 技術開発事業部:Cさん

こういう場合、データの持ち方によっては

  • 親クラス:部署
  • 子クラス:メンバー


というように、親子関係が生ずることになります。

このデータの持ち方で所属とメンバーを表現したい場合は、以下のような形式にするのが合理的です。


<div id="result"></div>

<script id="jsRenderTmpl" type="text/x-jsrender">
★{{:department}}
{{for members}}
<div>
・{{:name}}
</div>
{{/for}}
</script>

<script>
var data = {
  "department": "技術開発事業部",
  "members": [
      {"name": "Aさん"},
      {"name": "Bさん"},
      {"name": "Cさん"}
 ]
};

var template = $.templates("#jsRenderTmpl");

var htmlOutput = template.render(data);

$("#result").html(htmlOutput);
</script>

しかしですね、場合によっては以下みたいな表示にしたいケースもあるわけですよ。


これは痛いんですよね。
何故なら、階層が違うから。

JSONの定義では「members」をfor文のスコープとして回していますので、membersより配下のデータを表示する場合は何も難しくありません。
普通に順を追って定義していけば表示出来ます。

しかし、membersより上のデータはスコープの中に入っていませんので、単純に取得することが出来ません。
今回のテーマである#parentはこういう場合に使用します。

#parentを活用

#parentは、まあ、タグ名から分かりますようにfor文のスコープの外にある親データを取ってくる機能です。
その実装方法はこちら。

<script id="jsRenderTmpl" type="text/x-jsrender">
{{for members}}
<div>
・{{:#parent.parent.data.department}}:{{:name}}
</div>
{{/for}}
</script>

<script>
var data = {
  "department": "技術開発事業部",
  "members": [
      {"name": "Aさん"},
      {"name": "Bさん"},
      {"name": "Cさん"}
 ]
};

var template = $.templates("#jsRenderTmpl");

var htmlOutput = template.render(data);

$("#result").html(htmlOutput);
</script>

「#parent.parent.data.department」、これで「技術開発事業部」という値が取れています。

詳細解説

「あれ? parent2連打になってるのは何?」と思われたかと思います。

jsrenderでは「parent」を一個各毎に階層を一個遡るという意味になります。

今回の場合、スタート地点となるのはfor文の中のメンバーですから、


  • 単にそのレコードを出したい場合⇒「#data.name」(dataは通常省略)
  • その一個上のmembersを取りたい場合⇒「#parent.data」
  • その一個上のdataを取りたい場合⇒「#parent.parent.data」
  • 目的のdepartmentはdataの子供なので⇒「#parent.parent.data.department」

こういうわけです。
parentを連結することで一回一番上まで遡って、それからdepartmentを狙い撃ちするという段取りになるわけです。

parentを連発する辺り最初は戸惑いますが、分かってしまえば簡単なことですね。

終わりに

今回ご紹介した#parentは結構便利な機能です。
これを知らないと画面を表示する為にクラスを非正規化しなければならなくなり、手間も転送データ量も増えて大変ですからね。
上手く活用すれば開発効率もUPするでしょう。

ぜひマスターすることをオススメします。

2014年12月8日月曜日

【JsRender】#index

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

現在はJavaScriptによる画面レンダリングツール「JsRender」について連載しています。

さて、私はこの連載を行うに辺り、基本的に以下の公式サイトを読んで執筆しています。




しかし、どうも公式サイトのドキュメントに載っていない機能があるようです。
酷い話ですが、今回はその機能の一つ「#index」についてご紹介します。

for文のループ回数「#index」

普通、for文だと「for(int i=0;i;10;i++)」みたいに現在がfor文の何番目なのかを
iなどの変数で認識することが出来ます。

しかし、JsRenderのfor文はfor文と言ってもJavaで言う所の「for(String str : list)」の形式と言いますか、
つまり『配列全出力』の体を取っています。

この為、実装する上では通常のプログラミングである「i」に相当する変数が登場しません。
この為、「偶数行と奇数行で色を変えたい」みたいな、現在が何ループ目であるかを認識するには、挿入の元になっているJSONにパラメータを持たせる必要があるように見えます。

しかし、実は隠しパラメータとしてループの行数を持っていまして、それが「#index」です。

<script id="jsRenderTmpl" type="text/x-jsrender">
★{{:title}}
{{for members}}
<div>
・{{>#index}}:{{:name}}
</div>
{{/for}}
</script>

<script>
var data = {
  "title": "for文サンプル一覧",
  "members": [
      {"name": "碇シンジ",sex:"male"},
      {"name": "綾波レイ",sex:"female"},
      {"name": "ゼルエル",sex:"unknown"}
 ]
};

var template = $.templates("#jsRenderTmpl");

var htmlOutput = template.render(data);

$("#result").html(htmlOutput);
</script>



ちゃんと行数が出ているでますね?

加工

では、この「#index」がちゃんと加工したりif文で使えたりするかも検証してみましょう。

まずは偶数/奇数の判定から。

<script id="jsRenderTmpl" type="text/x-jsrender">
★{{:title}}
{{for members}}
<div>
・{{>#index}}:{{:name}}:
 {{if #index %2 == 0}}
  偶数行
 {{else}}
  奇数行
 {{/if}}
</div>
{{/for}}
</script>

<script>
var data = {
  "title": "for文サンプル一覧",
  "members": [
      {"name": "碇シンジ",sex:"male"},
      {"name": "綾波レイ",sex:"female"},
      {"name": "ゼルエル",sex:"unknown"}
 ]
};

var template = $.templates("#jsRenderTmpl");

var htmlOutput = template.render(data);

$("#result").html(htmlOutput);
</script>


バッチリですね。

では、「0からスタートではなく、1からスタートしたい」という場合は、
単純にindexに+1すればOKです。

<script id="jsRenderTmpl" type="text/x-jsrender">
★{{:title}}
{{for members}}
<div>
・{{>#index + 1}}:{{:name}}
</div>
{{/for}}
</script>

<script>
var data = {
  "title": "for文サンプル一覧",
  "members": [
      {"name": "碇シンジ",sex:"male"},
      {"name": "綾波レイ",sex:"female"},
      {"name": "ゼルエル",sex:"unknown"}
 ]
};

var template = $.templates("#jsRenderTmpl");

var htmlOutput = template.render(data);

$("#result").html(htmlOutput);
</script>



簡単ですね!

終わりに

この「#index」は、前述の通り無くてもJSON定義の方で回避可能です。
しかし、知っていると知らないとでは便利さが格段に違います。

何で公式サイトに載っていないのか謎ですが、ぜひ活用していきたいものです。

引き続き隠し機能を検証します。

2014年11月5日水曜日

【JsRender】prop

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

現在はJavaScriptによる画面レンダリングツール「JsRender」について連載しています。

今回は「props」、つまり「プロパティ」についてご紹介します。

要するにMap

このpropsとは、要するにJavaで言えばMap総当たりのことです。
propsは「Mapに格納されている全てのkeyに対して値を取得する」というものです。

例を挙げてみましょう。

まず以下にまたしてもメンバー情報一覧を表示するソースを記載します。

<script id="jsRenderTmpl" type="text/x-jsrender">
★{{:title}}
<table>
 <tr>
  <td>名前</td>
  <td>年齢</td>
  <td>性別</td>
  <td>血液型</td>
 </tr>
{{for members}}

<tr>
 <td>{{:name}}</td>
 <td>{{:age}}</td>
 <td>{{:sex}}</td>
 <td>{{:blood}}</td>
</tr>

{{/for}}

</table>
</script>

<script>
var data = {
  "title": "名前一覧",
  "members": [
      {"name": "Tacy1号","age": "28","sex": "男","blood": "AB"},
      {"name": "Tacy2号","age": "28","sex": "男","blood": "AB"},
      {"name": "Tacy17号","age": "28","sex": "男","blood": "AB"}
 ]
};

var template = $.templates("#jsRenderTmpl");

var htmlOutput = template.render(data);

$("#result").html(htmlOutput);
</script>



今回問題になるのは「横の長さ」です。
サンプルでは「名前」「年齢」「性別」「血液型」の4列ですけれども、案件によってはズラッと長々と出てくることもありますので、そういう場合に一つ一つペタペタ書いていくのは結構大変というもの。

そこで、「JSONタグの中にあるものを全て出力する」としたい場合に出番となるのがpropsです。

そのサンプルがこちら。

<script id="jsRenderTmpl" type="text/x-jsrender">
★{{:title}}
<table>
{{props members}}
<tr>
 {{props prop}}
 <td>{{>key}}:{{>prop}}</td>
 {{/props}}
</tr>
{{/props}}

</table>
</script>

<script>
var data = {
  "title": "名前一覧",
  "members": [
      {"seq":0,"name": "Tacy1号","age": "28","sex": "男","blood": "AB"},
      {"seq":1,"name": "Tacy2号","age": "28","sex": "男","blood": "AB"},
      {"seq":2,"name": "Tacy3号","age": "28","sex": "男","blood": "AB"}
 ]
};

var template = $.templates("#jsRenderTmpl");

var htmlOutput = template.render(data);

$("#result").html(htmlOutput);
</script>



「{{props members}}」とやることで、menbersの中の情報を全部取得します。
ここから、


  • 「{{>key}}」でそのパラメータのキー値を。(配列の場合はシーケンス番号)
  • 「{{>prop}}」と指定することそのパラメータを。


これらをループで取得することが出来るわけです。

propsは入れ子で使うことも可能で、上記の例も入れ子型です。
「{{props prop}}」となっている部分が入れ子に書いている部分ですね。

デザインが……

「あれ? 上と下で画面レイアウトが違うじゃねえか!!」とお気づきの方、その通りです。

propsはパラメータ総出力なので、細かいレイアウト調整が効かないのです。
なので「ヘッダーを日本語で追加したい」とか「JSONタグのうち、必要なカラムだけ出す」とか、
そういう細かい要望にはif文でも使わない限り対応出来ません。

そんなロジック入れるのであれば、最初から従来のやり方通りにkey値をペタペタ決め打ちして、for文で回して書いていった方が良いと思いますね。

需要としては「JSONタグの中身をシンプルに全部出す」というケースくらいしか無いでしょう。

JSONタグを合わせる

「加工無しの全出力なんて、デバッグでしか使わないよ!!」と思ってしまうかもしれませんが、
しかしですね、この問題は発想の転換で乗り切りましょう。
ズバリ、JsRender側ではなく、JSONタグの方を合わせる方法で解決出来ます。

「サーバ側処理で必要なデータ加工を全て完了させて、HTML側ではそれを出すだけ」という切り分けを徹底すれば、
非常に便利な機能になります。

こんな感じに。


<script id="jsRenderTmpl" type="text/x-jsrender">
★{{:title}}
<table>
<tr>
 {{props header}}
 <td>{{>prop}}</td>
 {{/props}}
</tr>
{{props members}}
<tr>
 {{props prop}}
 <td>{{>prop}}</td>
 {{/props}}
</tr>
{{/props}}

</table>
</script>

<script>
var data = {
  "title": "名前一覧",
  "header": {"seq":"No","name": "名前","age": "年齢","sex": "性別","blood": "血液型"},
  "members": [
      {"seq":0,"name": "Tacy1号","age": "28","sex": "男","blood": "AB"},
      {"seq":1,"name": "Tacy2号","age": "28","sex": "男","blood": "AB"},
      {"seq":2,"name": "Tacy3号","age": "28","sex": "男","blood": "AB"}
 ]
};

var template = $.templates("#jsRenderTmpl");

var htmlOutput = template.render(data);

$("#result").html(htmlOutput);
</script>


ほら?
ヘッダーの属性とかも全部ひっ包めてJSONに入れてしまえばキッチリ綺麗になるでしょ?

これはコーディング思想として一つの基準になると思いますね。
HTMLやJavaScriptにボコボコ書き過ぎてしまうと可読性や生産性が大幅に下がります。

JsRenderは確かに便利なライブラリですが、しかしJsRenderは重い処理をする為にあるのではないのです。
「必要な情報はJSONに全部入れる」という実装思想を持つことが良いモジュールを作るキーとなるでしょう。

終わりに

引き続きJsRenderについて連載を継続します。

2014年10月31日金曜日

【JsRender】include文

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

現在はJavaScriptによる画面レンダリングツール「JsRender」について連載しています。

前回までで「if文」と「for文」のご紹介は終わりました。
それだけ分かってればJsRenderはもう十分やっていけるのですが、JsRenderには他にもチョコチョコ機能がありまして、そういうのを使うと開発効率が向上しますので知っておいて損はありません。

今回は外部定義の取り込み機能「include」についてご紹介します。

includeを使わない場合

JsRenderではテンプレート部分を「scriptタグ」で定義するわけですが、一方のscriptタグの中に、別のscriptタグを取り込む機能が「incude」です。

要するに、

  • 一個のscriptタグに全部書くと複雑だったりサイズが大きすぎる。
  • 同一テンプレートを再利用したい。


こういう需要の為に使う機能です。

例を挙げてみましょう。

「メンバーの名前一覧を表出力する」という機能を想定します。
テーブル出力ですので「ヘッダー」と「ボディ」があります。
ただし、特殊な事情として「スクロール対策として10行置きにヘッダーを挿入したい」という
仕様があります。

画面としてはこんな感じ。



それを実現するソースが以下です。

<script id="jsRenderTmpl" type="text/x-jsrender">
★{{:title}}
<table>
{{for members}}

{{if seq % 10 == 0}}
 <tr>
  <td>名前</td>
 </tr>
{{/if}}

<tr>
 <td>・{{:name}}</td>
</tr>

{{/for}}

</table>
</script>

<script>
var data = {
  "title": "名前一覧",
  "members": [
      {"seq":0,"name": "Tacy1号"},
      {"seq":1,"name": "Tacy2号"},
      {"seq":2,"name": "Tacy3号"},
      {"seq":3,"name": "Tacy4号"},
      {"seq":4,"name": "Tacy5号"},
      {"seq":5,"name": "Tacy6号"},
      {"seq":6,"name": "Tacy7号"},
      {"seq":7,"name": "Tacy8号"},
      {"seq":8,"name": "Tacy9号"},
      {"seq":9,"name": "Tacy10号"},
      {"seq":10,"name": "Tacy11号"},
      {"seq":11,"name": "Tacy12号"},
      {"seq":11,"name": "Tacy13号"},
      {"seq":11,"name": "Tacy14号"},
      {"seq":11,"name": "Tacy15号"},
      {"seq":11,"name": "Tacy16号"},
      {"seq":12,"name": "Tacy17号"}
 ]
};

var template = $.templates("#jsRenderTmpl");

var htmlOutput = template.render(data);

$("#result").html(htmlOutput);
</script>

if文とfor文とテンプレHTMLが入り乱れてちゃってますよね?

まあ、このサンプルはカラムが「名前」しか無いので何とでもなりますが、
もっとカラムが増えると、段々と可読性が下がってきます。

そこで、includeの出番です。

includeを使う場合

上記のサンプルの可読性を向上させる為にincludeタグを使ってみましょう。
構造としては以下の3分割にしようと思います。


  • ベース部分を担当する親
  • ヘッダー部のテンプレート
  • ボディ部のテンプレート


これでそれぞれの性質、機能毎にscriptタグが切れますので、
「ちょっとヘッダーだけ修正したい」みたいな要望にも対応しやすいです。

その対応バージョンがこちら。

<script id="jsRenderTmpl" type="text/x-jsrender">
★{{:title}}
<table>
{{for members}}
{{if seq % 10 == 0}}
 {{include tmpl="#jsRenderTmpl_head"/}}
{{/if}}
{{include tmpl="#jsRenderTmpl_body"/}}
{{/for}}
</table>
</script>

<script id="jsRenderTmpl_head" type="text/x-jsrender">
 <tr>
  <td>名前</td>
 </tr>
</script>

<script id="jsRenderTmpl_body" type="text/x-jsrender">
 <tr>
  <td>・{{:name}}</td>
 </tr>
</script>

どうです?
ソースの総量は増えてしまいますけど、キチッと分割されているので見易くなったでしょう?

まあ、ちょっとしたことにイチイチincludeしていると逆に面倒なだけですので、
includeを使うかどうかは、実装者の判断ですね。

上手くやれば巨大な画面でも綺麗にコーディング出来るでしょう。

終わりに

次回はテンプレートに送り込むJSONタグが複雑な場合に活躍する機能「prop」についてご紹介します。

2014年10月27日月曜日

【JsRender】for文

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

現在はJavaScriptによる画面レンダリングツール「JsRender」について連載しています。
今回はif文に続きロジックの基本、for文です。

for文ではないもの

まず前置きですが、今までの例で使用してきましたように、
以下のようにJsRenderに配列を流し込みますと、それはループで処理されます。

<script id="jsRenderTmpl" type="text/x-jsrender">
<div>
{{:name}}
</div>
</script>

<script>
var data = [
{"name": "碇シンジ",sex:"male"},
{"name": "綾波レイ",sex:"female"},
{"name": "ゼルエル",sex:"unknown"}
];

var template = $.templates("#jsRenderTmpl");

var htmlOutput = template.render(data);

$("#result").html(htmlOutput);
</script>

ただ、これだと本当に何の芸も無いループに過ぎませんからね。
例えばループの中に更にループを持たせたいとか、ループとは別に通信の成功コードを持たせたいとか、ループ単品ではなくてヘッダー情報とセットでJSONを扱うとか、現実的なコード設計を考えますと、配列ではとても対応出来ません。

配列ダイレクトの使用は、まあ単なる動作確認程度の話。
実際の実装時は、仮に配列で十分なシチュエーションであったとしても、全実装をキッチリfor文型に統一して粒度を合わせた方が綺麗な実装になると思います。

for文

では、さっそく本題のfor文に入りましょう。

上のソースに近い状態でfor文構成にしますと、以下のようなサンプルになります。

<script id="jsRenderTmpl" type="text/x-jsrender">
★{{:title}}
{{for members}}
<div>
・{{:name}}
</div>
{{/for}}
</script>

<script>
var data = {
  "title": "for文サンプル一覧",
  "members": [
      {"name": "碇シンジ",sex:"male"},
      {"name": "綾波レイ",sex:"female"},
      {"name": "ゼルエル",sex:"unknown"}
 ]
};

var template = $.templates("#jsRenderTmpl");

var htmlOutput = template.render(data);

$("#result").html(htmlOutput);
</script>


「{{for members}}」「{{/for}}」でfor文になるわけです。

ちなみに、for文と言っても「for(var i=0;i<10;i++)」みたいな終了条件は設定出来ません。
流し込んだ配列を全件出力します。

「for(Map m : List<Map> list)」みたいな意味合いだと言えるでしょう。

if文との組み合わせ~偶数と奇数~

ここが一つの残念なお知らせなのですが、「偶数の行」「奇数の行」でテーブルの行を色分けする時に使うみたいな
for文の中でのその時の行数の読み分けする機能は存在しないのです。

この為、偶数/奇数を判断したい場合などは最初からJSONデータの中分岐に必要な情報を持たせておく必要があるわけです。

この場合、私だったら行数のカウントをJSONに持たせます。
そして、偶数/奇数の判断はJavaScriptの計算で実現します。

<script id="jsRenderTmpl" type="text/x-jsrender">
★{{:title}}
{{for members}}
<div>
・No.{{:no}}:{{:name}}:
{{if no % 2 == 0}}
  偶数行
{{else}}
  奇数行
{{/if}}
</div>
{{/for}}
</script>

<script>
var data = {
  "title": "for文サンプル一覧・if文と組みわせ",
  "members": [
      {no:"0","name": "あああああ"},
      {no:"1","name": "いいいいい"},
      {no:"2","name": "ううううう"},
      {no:"3","name": "えええええ"},
      {no:"4","name": "おおおおお"}
 ]
};

var template = $.templates("#jsRenderTmpl");

var htmlOutput = template.render(data);

$("#result").html(htmlOutput);
</script>


これで偶数行/奇数行の判断が出来ました。
後はスタイルシートの記述等を入れてしまえば色分けも完了です。

if文の説明になってしまいますが、このように「{{if no % 2 == 0}}」みたいな計算判定も出来ます。

こうやって簡易的なロジックも織り交ぜて実現していくのが現実的な実装と言えるでしょう。

終わりに

今回で「if文」と「for文」の使い方が分かりました。
ハッキリ言って、これだけ分かっていれば十分!!

基本的にテンプレートはシンプルに定義して、複雑な設定が必要な場合はAjax通信先のサーバ側など
別の所で頑張るべきですから……。

しかし、JsRenderには他にも便利機能がありますので、
次回以降はそれらについてご紹介していきたいと思います。

2014年10月8日水曜日

【JsRender】if文

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

現在はJavaScriptによる画面レンダリングツール「JsRender」について連載しています。
今回はロジックの基本中の基本、if文です。

if文(文字列一致)

「if」「else if」「else」は言語の基本中の基本かと思いますが、実は意外に不便することがあります。

例えばjspタグ。

「ifがあってもelseが無い」

elseを実現したい場合はif文ではなくて「when」とかを使わなければならないのですが、
それを知らないプログラマーが「ありゃ。elseは無いんだ。ならifだけで頑張ろう」とかってifだけで強引に書こうとしてグダグダのソースになる悲劇が多発しています。(泣)

今回は基本中の基本であるif文をチェックしていきましょう。

まずは基本から。
メンバーの一覧の性別をチェックして、男性か女性かチェックする機能を作ってみます。

それがこちら。

<div id="result"></div>

<script id="jsRenderTmpl" type="text/x-jsrender">
<div>
{{:name}}は
{{if sex == "male"}}
  男性
{{else sex == "female"}}
  女性
{{else}}
  性別不明
{{/if}}
です。
</div>
</script>

<script>
var data = [
{"name": "碇シンジ",sex:"male"},
{"name": "綾波レイ",sex:"female"},
{"name": "ゼルエル",sex:"unknown"}
];

var template = $.templates("#jsRenderTmpl");

var htmlOutput = template.render(data);

$("#result").html(htmlOutput);
</script>

この結果として、以下のように表示されます。



つまり、代表的なif-elseの構えで分岐が実現出来るわけですね。
whenとか使わなくて済みます。
分かり易くて助かりますね。

  • {{if sex == "male"}}
  • {{else sex == "female"}}
  • {{else}}
  • {{/if}}


if文(数値一致)

上は「{{if sex == "male"}}」と文字列一致で行いましたが、数字ならどうでしょう?

それもOKです。

普通に「{{if sex == 0}}」とでも書いて下さい。

<script id="jsRenderTmpl" type="text/x-jsrender">
<div>
{{:name}}は
{{if sex == 0}}
  男性
{{else sex == 1}}
  女性
{{else}}
  性別不明
{{/if}}
です。
</div>
</script>

<script>
var data = [
{"name": "碇シンジ",sex: 0},
{"name": "綾波レイ",sex: 1},
{"name": "ゼルエル",sex: 2}
];

var template = $.templates("#jsRenderTmpl");

var htmlOutput = template.render(data);

$("#result").html(htmlOutput);
</script>

ちなみに、JavaScriptは型の検証がいい加減ですから、


  • {{if sex == 0}}
  • {{if sex == "0"}}


このようにダブルクォート(")で括ろうと括るまいと同じように処理されます。

if文(数値大小比較)

では、数値の大小比較はどうでしょう?

これも普通に「<」「>」が通用します。

<script id="jsRenderTmpl" type="text/x-jsrender">
<div>
{{:name}}は
{{if 10 <= age && age <20 }}
  10台
{{else 20 <= age && age <30}}
  20台
{{else}}
  それ以外
{{/if}}
です。
</div>
</script>

<script>
var data = [
{"name": "碇シンジ",age: 14},
{"name": "綾波レイ",age: 29},
{"name": "ゼルエル",age: -1}
];

var template = $.templates("#jsRenderTmpl");

var htmlOutput = template.render(data);

$("#result").html(htmlOutput);
</script>

合わせて「AND条件は &&」、「OR条件は || 」で実現可能であることが分かりました。

boolean

ここまで来ると、どうやら直感で使うような普通の機能は全部備わっているということが分かってきました。
一応、「true」「false」といったboolean型の動作も確認してみましたが、こちらも何ら問題は起きないようです。

<script id="jsRenderTmpl" type="text/x-jsrender">
<div>
{{if flg}}
  {{:name}}
{{/if}}
</div>
</script>

<script>
var data = [
{"name": "碇シンジ",flg: true},
{"name": "綾波レイ",flg: false},
{"name": "ゼルエル",flg: true}
];

var template = $.templates("#jsRenderTmpl");

var htmlOutput = template.render(data);

$("#result").html(htmlOutput);
</script>


ロジック入り

極め付けはコレ。

if文の中にJavaScriptのロジックを入れてしまうという。

<script id="jsRenderTmpl" type="text/x-jsrender">
<div>
{{:name}}は
{{if age.substr(0,1) == 1 }}
  10台
{{else age.substr(0,1) == 2}}
  20台
{{else}}
  それ以外
{{/if}}
です。
</div>
</script>

<script>
var data = [
{"name": "碇シンジ",age: "14"},
{"name": "葛城ミサト",age: "29"},
{"name": "ゼルエル",age: "-1"}
];

var template = $.templates("#jsRenderTmpl");

var htmlOutput = template.render(data);

$("#result").html(htmlOutput);
</script>

「{{if age.substr(0,1) == 1 }}」みたいな記述も可能。
要するに、JavaScriptの機能がそのまま動くように作られているわけです。

ただし、JsRenderのif文にこういうロジックが入ってしまうとソースが複雑化してしまいます。

分岐が必要な場合はシンプルに記述出来るようJSON定義の設計を見直すことをオススメしますが、
諸々の事情で出来ない場合等は、こういう小技で乗り切るのもエンジニアの現場判断と言えるでしょう。

終わりに

次回は同じく基本中の基本、「for文」の書き方をご紹介します。

2014年10月3日金曜日

【JsRender】JSON定義

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

現在はJavaScriptによる画面レンダリングツール「JsRender」について連載しています。
今回のテーマはJSONです。

JSONデータ定義

さて、前回は単純な「ただ文字列を表示するだけ」という基本についてご紹介しました。

ここからif文やfor文など様々な機能に行きたいわけですが、その前にJSONの定義方法について検証したいと思います。

シンプル

一番シンプルなのが前回のこれ。

Scrpit

<script id="jsRenderTmpl" type="text/x-jsrender">
私の名前は{{:name}}です。
</script>

JSON

var data = {"name": "遠藤 太志郎"};

これで「私の名前は遠藤 太志郎です。」になります。

配列

次に配列に行ってみます。

Scrpit

<script id="jsRenderTmpl" type="text/x-jsrender">
<div>
私の名前は{{:name}}です。
<div>
</script>

JSON

var data = [
{"name": "遠藤 太志郎"},
{"name": "えんどう たしろう"},
{"name": "エンドウ タシロウ"}
];

結果がこちら。



ふ~む……。
どうやら導入データが配列になっていると、自動的にループでやってくれるようですね。

でもこれ、本当に単純なデータをベロベロ出すだけなら良いけど、
例えばゼロ件の時はメッセージを出すとか、僅かでも工夫が必要になると途端にデータ定義が不便になっちまうんですよ。

単純配列の出番は本当にシンプルな場合のみです。
まあ、配列にはこういう機能があるとだけ覚えておけば良いでしょう。


入れ子

JSON定義ってオブジェクト指向的な性質を持っていますから、JavaのクラスをJSONに置き換えるようなイメージで
定義することもできます。

こんなようなクラスがあるとしますと、

public class Person {

    /** 名前 */
    private String name;

    /** 年齢 */
    private int age;

}

JSONで書けばこうなります。

var data = [
var data = {person:{"name": "遠藤 太志郎",age:28}};
];

この場合、テンプレートはこうやって書く。

<script id="jsRenderTmpl" type="text/x-jsrender">
私の名前は{{:person.name}}です。年齢は{{:person.age}}です。
</script>

「.(ドット)」で連結すれば、JSONの階層構造を掘り進んで読んでくれるわけです。
これがJsRenderの良い所で、JavaでBEAN定義したクラスを丸ごとJSON化して叩き込むことが出来ますので、ロジックが非常に楽になるのです。
JavaScriptの機能がそのまま踏襲されています。

配列番号指定

次は「入れ子と配列の合体で番号指定」です。

以下みたいに入れ子構造の先が配列になっている場合です。

var data = {person:{"name": ["遠藤","太志郎"],age:28}};

nameの属性に「苗字」と「名前」が別々に入っていますね。
こんなデータ構造になってしまうのは余り良いサンプルとは言えませんが、
仮に「firstname」「familyname」と別々のフィールドに定義しますと、それだけでJSONもデータ量が増えてしまいます。

「転送データ量削減の為に定義を省略する」など、諸々の事情で配列でやらなきゃならないタイミングもあります。

こういう状況で、「配列のゼロ番目に入っている苗字部分だけを取り出したい」という需要があるとします。
その場合はこうやって記述すればOKです。

<script id="jsRenderTmpl" type="text/x-jsrender">
私の苗字は{{:person.name[0]}}です。年齢は{{:person.age}}です。
</script>

[0]で順番指定出来るわけですね。
痒い所まで手が届く便利設計になっています。

ちなみに「苗字+名前」としたい場合は「{{:person.name[0]}}{{:person.name[1]}}」と
単に続けて書けばOKです。

文字番号指定
最後に小ネタを。

JsRenderって、「文字列の何番目だけを出したい」という文字番号指定機能があるのです。

JSONデータをこれとします。

var data = [
var data = {person:{"name": "遠藤 太志郎",age:28}};
];

そしてこう書くと。

<script id="jsRenderTmpl" type="text/x-jsrender">
私の苗字は{{:person.name[0]}}{{:person.name[1]}}です。年齢は{{:person.age}}です。
</script>

これでもちゃんと「遠藤」ってなります。


  • {{:person.name[0]}}=遠
  • {{:person.name[1]}}=藤


この辺りもJsRenderの機能と言うより、JavaScriptの仕様なんですけどね。

JavaScriptには偶に変わった仕様が入っていることがあるのです。
配列と見間違えてバグの温床を化すのがオチなような仕様ですけれども、
テクニシャンな人は使ってみると良いでしょう。

終わりに

以上でデータ定義編は終わりです。
JavaScriptの標準に乗っ取った、直感的に理解出来る定義方法になっていると思います。

次回もJsRenderは続きます。

2014年9月30日火曜日

【JsRender】基本表示機能

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

本日よりJavaScriptによる画面レンダリングツール「JsRender」について本格的に連載を開始します。
最初の今日は導入編です。

ダウンロード

さて、まずは必要なJSファイルをゲットする所から始まります。

「JsRender」は単体でも機能するライブラリのようですが、普通はjQueryと組み合わせて使うものかと思います。
なので、本連載ではjQueryも併用することを前提で進めてまいります。

それぞれ、ダウンロード元は以下になりますので、各自ダウンロードして下さい。




取得したら、普通にHTMLのヘッダー部分でインポートしてあげればOK。
簡単ですね。

<head>
  <script src="./js/jquery-1.11.1.js"></script>
  <script src="./js/jsrender.js"></script>
</head>

表示箇所を定義

次に、HTML上のどこにJsRenderの結果を出力したいのかどうかを意味する場所を定義します。

DIVタグでIDを記述すれば良いでしょう。

<div id="result"></div>

「id="result"」と定義しました。この場所にJsRenderを使って文字列を流し込みます。

ちなみに、これはHTMLを作成する上では常識ですが「ID」とは画面上で一意にするのがルールです。

HTMLはいい加減な言語なので、IDが重複しようがタグを閉じ忘れようが、かなりテキトーに書いても
そこそこちゃんと表示されてしまいます。

しかし、本件の処理は「JavaScript」ですから、こっちはそういい加減に書くわけにはいきません。
ID属性は常に一意。
その他、可能な限りHTMLの記述ルールを守って実装して頂けますよう、お願い致します。

テンプレートを定義

では、この辺りからJsRenderならではの要素に入ります。
テンプレート定義です。

例えば、「私の名前は○○です。」という文章を表示したいと思います。
この○○の部分だけは動的に変更して、それ以外は固定値、というテンプレートにしたい。
そのテンプレートが以下になります。

<script id="jsRenderTmpl" type="text/x-jsrender">
私の名前は{{:name}}です。
</script>

これでもう何となくお察し頂けたかと思いますが、「{{:name}}」で、「画面上に文字を記述する」という
意味を持つJsRenderの定義なのです。

Strutsで言う所の以下みたい記述ものです。

<bean:write name="usrdata" property="name" />

もう一つの特徴が「type」です。普通であれば「type="text/javascript"」と書くところ、
JsRenderの場合は「type="text/x-jsrender"」となります。

「このスクリプトはJsRenderの記述だ」と宣言しているわけですね。

これにより、


  • scrpitタグで括っているのでHTMLには表示されない。
  • 「type="text/x-jsrender"」なので、JavaScriptとは認識されない。


この特性が得られて、純粋な外部定義のように扱うことが出来るのです。


JsRenderを実行
さて、上記でテンプレート定義が出来ました。
後はJavaScriptとしてJsRenderを実行するのみです。

そのスクリプトはこちら。

<script>
var data = {"name": "遠藤 太志郎"};

var template = $.templates("#theTmpl");

var htmlOutput = template.render(data);

$("#result").html(htmlOutput);
</script>

まず最初にJSONを読み込みます。

「var data = {"name": "遠藤 太志郎"};」

ここではサンプルなのでJSONは固定値ですが、本番ではこのJSONを非同期通信により
サーバから動的に持ってくることになります。
キー値としましては、上記のテンプレートと合わせて「name」としました。

JSONはAjaxとは切っても切れない基本要技術です。
ご存じ無い方はぜひ習得して下さい。

次に、テンプレートを読み込みます。

「var template = $.templates("#jsRenderTmpl");」

これはJsRender独自の記述ですね。
「type="text/x-jsrender"」のID「jsRenderTmpl」を指定してテンプレートを読み込みます。

そして次に、テンプレートとJSON定義を合体させて、描画する文字列を作成。
ここもJsRenderの機能です。

「var htmlOutput = template.render(data);」

最後に、jQueryの機能で描画して終わり。

「$("#result").html(htmlOutput);」

なので、JsRenderは描画する文字列を出力する機能に絞っており、
それを描画する機能はjQueryの標準機能で行うという関係にあるわけです。

機能毎の独立性が担保された良い設計だと思います。

ほら、簡単に表示されました。


まとめると以下のようなソースになっています。

<!DOCTYPE html>

<html>
<head>
  <script src="./js/jquery-1.11.1.js"></script>
  <script src="./js/jsrender.js"></script>
</head>
<body>

<div id="result"></div>

<script id="jsRenderTmpl" type="text/x-jsrender">
私の名前は{{:name}}です。
</script>

<script>
var data = {"name": "遠藤 太志郎"};

var template = $.templates("#jsRenderTmpl");

var htmlOutput = template.render(data);

$("#result").html(htmlOutput);
</script>

</body>
</html>

どってことは無い簡単なスクリプトで実現可能であることがお分かり頂けるでしょう。

終わりに

以上で、基本の基本。
「ただ描写するだけ」という機能についてのご紹介は終わりです。

ハッキリ言って、JsRenderが無くても描画は出来るって言えば出来るんですが、
「type="text/x-jsrender"」によって外部定義することにより、
ロジックとテンプレートを独立させることが出来るようになっています。

これはソースの可読性を高める為には大変効果的です。すばらしい!!

次回から順次、if文やfor文などの機能についてもご紹介していきます。

2014年9月17日水曜日

【JsRender】始めに

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

ここ最近は、クラウド基盤「Google App Engine(以下、GAE)」の連載していますが、
本日からは少し趣向を変えてAjax関連のお話に行こうと思います。

jsp禁止

さて、なぜ急にGAEからAjaxに話が触れたかと言いますと、こちらの回でご紹介しましたとおり、GAEというのはフルAjaxで開発するのがベストなのです。

jspを使うと画面がホワイトアウトしている時間が発生してしまいますからね。
まあ、機能を果たすという意味ではjspを使っても良いんですけど、やっぱり何秒もホワイトアウトしているのは心象が悪いでしょう。
私は断固としてAjaxを推したいと思います。

しかしですね、jspが使えないとなると、一体どうやって画面上の分岐を表現すれば良いのでしょう?
例えば、「検索結果がある時は一覧を表示する。無い時は赤文字でエラーメッセージを表示する」とか。

jspが使えればifタグで一発なのですが、使えないとなるとこれは厳しい。

そこをAjaxを使って動的に……。
って、もちろんやろうと思えば自前でAjaxを作って実現出来ますけれども、その度にロジックを組むのは効率悪過ぎです。

jspと比較しての生産性の低下が深刻です……。

そこで私が考えたのが、「AJaxでjspみたいなことが出来る便利なライブラリは無いの?」でした。
そう、Ajaxによるテンプレートエンジンです。

テンプレートエンジン

さて、Ajaxで困った時はjQueryに限ります。

十年ほど前ははAjaxライブラリが群雄割拠していましたが、今はjQueryが支配的になりました。
「何かAjaxで良い機能無いかな?」と思った時は、とりあえずjQueryから着手するのが一番効率的です。

そして私が探した所、ありました!!


  • jQuery Template


まさに期待通り、jspタグみたいな機能をAjaxでやる為のjQuery公式プラグインです。
jQuery公式なら安心ですね。早速導入を、と思いきや。

何と開発停止。(泣)




作り始めたは良いものの、途中で方針を変更して、まだ諦めずに頑張ると言いつつも、そのうち頓挫したというオチのようですね……。

残念な限りですが、その代わりに台頭してきたのが「JsRender」です。


JsRender

JsRenderはjQuery公式ではありませんが、jQuery Templateの後継のようなポジションにあるライブラリです。
公式サイトはこちらです。


公式サイトに解説も沢山載っていますので習得も捗ります。

フライングになりますが、例として『検索結果一覧の表示』のサンプルをご紹介すると、以下になります。

まず、HTMLにテンプレートとなるタグを書いて、

<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>

後はJavaScript部に「$("#shohinList").html($("#shohinListTemplate").render(json));」を一発書くだけでOK。
(json変数にはもちろんjsonのデータが入ります)

「{{for shohinList}}」を見ただけでも、何となくfor文でループしていることがお分かりになるでしょう。
直感的に使いやすい良いデザインのライブラリですね。

終わりに

このJsRender作戦はGAEに限ったことではありません。
私は別業務でAndroidの開発も行った事があるのですが、その時に「HTML5アプリ」と「ネイティブアプリ」の良い所取りをする「ハイブリット型」の設計を採用しました。

その時に、ネイティブ機能で整形したデータをHTMLに表示する時に使ったのも、この「JsRender」でした。

とても使い勝手の良いライブラリなのでぜひオススメです。

次回から各種機能について順次ご紹介していきます。

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年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関連の要望はこの辺りでフォロー完了です。

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