金曜日, 9月 04, 2009

【Google App Engine】 ついにキター! TaskQueueとXMPP このエントリーを含むはてなブックマーク



ついにきたぞ。

App Engine SDK 1.2.5 released for Python and Java, now with XMPP support


Task Queue API for Java

Python developers have been processing tasks offline using App Engine Task Queues since mid-June, but until now the feature was not available in the App Engine for Java SDK. The 1.2.5 SDK now includes support for creating Tasks and Queues in our Java runtime.


今週末は眠れなさそう。

木曜日, 9月 03, 2009

【Google App Engine】 datastore.Entityにおけるプロパティ変換について このエントリーを含むはてなブックマーク


Reflex ore 1.2.2におけるJSON変換

 今回はまず、Reflex Core 1.2.2の紹介からしたい。このバージョンから、JSONにおいてもハイフン、コロン、属性、テキストノードの表現ができるようになった。
 もともとJSONはプロパティ名と値がペアになったシンプルな構造表現しかできないが、XMLからJSONに変換する際には、名前空間や属性、テキストノードといったものをムリクリ表現しなければならない。先日、ココでお伝えしたとおり、実は1.2.1でも属性表現ができるようになっていたのだが、これには一部不具合があったので修正したのと、名前空間やテキストノードなどにも対応したので、1.2.2として公開させていただくことにした。

1. SONでは、要素名の"-"が"__"に変換されるため"type_str-ing"は、"type_str__ing"となります。
2. XMLの名前空間で用いる”:”は、JSONでは"___"に変換されるため、"xml:atom"は、"xml___atom"となります。
3. XMLの属性は、JSONでは"要素名____属性名"プロパティとなるため、"attr1"は、JSONでは"type_str__ing____attr1"プロパティとして表現されます。
4. XMLのテキストノードは、JSONでは、"_______text"プロパティに変換されます。


datastore.Entityのプロパティ変換について

 次にGAEのdatastore.Entityのプロパティ変換の話。
 GAEには、低レベルAPIが用意されているが、これを使うと検索結果はdatastore.Entityクラスのなかのプロパティと値という形で格納される。このままでは不便なので、JDOのようにJavaオブジェクトに変換する必要があるが、GAEのAPIでは変換APIが用意されていないので、私たちは自作のものを利用している次第である。(ReflexGaeのなかのEntityConverter)
 ところが、datastore.Entityのプロパティ変換は以下のような癖があるため、注意しなければならない。Javaオブジェクトとプロパティ変換という意味ではReflexと同じように一筋縄ではいかないようで、ここでもムリクリやっているのがよくわかる。

 1)元のクラスの項目名(JavaのField名)に"$"があると、datastore.Entityのプロパティでは"24"に変換される。
 2)元のクラスの項目名の先頭に"_"があると、datastore.Entityのプロパティでは""に変換される。項目の間の"_"はそのまま。
   o 例)modelの項目名「_$xml$lang」は、datastore.Entityのプロパティ名が「24xml24lang」となる。
    + -> "$"と先頭の"_"について、プロパティ名を変換して読む必要がある。
 3)プロパティ名が重複すると、名前の末尾に「"_"+番号」が付加される。
   o アルファベットの昇順で、番号なし、"_0"、"_1"、・・・という順番のようである。
   o 例)modelクラスに「test」「_test」「__test」という3個の項目があると、Entityクラスのプロパティ名は順に「test_1」「test_0」「test」となる。
     (abc...よりも、"_"の方が先)


 1)および2)に対応したEntityConverterは、ReflexGae 0.9.6に含めているが、3)については未だ対応していないのでご注意願いたい。これは今のところ、modelの項目名に、「test」「_test」のように、頭にアンダースコアがあり、かつ、同じ名前をつけないようなルールで対応するしかない。 

ReflexGae 0.9.6

 * KeyUtils : 親キーのnameを指定できるようにした
 * EntityConverter : Entityのプロパティ名を指定する時、"$"を"24"に、先頭の"_"を""に変換するよう修正


火曜日, 8月 25, 2009

【HTML5】 XMLシリアライゼーションの意味 このエントリーを含むはてなブックマーク


続・XMLなのかHTMLなのかそれが問題だ

 久しぶりにHTML5とXMLのネタ。私は1年前のこの記事において、HTML5で作成されたコンテンツが実質的にWelformedでなければ、Web世界の時計が大きく逆戻りすることになる、と主張した。ところが、あらためて最近のWeb事情、特にHTML5の記事を調査してみると、「むしろwelformedでない方がよい」といった感じで書かれているものが多く見つかるようになってきていて大変驚いた。今回は、HTML5のXML対応について、とりわけXHTML5とは何なのかについて述べてみたい。

“Misunderstanding Markup” 日本語訳

(訳:ぼくはXHTML構文が好きだ。なぜなら、そうやって学んだから。小文字で書くこと、引用符で括ること、brやimgにスラッシュを書くことに慣れているんだ。この書き方は、とても気持ちがいい。オバルチンを飲みながら、テレビでEvil Deadを見るような感じさ。でも、君はそうじゃないかもしれない。まるで叫んでるように見える大文字のタグや、スラッシュなしのimg, br要素、属性の省略表記が好きかもしれない。HTML 5だと、これらはぼくらの好きなように書けるんだ。“pave the cowpaths”っていう原則のおかげで、書く人のおまかせになるんだ。「お気に召すまま、お好きなように、好きなだけ、どうにでも」ってね。)



続・セマンティック・ウェブにXMLはいらない?

 セマンティック・ウェブは、ユニバーサルな情報空間を目指したバーナーズ=リーの考えを受けて、強靱で拡張性があり、適応性のある情報の基盤としてウェブを発展させるための目標の一つ。ウェブの目指すもの:W3Cの目標
 これまで、利用者が、ウェブ上のリソースを最大限に活用できるようなソフトウェア環境を構築するためには、XHTML化とRDFaなどのメタデータを使ったウェブの注釈が重要であるとされてきた。しかし、RDFaに代表される厳格なXMLベースのセマンティック・ウェブのアプローチはなかなか広まらず、最近では前述の記事のような「お気に召すまま」が流行っていることもあって、そのきざしすら感じられなくなってきているのが現状のようである。つまり、「貴族になって楽をしよう」で述べたボヘミアンの完全勝利。それは、MicroformatやHTML5のように、Webが実質的にXML以外の様々な表現や実装技術によって成り立っており、そもそもXMLで厳格にすべてを表現すること自体に無理があるのではないかという見方が主流となってきている背景がある。セマンティック・ウェブの目的だけ考えれば、データを取り出せて意味の疎通ができればよいのだから、必ずしもXMLでWelformedでなくてもよい。HTMLでは、どう処理するのかを規定されることに重きを置き、microformatsはXMLさえ前提にしていない。そう考えていくと、welformedかどうかは、結局のところ、XMLという1つの実装規約の範疇にすぎないという狭い議論に落とし込まれていく。関連記事

XHTML5って何?

 一方で、HTMLをデータとして処理したいという要求が大きいのも事実である。Reflex iTextは、テンプレートとしてHTMLを用いるが、これがXMLでなかったらパース処理に大変な苦労を強いられることになる。今後標準となると思われるHTML5についても同様で、テンプレートをHTML5にバージョンアップする際には、できることならXHTML表現を使いたいところである。
 そこで、XHTML5の明確な定義を調べることにしたのだが、これがとても曖昧で気持ち悪いのだ。その原因は、XHTML5のスキーマがないということ、それから、XHTML1.xと同じ名前空間を使っているということの2つである。こうなってくると、モジュール化して他のモジュールを自由に取り込めないのではないかとか、極端な話、XHTML1.xのボキャブラリ以外使えないのではないかとか不安にもなってくる。
 ちなみに、私と同じような疑問をもっている方もいるので抜粋して紹介したい。去年の記事なんでちょっと古いが、開発者の率直な意見をまとめてみた より。


what-wgが「HTML5はモジュールに分割せずモノリシックな規格を作る」って決めてしまったからなぁ…

<video src="video.ogv" xmlns:cc=" http://creativecommons.org/ns#"
rel="cc:license" resource=" http://creativecommons.org/licenses/by/2.1/jp/"/>
XHTML 5でこういうことができたらいいのになあ。video要素とRDFaを両方使うとオレオレXHTMLになってしまう。せっかくスキーマを取り払ったんだから、モジュール化して他のモジュールも自由に取り込めるような仕組みがいいと思う。

validatorにかけると
Error: Attribute rel not allowed on XHTML element video at this point.
などのエラーを返される。@relや@resourceをvideo要素に書くとHTML 5準拠にはならないんだよね。ブラウザでの表示やRDFの抽出はできるだろうから実際あまり問題はなさそうだけど、そのXHTML文書が準拠する仕様はどこにも存在しないってのは、ブラウザの独自拡張時代を思い出させてあまり好きではない。HTML 6あたりで「やっぱりCSSみたくモジュール化しよう」ってことになり、Multimedia ModuleとかMetainformation Attribute Moduleとかが定義されて、制作者はいろいろなモジュールの中から好きなものを選んで組み合わせて使えるようになったりしないかなあ。


HTML5がモジュール化しない理由とXML名前空間の試み

HTML5はモジュール化しないの?で述べられているように、HTML5はモジュール化しない。それは、HTML5から見れば、XMLは単なるシリアライズ表現の一つと位置づけているように、あくまでHTMLがもつ機能だけに焦点をあてた結果であるといえる。XHTML5はXMLに見えてXMLではない、というのは言いすぎかもしれないが、少なくとも、名前空間とスキーマを充実させてモジュール化し、必要に応じて組み合わせて利用するといった、ユニバーサルな情報空間構想の元に作られたものではないことは明らかである。XMLの生みの親である村田さんもこの構想について以下のように述べられている。しかし、2005年の時点ではまだ名前空間の試みがうまくいっていないことを吐露されてもいる。

お仕着せスキーマと誂えスキーマ

今後の方向は、お仕着せスキーマにちょっと手直しをして、従来は誂えスキーマでしか得られなかったような利点を
得ることだろう。名前空間も、もともとそのための試みで
あった(残念ながらうまく行っていない)。
XHTMLがモジュール化されたもの、
RELAX NGがスキーマのモジュール化を重視しているのも、
NVDLが名前空間による分割検証を提供しているのも、すべて
この方向を目指す試みである。あと10年もすれば、
お仕着せスキーマにちょっと手直しをするという方法が
有効かどうか結論が出るのだろう。


XMLシリアライゼーションの意味

XHTML5の疑問を解決すべく、メーリングリストに聞いてみることにした。
回答の要点は以下のとおり。

1) XHTML5の名前空間はXHTML1.1と同じものを使うがXHTML1.xとは区別される。ただし、両者を判別する方法は今のところない
2) HTML syntaxとXHTML syntaxは、HTML5 DOMのrepresentationであり、同じ要素をもつ

詳細は以下のとおり。
議論がずれることを恐れたので深く突っ込まなかったが、名前空間の意味と運用について、これほど解釈が違う人がいるとは思わなかった。


XHTML 2が打ち切りになったのは皆さんご存知だと思うのですが、以下の記事のなかで、XHTMLをHTMLのXMLシリアライゼーションとみなす、といった説明があります。

「W3CはXHTMLをHTMLのXMLシリアライゼーション(XML形式への変換)と見なしている。HTML
5仕様にXMLシリアライゼーションを含め,引き続きHTML WGで検討していく。」

http://itpro.nikkeibp.co.jp/article/NEWS/20090703/333143/

名前空間がXHTML1.xのままだとHTML5のタグを使えないものがあるので、完全なシリアライゼーションはできないと私は思います。

単純に、HTML5のままで、welformedなものをXHTML5と呼ぶと解釈したとしても、welformedかどうかを示すヘッダーなり何かがないと不都合です。(つまりXML宣言なんですけど)
その場合、名前空間はXHTML1.x以外の何かになると思われるのですが、最新のDraftにも新しい名前空間は見当たりません。

この話は以下にも書いているように、去年からずっと気になっているところなんですが、いまだにモヤモヤしています。

http://www.virtual-tech.net/blog/2008/08/html5-htmlxml.html
http://www.virtual-tech.net/blog/2008/09/html5-xml.html

また、弊社のReflex iTextのテンプレートの文法をHTML5にあわせるかどうか考え中なんですが、このテンプレートはXMLが前提なんでどうしたもんかと。
つまり、HTML5のタグを使いたいのだけど、welformedなHTML5であることを知ることができるのか、あるいは、HTMLとしてパースしないといけないのか、という点がはっきりしないのです。

このあたりで何かわかる方がいれば、ご意見を伺いたいのですが。

よろしくお願いします。


回答1


Y氏)
XML宣言が必要であることと、名前空間がこれまでのXHTMLと別になることの関連、またそれが「完全なシリアライゼーションにならない」にどう繋がるのかがよく分からないのですが、そこは置いといて。

HTML5で言うところの「XHTML」は、「well-formedなHTML5文書を、application/xhtml+xml または
application/xml で送出したもの」になります。
これが text/html で送出されている場合、それはXHTML5ではなく、HTML5になります。
「どう書いたか」ではなく「どう扱うのか」によって、HTMLとXHTMLという区別がつけられるわけです。

> また、弊社のReflex iTextのテンプレートの文法をHTML5にあわせるかどうか考え中なんですが、このテンプレートはXMLが前提なんでどうしたもんかと。
> つまり、HTML5のタグを使いたいのだけど、welformedなHTML5であることを知ることができるのか、あるいは、HTMLとしてパースしないといけ ないのか、という点がはっきりしないのです。

XMLのどの機能を利用しているかによって答えが変わるのですが、たとえば「
」のように空要素のXML的表現は、HTML構文でも利用できるように定義されています。

=====
私)

早速のご回答、ありがとうございます。

>具体的に何が利用できないか、教えていただけますか?
や
などは、XHTML1.0や1.1では使えないと思います。
使うためには別の名前空間が必要ですよね。

> HTML5で言うところの「XHTML」は、「well-formedなHTML5文書を、application/xhtml+xml または
> application/xml で送出したもの」になります。
> これが text/html で送出されている場合、それはXHTML5ではなく、HTML5になります。
> 「どう書いたか」ではなく「どう扱うのか」によって、HTMLとXHTMLという区別がつけられるわけです。
なるほど。たとえ中身がXMLであっても、「どう扱うのか」ということを、content-typeで区別しましょう、という話ですね。わかります。
では、application/xml
で送り出したXHTML5とは具体的には何になるのでしょうか。welformedなHTML5なのか、XHTML1.1なのか、あるいは、両方が可能なのか。それが知りたいのです。

それにこだわっているのは、要は、XMLパーサしかもっていない当方の実装上の都合だけなんですが、HTML5互換のXMLが扱えるか、もし、application/xmlで送り出すことで、welformedなHTML5を縛れるとすれば、簡単に目的を果たせて嬉しいんです。
=======
S氏)

ちょうど仕様を読み返しているところでしたので、ご質問を受けて、XHTMLに関して言及しているところも軽く読み直してみました。
正しく理解できているかどうかはわかりませんが・・・(^^;
もし間違いがあったら、皆さんご指摘お願いします。

で、ご質問の意図は以下のようなものかと捉えたのですが、いかがでしょうか?

1. ファイル形式がXHTML5か、XHTML1.xかを見分ける手段がないように思えるが、その通りか?
2. 名前空間がXHTML1.xと同じく「http://www.w3.org/1999/xhtml」のままでは、HTML5のボキャブラリを扱えないのではないか?

こういった意図のご質問だと仮定して、僕の意見を述べさせて頂きますと、

1に関してはその通りだと思います。名前空間も同じですし、配信時のContent-Typeも特殊なものではありませんし。

2に関しては、ボキャブラリを扱えるかどうかは実行系に依存するのではないでしょうか。XHTML1.0しか扱えないブラウザは新要素を取り扱えないでしょうし、今後出てくる新しいブラウザはそれらを扱えるようになる、と。

なので、僕はXHTML5というのは、竹嵜さんのおっしゃった

> 単純に、HTML5のままで、welformedなものをXHTML5と呼ぶ

と言うことで良いのかな、と思っています。
(もちろん、文字エンコーディングの指定方法とか、細かい部分は違うにせよ)

ちなみに、仕様(http://www.w3.org/TR/html5/introduction.html#relationship-to-xhtml-1.x)では

"This specification is intended to replace XHTML 1.0 as the normative
definition of the XML serialization of the HTML vocabulary"

意訳:この仕様(HTML5)は、HTMLボキャブラリをXMLシリアライゼーションするための標準定義である、XHTML 1.0の置き換えとして意図されている

と述べられてます。

その上で、
> また、弊社のReflex iTextのテンプレートの文法をHTML5にあわせるかどうか考え中なんですが、このテンプレートはXMLが前提なんでどうしたもんかと。

ちょっと、この部分の問題意識がとらえきれていないので、役に立つ意見になっているかどうかはわかりませんが・・・
何かの参考になれば幸いです。

=====
私)
質問の意図をまとめていただきありがとうございます。まったくそのとおりです。
ご回答内容で、だいぶ見えてきたのですが、新たな疑問もうかんでいます。将来的に、XHTML5の名前空間って何になるのだろう?、と。

>> 単純に、HTML5のままで、welformedなものをXHTML5と呼ぶ
> と言うことで良いのかな、と思っています。

> 2. 名前空間がXHTML1.xと同じく「http://www.w3.org/1999/xhtml」のままでは、HTML5のボキャブラリを扱えないのではないか?
>ボキャブラリを扱えるかどうかは実行系に依存するのではないでしょうか。

ということは、XHTML5は実質的に当面はXHTML1.xと同じ名前空間を使うということになりますね。XMLで新たなボキャブラリを追加する際に、名前空間を変えるべきかどうかは設計者によるといわれていますが、HTML5の要素が加わったXHTMLと、これまでのものとを区別できないのは、やはり不便な感があります。ああ、モヤモヤ。

http://www.ibm.com/developerworks/jp/xml/library/x-namcar/

「最終的には、XHTML 1.0のすべての範囲に1つの名前空間を使用する新たな仕様を発行することで、XHTMLワーキング・グループは軌道修正を行ないました。この教訓から多くを学ぶべきです。命名された物の間に本当に違いがある場合にのみ、XML名前空間を区別すべきでしょう。

残念ながら、物事に白黒をはっきりと付けられることは滅多にありません。新しいバージョンのボキャブラリが、新たな要素を追加するのはよくあることです。前のバージョンから持ち越された要素の意味は変更されていないかも知れないので、名前空間の変更は不適切であると思うかも知れません。しかし仮に、元の同じ名前空間を使用するとしたならば、新たなボキャブラリに追加された要素を元の名前空間内に設置するのも不適切であると思うでしょう。また、新たな要素のみに違う名前空間を使用するのは、とても賢明な選択とは言えません。結局、ボキャブラリで名前空間を改変するかどうかを判断する(自分自身の)判断力が必要です。」


回答2
N氏)

> 名前空間がXHTML1.xのままだとHTML5のタグを使えないものがあるので、完全なシリアライゼーションはできないと私は思います。
> また、弊社のReflex iTextのテンプレートの文法をHTML5にあわせるかどうか

誤解です。

おそらく、XMLの「名前空間」というのは要素のコレクションとその意味を規定するものである、とお考えなのではないでしょうか。

「XML名前空間の簡単な説明」で説明されているのですが、
http://www.kanzaki.com/docs/sw/names.html
実際には、「要素の分類・同定」以上の機能はありません。

そして、XHTML 1.xの http://www.w3.org/1999/xhtml という名前空間は、
「XHTML 1.xが主に使ってきた名前空間」であるだけで、
「XHTML 1.x だけの名前空間」ではありません。
実際、そのURLを開いてみると、いくつか規格がリストアップされています。

で、この中にHTML5が加わることが何を意味するかというと、例えば、
「XHTML1.0のp要素と、HTML5のp要素が、XMLとして同じ意味を持つ」
ということになります。
また、ご指摘のXHTML1.0になくて、HTML5にはあるsection要素等ですと、
名前空間を考えるまでもなく、XHTML1.0にはないので、ここでは問題ありません。

XHTML 2.0の場合は、XHTML 1.0から要素の意味を変更しようとしました。
その場合は同じ名前空間にあると区別ができないので、別の空間を用意することになります。
つまり、XHTML 1.0のp要素と、XHTML 2.0のp要素は別物、ということになります。

XMLだとちょっと考えづらくなってしまいますが、
CのAPIを想像するとわかりやすいのではないでしょうか。

XHTML 1.0にxhtml_foo(int a)という関数がありました。
XHTML 5.0では互換性を保つように作ったので名前はそのままにしました。
XHTML 2.0では引数aをlong型に変えました。
これをそのまま既存のライブラリと強引にリンクさせようとすると落っこちます。
なので、xhtml2_foo(long a)という名前に変えるわけです。

そんなわけなので、
> HTML5のボキャブラリをXHTML1.xに追加するという明確な定義ってありますかね?
「追加」する必要はありません、そのような機能は名前空間は持っていないんですよ。

> 考え中なんですが、このテンプレートはXMLが前提なんでどうしたもんかと。
> つまり、HTML5のタグを使いたいのだけど、welformedなHTML5であることを知ることができるのか、
> あるいは、HTMLとしてパースしないといけないのか、という点がはっきりしないのです。

これはConformance Requestにあるんですが、
http://dev.w3.org/html5/spec/Overview.html#conformance-requirements
XHTML syntaxのみという選択肢があります。
なので「XHTML syntaxしかサポートしない」と言ってしまえばそれでおしまいです。
HTML syntaxの文書はtext/htmlで送られることになっていますから、
これは非サポート、ということですね。
text/html以外は全てXHTML syntaxです。

さて、懸案のXHTML 1.xとの区別ですが、区別する必要はありません。
全てHTML5として処理してください、名前空間が同じというのはそういうことです。
もしもこれで問題が生じるならばそれはHTML5のバグですから、報告なさるのがよろしいかと思います。
=========
私)
ご丁寧なご指摘ありがとうございます。

>> 名前空間がXHTML1.xのままだとHTML5のタグを使えないものがあるので、完全なシリアライゼーションはできないと私は思います。

これはたしかに誤解ですね。XHTML1.x上位互換のXHTML5が定義されていて、名前空間がhttp://www.w3.org/1999/xhtml
であるという考え方をすれば、シリアライゼーションは可能だと思います。ただ、AtomにしてもRSSにしても、大きなバージョン変化があった際には、名前空間も変わるのが一般的だと思うので、下位互換性という理由があるにせよ、名前空間とはそういうものですというのは言いすぎのような気もしています。機能的な議論はあるのですが、実際の運用のところは、先ほどのメールの添付(http://www.ibm.com/developerworks/jp/xml/library/x-namcar/)のように、ケースに応じていろいろなんだと思います。
また、先ほどのメールにも書きましたが、そもそもXHTML5の定義って何なの?という根本的なところが私のなかで曖昧のまま残っていて、ドキュメントのなかにXHTML1.x以上の定義が見つけられなかったので、それ以上も以下もないという発想が元になったのも事実です。
新しいボキャブラリを含むXHTML5を定義したうえで同じ名前空間を使うのか、単なるXHTML1.1なのか、そこが知りたいポイントなのですが、まだしっくりきていません。

>そんなわけなので、
>> HTML5のボキャブラリをXHTML1.xに追加するという明確な定義ってありますかね?
>「追加」する必要はありません、そのような機能は名前空間は持っていないんですよ。

これは、名前空間の機能というより、XHTML5の定義というふうに解釈していただきたいですね。繰り返し恐縮ですが、XHTML5は、HTML5のボキャブラリをXHTML1.xに追加するような定義になっていますかね。(名前空間の話はもう忘れてください。純粋な定義の確認です。)
もしそうだとしたら、ドキュメントの具体的な箇所を教えていただけないでしょうか。白石さんからいただいた参照を見て、私もそうなんだろうなとは思っていますが、念のため。

>ちなみに、仕様(http://www.w3.org/TR/html5/introduction.html#relationship-to-xhtml-1.x)では

>"This specification is intended to replace XHTML 1.0 as the normative
>definition of the XML serialization of the HTML vocabulary"

>意訳:この仕様(HTML5)は、HTMLボキャブラリをXMLシリアライゼーションするための標準定義で>ある、XHTML 1.0の置き換えとして意図されている

>と述べられてます。
=======
> >> 名前空間がXHTML1.xのままだとHTML5のタグを使えないものがあるので、完全なシリアライゼーションはできないと私は思います。
>
> これはたしかに誤解ですね。XHTML1.x上位互換のXHTML5が定義されていて、名前空間がhttp://www.w3.org/1999/xhtml
> であるという考え方をすれば、シリアライゼーションは可能だと思います。

> ただ、AtomにしてもRSSにしても、大きなバージョン変化があった際には、

これらはHTMLとは事情が違うように思います。
RSSは0.9, 0.91, 1.0, 2.0, 0.92 全て互換性がありませんでした。
なので、名前空間が異なるのは当然でしょう。

Atomも上記のRSS達とは違うので名前空間が異なるのは必然です。
また、Atom 0.3 から 1.0 へもdraftから正式版への変更なので、変えるのは当然でしょう。
が、Atomの改定版が出た時にはHTML5と同じ悩みを持つことになるでしょう。
Atomの事情はよく知らないので想像になりますが、Atomは関連規格が多く、
それらの多くがAtomのnamespaceを指定している以上、変えられないのではないかとは想像しています。

> 下位互換性という理由があるにせよ、名前空間とはそういうものですというのは言いすぎのような気もしています。

下位互換性は名前空間を同じにするかどうか考える際の材料の一つになりますが、
実際にXMLの名前空間を見るときには考慮されません。

XMLの名前空間が持っているのは、
* 名前空間が同じ→同じ名前の要素は同じもの
* 名前空間が違う→全ての要素は全くの別物
ということだけだということです。
そして、ここで同じものならばXMLのレイヤーにおいては区別する手段を持たないということです。
(もちろんversion属性を追加してそこでもっと上のレイヤーで区別することは可能ですが)

これをふまえたうえで、名前空間を同じにするか別にするかは仕様策定者に委ねられているわけです。

> 機能的な議論はあるのですが、実際の運用のところは、先ほどのメールの添付
> (http://www.ibm.com/developerworks/jp/xml/library/x-namcar/)
> のように、ケースに応じていろいろなんだと思います。

うーん、ちょっとこの記事、もっといえば特に
> XHTMLワーキング・グループは、それぞれのXHTML DTDに対応する3つの別々の名前空間を
> 使用することを決定しました
のXHTML WGの人々はちょっと神経質すぎるように感じます。

名前空間はXMLのレイヤーにおいて同じ名前の要素を別物扱いさせる、ただそれだけの効果しかありません。
もっと具体的にいえば、XMLパーサが同じ名前の要素なのに別物扱いしてしまう、という意味になります。
XMLの上に構築された多くの仕様では、ここで別物扱いされると上のレイヤーで同一視することはなかなか困難な作業になります。

実際にその仕様が勧告され、その仕様に沿った文書が、過去の当該仕様に沿った文書や他の関連仕様とそれに沿った文書、
その仕様に沿ったアプリケーションや過去の仕様、関連仕様に沿ったアプリケーション、などなど、と協調して動こうという時にどう振舞ってほしいかを考えれば、おのずと答えは見えてくるのではないでしょうか。

Atomの方が例としてシンプルなのでこちらで話しますと、例えばAtom 2.0で名前区間を変えたとすると、既存のアプリケーションや関連仕様は全てバージョンアップしない限りAtom 2.0の文書を無視するわけです。
この壁を乗り越えるにはよっぽど派手な花火がないと難しいように思えます。

> また、先ほどのメールにも書きましたが、そもそもXHTML5の定義って何なの?という根本的なところが私のなかで曖昧のまま残っていて、

HTML5は今までのようにタグの寄り集まった文法ではなく、DOM (Document Object Model) で定義されています。
つまり、ある構造をもった文書を、ブラウザやその他のアプリケーションがどう表示・処理するかと、
そのの構造の文書どうシリアライズ―ありていにいえば文字列にダンプするかを定めているわけです。
で、ダンプの仕方はHTML syntaxとXHTML syntaxがある。
この点において、「HTML5」とは言いつつも、今までのSGMLの子孫であったHTMLやXML一族のXHTMLとは一線を画しています。

> ドキュメントのなかにXHTML1.x以上の定義が見つけられなかったので、
> それ以上も以下もないという発想が元になったのも事実です。

仕様上は以上でも以下でもありませんね。
同じ名前空間の中にいる兄弟と言ったところでしょうか。
で、たまたま(実際はもちろん意図的なんですが) (X)HTML5 は XHTML 1.x の要素集合をおおむね包含しているだけです。

> 新しいボキャブラリを含むXHTML5を定義したうえで同じ名前空間を使うのか、

「新しいボキャブラリ」をここでどのような意味で使っているのかが問題ではありますが、
推測するに、この認識が正解であるように思います。
で、
Q: 別物のはずなのになぜ同じ空間を用いるの?
A: 互換性のため
Q: 互換性のためなら同じ空間を使ってもいいの?
A: よい、だって別にしたからXHTML 2 は死んじゃったんだもの
Q: 同じ空間だと何か問題が起きるんじゃない?
A: 何も問題が起きないように頑張る
と、こうなるわけです。

どうも HTML 5 WG はそれなりに迷った末に同じ空間を用いることを決定したようですが、
現状の膨大なHTML/XHTMLの資産や、HTML5以前に行われた数々の試みがどうなったかを考えると、同じ空間をもちいるか、黙殺されるか、の二択でしかなかったと思います。

> 単なるXHTML1.1なのか、そこが知りたいポイントなのですが、まだしっくりきていません。

XHTML 1.1 は DOCTYPE 等一定の要件を定義していますが、XHTML5 はそれから外れています。
よって、XHTML5 は XHTML 1.1 ではありません。

> >そんなわけなので、
> >> HTML5のボキャブラリをXHTML1.xに追加するという明確な定義ってありますかね?
> >「追加」する必要はありません、そのような機能は名前空間は持っていないんですよ。
>
> これは、名前空間の機能というより、XHTML5の定義というふうに解釈していただきたいですね。繰り返し恐縮ですが、XHTML5は、HTML5のボキャブラリをXHTML1.xに追加するような定義になっていますかね。(名前空間の話はもう忘れてください。純粋な定義の確認です。)

XHTML 1.x と XHTML 5 は別の規格ですね。
XHTML 5 に沿ったアプリケーションが XHTML 1.1 の文書を解釈できるのは「たまたま」です。
もちろん、その「たまたま」を実現するために、名前空間を同じにするだけでなく、
各種の注釈で既存UAの動作を解説したりと、HTML 5 の策定者達が努力していることは言うまでもありません。
(もちろん、XML的には同じ名前空間の同じ要素を扱っているのだから互換性が期待されるわけですが)

=======
1.7 HTML vs XHTML
http://dev.w3.org/html5/spec/Overview.html#html-vs-xhtml
が直接の参照先かと思います.正確にはDOMもrepresentationの一つです.

また,根底部分の知識として,Architecture of the World Wide Webも一読の価値があるかと思います.
http://www.w3.org/TR/webarch/

======
> 1.7 HTML vs XHTML
> http://dev.w3.org/html5/spec/Overview.html#html-vs-xhtml
> が直接の参照先かと思います.

HTML syntax と XHTML syntax が the DOM に対して用意されている、というのはそこですね。

2.2.1 Dependencies
「The DOM is not just an API; the conformance criteria of HTML
implementations are defined, in this specification, in terms of
operations on the DOM.」
http://www.whatwg.org/specs/web-apps/current-work/#dependencies
で、「DOMで定義されている」というのはこの辺の話です。


月曜日, 8月 24, 2009

【Google App Engine】 Entityとトランザクション3 このエントリーを含むはてなブックマーク


 Scaleするかどうか、それが問題だで主張したとおり、クラウドではスケールしなければ意味がない。以下は、GAEで大規模な処理をやって、特にトランザクション処理で悪戦苦闘した際のメモである。本日の発表資料=>ココにも含まれている。

大量データをINSERTできない件について

 先日、Entityとトランザクション2において、Ownedな関連を使ったUpdateサンプルを紹介したわけだが、実はこれ、大量データをINSERTすると急激に遅くなるという問題を含んでいることがわかった。実際にテストしてみたところ、15000件を超えた時点でタイムアウトが頻発、15300件からはとうとう1件も登録できなくなってしまった。

com.google.apphosting.runtime.HardDeadlineExceededError: This request (c1fe232d20adabba) started at 2009/08/11 07:12:31.975 UTC and was still executing at 2009/08/11 07:13:05.802 UTC.


 原因としては、1)インデックス作成における負荷、2)所有関係を作成する負荷、が考えられるが、3)JDOのトランザクション処理自体に問題がある可能性もあるので、その場合はLow Level APIの利用も視野に入れなければならないと考えた。

トランザクション処理の必要性

 トランザクション処理でここまで嵌ってしまうと、ACIDじゃなくて別のよい方法はないものかという考えに傾いていく。最近よくBASEという考え方が紹介されていて、ACIDが大変なんで、BASEでいいんじゃないかと思うこともある。あるいは、エラー忘却型コンピューティングとか、BASEの1種になるかもしれないが、Saga(ロングトランザクション)モデルといった補正トランザクションなどを利用しようという考え方もある。Sagaは、Webサービスが密に結合しているようなシステムにおいて過去多く見られた。最近のRESTfulなシステムへの導入では、ステートレスになじまないことでやや否定的だが、やろうと思えば可能だろう。(参考:RESTとトランザクション?)
 しかし、ロングトランザクションなどのACIDではない仕組みに関しては、補正失敗のケースも考慮しておかないと運用面で支障をきたすことになるので要注意である。
 私は以前作ったWebサービス連携のエラーリカバリーで酷い目にあった経験があるので、この手のものをACIDで組まないことに、ものすごく悲観的である。ACIDによる一貫性保証がないカウンタの実装などは特に嵌る。そして、すぐに不具合を見つけることができないことが問題を大きくする。単体テストでは成功して、本番運用に入っても数日は成功するので一見うまくいくように思うかもしれないが、失敗の失敗、つまり、リカバリーの失敗までを考慮していないと、破綻は必ず訪れることになる。(マーフィーの法則「起きる可能性のあるものは必ず起きる」)(関連:信頼性とジレンマ)

調査結果および対応策

 話は戻り、パフォーマンス劣化の原因について調べてみた。そして、なんとかACIDでやる方法はないものか、もう一度考えることにした。

一度に100件登録した場合

<所有関係の負荷>
  * 所有関係あり: 15秒くらいから。件数が増えるとだんだん遅くなる。
  * 非所有: 3秒くらい。件数が増えても変わらない。

<Index作成の負荷>
  * インデックスなしと、インデックスありの、どちらも100件登録で3秒くらい。
  * インデックスありのテーブルで、100000件弱登録できた。
  * 登録にかかる時間も100件約3秒と変わりなし。


 以上の結果のとおり、Indexの数というより、所有関係の方がパフォーマンスへの影響が大きいということがわかった。所有関係については、それを解消しなければ、ほとんど使い物にならないような感じであった。

所有関係とEntityGroupについて

 実はEntityGroupと関連に直接的な関係はない。そもそも親子関係をしているのは、JDOのowned関係がEntityGroupとみなされることを利用してトランザクションを実行したいからであった。しかし、そうしなくても、明示的なKeyの生成、つまり、子のキーを親のエンティティのキーを使って生成することでEntityGroupは作成できるのだ。パフォーマンスを出すには、Owendな関連を解除して、Keyの親子関係で関連付けたEntityGroupを使って、トランザクションを実行すればよいことがわかった。



以上の結果を踏まえてコードを修正した。




金曜日, 8月 21, 2009

【Google App Engine】 大量のPDFを生成してわかったGAEの真の実力 このエントリーを含むはてなブックマーク


クラウドPDFサービスのパフォーマンス測定について

 先日公開したScalable PDFのパフォーマンステストと同様に、クラウドPDFサービス(GAEのReflex iTextサービス)についてもパフォーマンステストを行ったので公開したいと思う。
 クラウドPDFサービスは、AmazonEC2のかわりにGAEを使っており、負荷分散機能はGAEまかせとなるが、Reflex iTextのサービスを使っている点はScalable PDFサービスと同じである。ただ、GAEの場合は実行時間など、いろいろな制約があり、リクエストが失敗する前提で設計しなければならないといった考慮が必要となる。今回のテストにおいては、エラー時に時間間隔を置いてリトライする仕組みを取り入れている。
 結果としては、以下のように、GAEでも約8千ページを4~5分で処理できたということで、まずまずであったのだが、これがいっぱいいっぱいであったことも事実であった。1リクエストが80ページ生成で30秒かかることを考えると、少なくとも数十のアプリケーションが並列処理していたのではないかと思われる。(1アプリに複数リクエストを与えると30秒以内で返せなくなりタイムアウトエラーになるため、成功リクエストのほとんどが単独で処理されていたと考えられる)しかし、さらに多くのリクエストを処理させたい場合には、アプリケーションを分けるなどの工夫が必要となってくるだろう。ただ、一回目より二回目の方が明らかにエラー件数が減ったため、徐々にリクエストを増やしていくのであれば、そのままでもスケールしていくかもしれないという期待はある。
 ちなみに、EC2を使って作成する場合には、8000ページを約1分で処理できるが、400プロバイダ/20インスタンス(c1.xlarge)の起動が必要となる。また、レイテンシーの問題があるので実際の処理時間は2~3分といったところだと思われる。
 c1.xlargeは、Opeteron 1.0-1.2GHz相当*20Unitで7GB Memあり、Celelon2.2Ghz、512MBのマシンの10倍程度の処理能力が確認されたが、GAEの場合はCelelonの2~3倍、c1.xlargeの1/4程度の能力であった。

 * エラーがあれば時間間隔を置いてリトライする仕組み(時間間隔=100msec*リトライ回数*10msec)
 * 1リクエストで生成できるページ数の限界は80ページ(処理時間は30秒でこれ以上はタイムアウト)
 * リクエストを100並列で実行するとエラー数が膨大となった(失敗)
 * リクエストを1回目より減らして95並列で実行すると4~5分かかった(成功だがリトライが多いもので8~10回程度発生した)
 * 一回目より二回目の方が明らかにエラーの数は減っている(GAE側でスケールしている?)
 



クラウドPDFサービスとScalablePDFサービスのすみわけ

 クラウドPDFサービスは、当初は、Morph AppSpaceで動作する無償のサービスとして発表したが、GAE上で動作する現在においては、Googleサービスの特長である、素早いレスポンス、自動的にスケールする機能といったものが追加されて素晴らしく進化した。一方、EC2を動作環境とするScalablePDFサービスでは、AutoScale機能+自前の負荷分散プログラムによってクラスタリングを行い、スケーラビリティを実現しているが、複雑になってしまった分、管理コストが重くなってきている。
 これら2つのサービスは現時点において機能的に大きな差はない。強いて言えば、スケーラビリティに関してEC2の方が瞬間的に大きなトラフィックが発生した場合に対応しやすいということぐらいである。ただし、EC2にはレイテンシーの問題が依然として存在している。
 したがって、リトライ処理などを含めた時間的なばらつきや応答時間が許容範囲であれば、現時点でのおすすめは、当然、GAEによるクラウドPDFサービスである。どうしても厳密な応答時間が要求されるのであれば、ScalablePDFサービスをおすすめすることになる。
 利用料は、クラウドPDFサービスは大企業だろうと個人だろうと月額固定の3000円であるが、ScalablePDFサービスは月額数万円程度とさせていただいている。運用管理コストが料金の差となって反映されていると考えていただければと思う。

木曜日, 8月 20, 2009

【おしらせ】 8/24次世代Web活用部会でGAEについて話します このエントリーを含むはてなブックマーク


第2回XMLコンソーシアム 次世代Web活用部会にて、『GAE使いこなしのディープな世界』というテーマで話します。単なるアプリの作り方ではなく、大量データ処理を実際にやってみて、うまくいったこと、いかなかったことを中心に、実践的な内容を話す予定です。

 * 大量のPDFを生成してわかったGAEの真の実力(Scalability、パフォーマンス)
 * 大規模データを問題なく処理するためのテクニック(トランザクション、EntityGroupとRelationなど)

【Reflex】 JSONにおける属性表現、List表現 このエントリーを含むはてなブックマーク


JSONにおけるXMLの属性表現

 JSONではXMLの属性といったものを表現できないため、これまでReflex JSONでも属性を無視していたのだが、AtomをJSON化したくて、Link要素の属性を扱う必要が出てきたので、Reflexでもムリクリ表現できるようにしてみた。Reflex Core に詳細を記述してあるが、V1.2.1から使えるようになっている。(同時に要素名のハイフン変換もサポートした)


* JSONでは、要素名の"-"が"__"に変換されるため"type_str-ing"は、"type_str__ing"となります。
* また、XMLの属性は、JSONでは"要素名___属性名"プロパティとなり、"attr1"は、JSONでは"type_str__ing___attr1"プロパティとして表現されます。


ListをJSONに変換する

 JavaのListをJSONに変換する際に、両者の関係だけにとらわれてしまうと、なかなか思うように作れないので、一旦、JSONとListのことは忘れて、XMLに変換するイメージをもちながら構造化していくといった方法で設計するのがよいと思われる。以下にQ/Aの抜粋を添付する。


List をJSON に変換するのは基本的に構造化が必要です。
以下のサンプルがわかりやすいかと思います。
http://reflex.sourceforge.jp/reflexitextsample_function4.html

EntityはStockInfo.javaで、StockList.javaで値を詰めています。
構造化をイメージしやすいのはstocklist.xmlですが、stocklist.jsonも同じentityから生成したものですので、同じ構造をしているといえます。

Reflexでは構造を意識しないといけない仕様になっていますので慣れるまでちょっと不便ですが、そういうものと思っていただくしかないでしょうね。

ただ、JSONの配列の便利さも損ないたくないので、以下のサンプルのような、配列の要素数
を与えてムリクリ変換できるような方法も用意しています。

http://reflex.sourceforge.jp/reflexjsonsample.html




日曜日, 8月 16, 2009

【Reflex iText】 Publicクラウドはサブスクリプションモデルがいい このエントリーを含むはてなブックマーク



publicクラウドのビジネスモデルは「XX放題」

 Reflex iTextは、先日、月額3000円で利用できるサービスとして発表させてもらった。Reflex iText


Morph AppSpaceやGoogle App Engineなどのクラウドサービスにデプロイして使用する場合は、1インスタンスにつき1ライセンス料が発生します。1ライセンスはトランザクションの量に関係なく固定で月額¥3,000となります。warファイルの提供はありません。基本的に弊社にて導入支援を行います。サービスレベルはMorph AppSpaceやGoogle App Engineに依存します。また、利用料は別途お客様負担となります。


 Reflex iTextがこのような定額の利用料を徴収するサブスクリプションモデルとしたのは次のような理由による。

 7/23に開催された、アカマイ・カスタマ・カンファレンスの「ビジネスを伸ばす次世代Webとは何か?」というパネルディスカッションで知ったのだが、アテインさんの動学.tvという教育サービスは月額2800円で受講し放題である。これが割と繁盛されていて、現在も会員さんをどんどん増やしているらしい。月額固定で借り放題といえば、TSUTAYAのDISCASなんてのがあるが、これとよく似たサービスだ。モデレータの佐々木さんは、次世代Webのビジネスモデルは、サブスクリプションモデルと広告モデルが中心になるだろうとおっしゃっていたが、たしかに、月額定額利用料で「XXし放題」というのはとても魅力的なモデルである。これまでクラウドもユーティリティーコンピューティングというキーワードで電気やガスのように利用するモデルが吹聴されてきたが、要するに、サブスクリプションモデルのことである。どんなに使われても非常に安く提供できるクラウドがあればこそサブスクリプションモデルも生きる。

受託開発でGAEを利用することは可能か


 publicクラウドは、大企業から請負で開発するというビジネスモデルより、コンシューマをターゲットに小売的な商売を行って、使ってもらってナンボといった感じのビジネスモデル方が合うような気がしている。クラウドが請負になじまないのは、最大の壁であるセキュリティ問題があるからだ。セキュリティは最後の難問題でも触れているが、クラウドでセキュリティを確保することは一筋縄ではいかない。メーリングリストで以下のような議論が行われていたので、SLAと担保の2つをどう解決するかという自分なりの意見を述べてみた。


受託開発でGAEを利用することは規約的に可能でしょうか?
またデメリットとしてどのようなことが考えられるのでしょうか?


>情報漏えいやシステムダウンは、どんなに注意を払っていてもおきるときはおきます。

私もそう思います。
ただ、実際には、Googleは免責されている(事故が起こっても損害金額などを払ってくれない)ので、発注者側がリスクを負うことになってしまいます。

少ない確率であっても、実際に情報漏えいやシステムダウンが起きる可能性があるため、発注者は導入の際に、事故が起きたときに誰がリスクを負うのかということを、会社に説明できなければなりません。(私も数年前までは発注者側にいたので、少なくとも会社はリスクを負わないということを、役員会などで説明する必要がありました。私に限らず皆そうなんだと思います)

その点、アウトソーサが管理しているシステムであれば、サービスレベルの質はどうであれ、彼ら自身が担保してくれている(個人情報漏洩であればほぼ無限責任)ので、発注者側はとても楽です。ただ、この形態は、ものすごく高くつくので、本当に会社の利益になっているかどうかは、経営者自身がよく考える必要があると思っています。Googleのシステムだからとか、彼らは優秀だからといった形容詞を使ってリスクをいかに少なく示したところで、担当者による説明はとても難しいと思いますので、結局は経営者判断になるんじゃないかと。

最近、私は逆の立場になり、GAEを提案しています。

お客様の意見としては、安さが会社の競争力になるという評価をしていただいている一方で、やはり、セキュリティの課題をクリアーできずに導入できないというところが多いようです。
また、抵抗を示している多くがIT部門なのもおもしろいと感じています。彼らは、個人的には導入にものすごく意欲的なんですけど、大企業だと会社ルールの壁を突破できないというか、政治的に動く必要があって、それがなかなかできないみたいです。事例もない現時点では無理もないことだとも思います。

結局のところ、大企業に浸透するまでには時間がかかりそうだから、中小企業か個人をターゲットに数多くのユーザを獲得する以外なさそうかな、と私は感じています。コンシューマをターゲットに小売的な商売を行って、請負でいくらというより、使ってもらってナンボといった感じの方が合うような気がしています。


水曜日, 8月 12, 2009

【雑記】 イケメンとチョイワルの喧嘩 このエントリーを含むはてなブックマーク


 今回の喧嘩ネタはちょっとレベルが高い。(見方によっては低い)

 福岡の夜 (shi3zさんとの喧嘩)
 そりゃブログを書くだけで平和が来るなんて甘い夢など見ちゃいねえさ

 リーダシップを論じるお二人の主張が異なるのはさておき、shi3zさんがid:mkusunokさんに最も伝えたかったことは「ブログの無力さ」であり、1個人がブログに書くということが、いかに微々たる力であるかを認識すべきであるということだった。それを、「お前みたいなクズのような存在」と相手に直接いうことでわからせようとした。(たぶん、この表現自体に問題があると思われるが、ストレートに表現することを信条としているshi3zさんにとっては普通のことらしい)

 一方、普段からshi3zの言動が気になっていた小野さんはこれにブチキレ。以前、「嘘を平気でつける人間」であり、それ故に「欺瞞に満ちた人間」であると言われていることもあって、「お前はいままで、いったい何をしたんだ。何もしてないクズだろ」となった。

 shi3zさんは本当のことを伝えることが「善」であり「手段」を選ばないこともある。一方の小野さんは「手段」を選ばないと損するよ、と伝えたい。

 どちらも正しいことをしているように思えるが、ぜんぜん会話になっていない。コミュニケーションを阻んでいる原因は「妬み」と「欺瞞」なのだろう。面白いのは、自分は妬んでいるといっているshi3zさんがいる一方で、自分は欺瞞であるとはいえない小野さんがいること。

 また、今回の件で相当な決意とやる気をだしたshi3zさんの未来は明るいかもと思った次第。

木曜日, 8月 06, 2009

【Google App Engine】 Entityとトランザクション2 このエントリーを含むはてなブックマーク


 今回は親子関係をもったEntityのUpdateを中心に解説する。例によって、請求書アプリを題材にしている。ソースは=>ココで、使用しているReflexGaeライブラリは=>ココ

親子関係をもつEntityのUpdate

 請求書アプリでは、請求書1枚がEntity1インスタンスとなるように設計されている。1つの請求書(Invoice)が複数の明細レコード(Order)をもち、さらには、Invoiceを親であるInvoiceBaseがListとして複数保持している。また、InvoiceBaseはInvoiceのレコード数、および、Orderのレコード数も保持している。このような関係をownedというが、このような構造をしているのは、EntityGroupとして定義しているからで、1トランザクションで実行する必要があるからである。
 これらを更新するには、下図のように、まず、クライアントから受け取ったJSONのInvoiceをオブジェクトに変換したものとDatastoreとの差分を取り、その差分を反映させたオブジェクトをあらためてPersistentにすればよい。



DatastoreをUpdateする際の注意点


* 更新でmakePersistentは必要なし

 Datastoreから取得したオブジェクトはPersistentな状態である。Persistentな状態とは、Datastoreへのアクセサを保持している状態で、setterを使って値をセットするだけで、(makePersistentを呼び出さなくても)永続化されることになる。
 また、所有関係にあるオブジェクトがデータストアに保存されると、そのオブジェクトと関係があるすべてのオブジェクトの内、保存する必要のあるもの(新しいオブジェクト、または最後に読み込まれてから変更されたもの)は自動的に保存される。つまり、Persistentなオブジェクトにaddすることで、親のオブジェクトは子を含めてPersistentになる。

* lazyな読み込み

Datastoreから取得したオブジェクトの子は eager loadされない(アクセサを使って参照したときのみ値を取得できる)ので、別途、子を取得して親にセットする必要がある。

 * propertyの更新は1トランザクションで1回のみ許される

2回以上更新すると、「can't update the same entity twice in a transaction or operation」というエラーが起きる。

* オブジェクトの親子関係とKind(≒Table)の親子関係に関係はない

親オブジェクトに子をaddしたからといってKindの親子関係になるわけではない。Kindの親子関係を紐付けるのはKeyであり、子のKeyに親のKeyをAddすることで関係付けることができる。Keyを明示的にセットしない場合(nullの場合)はシステムが勝手にKeyをセットする。

サンプルプログラム

 
 以下のように動作するサンプルプログラムを示す。
1.でそれぞれ取得しているのは、InvoiceBaseからInvoiceを直接取り出すのができないから。(forループでまわさないと取れない)また、lazyな読み込みなので、3.の処理が必要となる。
2.でdetachCopyしているのは、Persistentオブジェクトを直接操作するのを避けるため。Persistent直接だと、setterを1回しか呼べないという制約にもひっかかる。


1.InvoiceとInvoiceBaseのPersistentオブジェクトをそれぞれ取得する
2.invoicePersistentから、いったんdetachCopy
3.コピーしたものに子要素のorderを追記補完
4.invoiceからinvoiceSource(1品一葉)を取得する。
5.クライアントからのリクエストデータで更新があったものだけをinvoiceTargetにコピーする
6.Revisionを更新
7.InvoiceBaseにaddすることでInvoice以下子要素のorderなどもpersistentになる





AJAX CRUDサンプルとJDO代替ライブラリ
RESTfulアプリのCRUDサンプル -Modeling編-
RESTfulアプリのCRUDサンプル -Servlet編-
Entityとトランザクション

水曜日, 8月 05, 2009

【iPhone】 AppleのiPhone向けのアプリビジネスはなぜ魅力的ではないか? このエントリーを含むはてなブックマーク


GoogleのAndroid向けのアプリビジネスはなぜ魅力的ではないか?をちょっと書き換えてみた。


Android向けの開発意向表明を行って数ヶ月になるが、もっとも良く投げかけられる質問は「iPhoneへのプラットフォームでは開発しないの?」というものだ。
少し前までは、ObjectiveCの開発スキルやリソースに目処がつけばやってもいいけど、学習コストが重いよねえ、などと答えていたが、最近は少し見方が変わってきた。

今の勢いでHTML5が進化・浸透してくれるのであれば、わざわざ移植コストをかけてiPhoneやWindows Mobile向けにネーティブ・アプリを開発するよりは、少なくともUIの部分をすべてHTML+Javascriptにまかせたアーキテクチャでのインタラクティブなアプリの開発というのも十分に可能性があるように思えてきたのだ。

この「HTML+Javascriptですべて出来るじゃん」という発想は、そもそもマイクロソフト時代のInternet Explorer 4.0やNetDocs(90年代の終わりに開発していたマイクロソフト版Google Docs)のころから持ち合わせて来たもの。パソコン向けではWindowsの成功が故に日の目を見なかった発想が、数多くのプラットフォームが混在・競合しているスマートフォンの時代になって大きな意味を持ってきたというところが興味深い。

 そもそも、「AppleがiPhone」を発表する、と聞いた時には、「ついに超高速・超小型のJavascriptエンジンの開発に成功したか」と過大な期待を抱いてしまった私にとって、ObjectiveCでNativeコードを書かなければならないiPhoneははっきり言って期待はずれの拍子抜け。明らかにGoogleの「ウェブアプリケーション中心」というの基本路線から外れており、せっかくiPhone向けのアプリを作った開発者がAppleに途中ではしごを外されて途方にくれる可能性は大きいと私は見ている。

現時点で、最新のハードウェア向けのアプリを作りたければAndroidがiPhoneよりも魅力的なプラットフォームなことは火を見るよりも明らかだし、どうしても「Googleに縛られたくない」のであれば、標準になりつつある、HTML5上でJavascriptを駆使した本気のインタラクティブなアプリを作るノウハウを蓄積しておく方がよほど魅力的に思える。もちろん、現時点でもっともすぐれたHTML5の実装を持つモバイル端末は iPhoneだし、ちゃんと作っておけば、Android端末でもPalm Preでも動くはずなので、投資効率は高い。

 ということで結論から言ってしまえば、「移植性を無視して最新のハード向けにばりばりのネーティブコードを書きたかったらiPhone向けのアプリを Objective Cで作るか、Android用にJavaで作り、さまざまなデバイスへの移植性が重要ならHTML5+Javascriptでインタラクティブなアプリを作ってiPhone上のSafariでテストしておく」というのが現時点でのスマートフォン向けの開発投資の仕方としては、最も賢い選択肢だと考えている私である。

ちなみに、JavaとかFlashとかはどうなの、という質問が来そうなので答えておくと、「スマートフォン向けではなく、現状の端末向けのアプリの開発であればまだまだJava、BREW、Flash liteは健在。ただし、3〜5年後にそれらのプラットフォームがHTML5によって駆逐されてしまう可能性は大」というのが私の見方。もちろん、「ガラパゴス携帯」とか「Windows Mobile」という局所的な抵抗勢力はそれなりに残るだろうけど、WebKitがモバイル端末でさくさく動くことをAppleが証明してしまった今、世界の流れはHTML5に一気に向かっている。

日曜日, 8月 02, 2009

【Google App Engine】 RESTfulアプリのCRUDサンプル -Modeling編- このエントリーを含むはてなブックマーク


 前回に引き続き、RESTfulアプリの解説をおこなう。今回はModeling編。本当は最初に説明すべきところ。

Entity設計

 外部設計において基本となるのはBCEモデル。3分で分かる設計の話 にもあるように、Entity設計とは項目の整理をすること。項目の抽出は画面設計を行ってからじゃないとできないので、実際には、Boundary=>Entity=>Controlの順番で設計を進めていくことになる。Entityの設計では、基本的には名前と型と多重度の定義を行うだけでよいのだが、もう少しがんばって、具体的にXMLのインスタンス(項目とデータを含むもの)まで作成するとよいと思う。ちなみに、請求書アプリのXMLインスタンスはこんな感じになった=>XML

 ReflexではModel設計のことを敢えてEntity設計と言うようにしている。Modelは型で、Entityはその型から作ったインスタンスであるから、Entityは金太郎飴の一粒のようなもの。Reflexでは、Entityのインスタンスイメージの方が具体的で好都合なので、なるべくそう呼ぶようにしているが、設計フェーズを意識しないときは、Model設計とEntity設計はよく同じ意味で使ったりもするので、実際にはそれほどこだわっているわけでもない。ただ、Modelを強調してしまうと、「Model=抽象的な器+ロジック」のロジック部分が強調されることになって、データ構造を中心に考えるReflexの設計に合わなくなってしまうので、やっぱりEntity設計と呼ぶのがいいような気がしている。
 
Modelの設計とサービス化
 
 Entityの設計ができたら内部設計をすすめてModelを設計していく。Modelには、データ構造を表現する部分と、ロジック(ビジネスロジック、または、ドメインロジック)を表現する部分から成るが、Reflexではこれを2つに分離させることを推奨している。具体的には、データ構造を表現したModelに実際のデータの管理をまかせてCRUDのAPIを持たせることが一つと、ドメインロジックには、専らビジネスロジックの実行処理をまかせるのが二つ目。ビジネスロジックの実行では、例えば、Entityから別のEntityへの変換や、HTMLやPDFなどへの変換、メール送信などが考えられる。前者はDatastoreへの登録があるから不揮発性なのに対し、後者は揮発性であるのが特徴である。ドメインロジック内で許されるのは参照のみで、登録や更新は絶対に許されないとものする。ただし、Datastoreからの参照は可能であり、必ずしもWebサービス経由で参照する必要はない。
 また、Modelのこれら2つの機能をREST化したものをサービスと呼んでいる。例えば、EntityをCRUDで操作するデータ管理用のサービスと、PDF変換などのロジック実行用のサービスなどがある。これらはModelの2つの性質をサービス化したものと考えることができる。

サービスにおけるEntityの扱い方と具体例

 RESTfulな新3層アーキテクチャでも説明しているとおり、すべての操作はパラメータにentityを与えることにより実行する、というポリシーで設計する。こうすることで、entityという一貫したI/Fが扱えるようになり、その結果、外部設計の目的が明確になる。外部設計とは、つまり、entity設計を意味することになる。


 entity = create(entity);
 entity = retrieve(entity);
 entity = update(entity);
 entity = delete(entity);


 請求書アプリで具体的に説明してみよう。まず、create(entity)に対応するのは、invoiceのXMLをPOSTメソッドで実行する部分である。Servlet内部では、reflexgaeライブラリを使ってXMLをrequestオブジェクトから取り出してinvoiceオブジェクトに変換、それを直接datastoreに挿入している。非常にシンプルな実装である。<参考>RESTfulアプリのCRUDサンプル -Servlet編-


 Invoice invoice = (Invoice) getEntity(req, MODEL_PACKAGE);
 jdoUtils.insert(invoice);


 retrieve(entity)に対応するのは、以下の部分で、requestオブジェクトのクエリパラメータの内容を、一旦、Invoice paramに詰めて、それを条件にjdoUtils.getEntriesByParam()で検索を実行している。


 Invoice param = new Invoice();
 (new RequestMapper()).setValue(req, param);
 ・・
 jdoUtils.getEntriesByParam(param, pagesize, nextid);


 update(entity)も基本的に同様であり、PUTされてきたXMLをInvoiceに変換して、jdoUtils.update(invoice)を実行するだけである。delete(entity)も同様である。


 Invoice invoice = (Invoice) getEntity(req, MODEL_PACKAGE);
 JdoUtils jdoUtils = new JdoUtils();
 jdoUtils.update(invoice);


 jdoUtilsの内部では、reflexgaeの FieldMapperを使って、targetのinvoice(persistant)に対して、sourceのinvoiceの更新があるプロパティだけをセットするようにしている。つまり、sourceのinvoiceのプロパティのうち、nullでなく、かつ、targetと異なる内容のものを、setterを介してセットするという処理をfieldMapper内で行っている。わざわざsetterを介すのは、JDOに更新であることを認識させるためである。(フィールドを直接書き換えると更新されないので注意)
 また、invoiceのように、1:nの子要素を持つ場合でも、この操作1回ですべての子要素を更新することができる。ただし、子要素のKeyはしっかりsourceの方にセットしておく必要がある。(私が試したのは2階層までであり、3階層の孫要素も更新されるかどうかはわからないので注意。)


fieldMapper.setValue(invoice,target);


 

Control

 Controlは、簡単に言うとサービスを呼ぶ機能のことである。他のサービスを呼びつつ、一連のフローを実行して、別の新たな結果を返すような機能、といった感じだろうか。いわゆる、マッシュアップがそうだし、先日公開した、Google Spread SheetからPDF生成するBookmarkletもControlの一種である。ドメインロジックをサービス化したものはControlになることができるが、すべてがControlと呼べるかというとそうではない。

Boundary

 いわゆる画面のことである。本当はこれを最初に説明すべきところだが最後になってしまった。ここでいいたいのは一つだけ。レンダリングのために必要な処理はBoundary内に記述すべきということ。EntityをサービスからXMLやJSONで受け取ってレンダリングしたり、あるいは、GEARなどに一時的に保存したり、サービスに登録内容を送信したりする処理は、Boundaryとして実装すべきであり、決してControlなどに押し付けてはいけない。画面内に閉じることで疎結合のメリットを最大限に享受できるようになるからだ。

まとめ

 ここまで、主に外部設計の話とBCEモデルについて説明してきたが、あらためて強調したいのは、Entity設計をクローズアップするということ。つまり、Entityの設計をしっかりやって、それを一貫したI/Fとして扱うべし、ということにつきる。サービスについても、EntityをパラメータとしたCRUDや、Entityを与えてEntityを返すという単純なものにすべきである。そう考えていくと、Entityさえできれば、それをもとに自動的にscaffoldを作成するなんてことは簡単にできる。ちなみに、Reflexでは、reflexentityeditorというものを用意しており、Reflex表現からEntityを自動作成するツールも用意している。将来的には、CRUDができるServletや、HTMLページなどを生成するscaffold生成機能も用意したいと考えている。しかし、今現時点では、まだ他に優先してやるべきことがたくさんあるので取り掛かることはまだしない。

 最近知ったのだが、これに非常によく似たものに、Simple Modelerというものがある。モデル駆動開発を掲げ、Scalaで作成したメタデータを元にモデルを自動生成できる。

 Reflexは割と古くて2005年ぐらいから存在しているが、そのコンセプトは黙殺されてきた感がある。(別に流行らなくても結構なのだが)
 最近、同じようなことを考えている方が世の中にもいるということで、ちょっと安心した感もある。だが、Simple Modelerが大流行りして、Reflexが真似しているといわれないようにしないといけないなあとは思う。

木曜日, 7月 30, 2009

【Google App Engine】 RESTfulアプリのCRUDサンプル -Servlet編- このエントリーを含むはてなブックマーク


 今回は、RESTfulアプリのServlet部分について説明する。例によって、請求書アプリを題材にしている。使用しているReflexGaeライブラリは=>ココ

検索(GET)

 検索ではRequestパラメータを受け取り、それを条件にdatastoreを検索して、結果をJSONもしくはXMLで返す。

 1. ReflexServletをextendsしてRESTful機能をもたせる
 2. RequestパラメータをEntity(Invoice)にセットする
 3. パラメータInvoiceを与えてdatastoreを検索
 4. 結果をJSONもしくはXMLにして返す
 5. エラー発生時はエラー内容をJSONもしくはXMLにして返す




登録(POST)

 登録では、クライアントからPOSTで受け取ったXMLもしくはJSONをEntityオブジェクトに変換した後、datastoreへのINSERTを実行する。クライアントには登録成功でHTTPStatus201(created)を返す。


 1. クライアントから受け取ったXML(JSON)をEntityオブジェクトに変換
 2. すでに登録されている場合エラー
 3. 登録されていなければinsert
 4. エラー発生時はエラー内容をJSONもしくはXMLにして返す




更新(PUT)

 更新では登録と同様に、クライアントからPOSTで受け取ったXML、JSONをEntityオブジェクトに変換してUpdateを実行する。更新オブジェクトのRevision番号が同一であれば更新実行、異なれば楽観的ロック失敗とする。



削除(delete)

 削除では、リクエストパラメータをもとに削除したいオブジェクトを取得し、deleteを実行する。該当するオブジェクトが存在しない場合に、HTTPStatus 204(no content)を返すようにしているが、これは特になくてもよい処理かもしれない。



嵌りどころと注意点

 1. POSTの場合、Requestに対し、getInputStreamを実行する前に、getParameterを実行してしまうとInputStreamの値が読み取れない。(PUTの場合はなぜかうまく動作する)
 2. HTTP Status 200,201以外をセットする場合、ResponseのOutputStreamに内容が出力されない。具体的には以下のような現象となる。


(HTTP Status) : (現象)
* 204 : 内容なし
* 409 : java.io.IOException
* 404 : java.io.FileNotFoundException


 最後に一言。GAEにおけるアプリケーション開発で特に重要だと思うのはテストである。テストコードを書けとか、TDDをやれとかいう意味ではなく、実際にテストして動作を確認することが重要である。(私は自分が作ったアプリのテストコードはほとんど書かないが、他人が作ったものは実際に検証しないと何も信じないタチである。)実際に現象を確認しないで、SPECやDocumentを見て判断してはいけないし、やみくもに信じてはいけない。ある程度予想できる結果であったとしても、必ずテストして確認すること。GAEは特にそうしないと危ない。石橋を叩いて渡るぐらいの心構えでちょうどよいと感じている。

<関連>
AJAX CRUDサンプルとJDO代替ライブラリ
RESTfulアプリのCRUDサンプル -Modeling編-

火曜日, 7月 28, 2009

【Google App Engine】 Pagingをどうやって実現するか このエントリーを含むはてなブックマーク


 JDOをそのまま使うとPagingができないことは前記事で述べたとおりだが、DatastoreAPIを使えば可能なので、今回はその方法について述べたいと思う。前記事と同様、請求書アプリを元に説明する。

KeyとCounter


 まず、基本となるEntityのInvoiceBaseとInvoiceの構造から。



 InvoiceBaseは、Invoiceレコードを保持するする親のEntityだ。InvoiceBase(親)に紐づくInvoice(子)は1:nのOwnedな関連である。また、Invoiceレコードの件数を格納するcounterプロパティをもつ。InvoiceのKeyは、トランザクションで括る必要があってKeyを連結させているが、拙作ライブラリReflex GAEのAPI、keyUtils.getChildKey()を使って、連結されたInvoiceのKeyを取得している。(ReflexGaeライブラリは=>ココ)
 レコードの件数counterは、Pagingのために必要な連番を振るためにも使用される。Datastoreは自動で連番を付けられないため、InvoiceBaseの自身でもつより他ない。

 一応、JDOのドキュメントで以下のようなものがあったので試してみたが案の定できなかった。(追記:最新版SDK1.2.6では、Statics APIで件数が取れるようになっている。こちらの記事の参考に=>Keyとカウンタは別々に考えるといいかも)


【コード】
 @PrimaryKey
 @Persistent(valueStrategy = IdGeneratorStrategy.INCREMENT)
 private Long uid;

【エラー内容】
 Message : There is no available value generator for strategy "increment" for this datastore. Please consult the documentation for details of which generators are available.


 そもそも、親子関係をもつようなEntityを扱うには、Long型ではなくKey型を使わなくてはならない。Googleのドキュメントにも記述されているが、Keyの中身が以下のようなツリー構造をしていることからもその理由は想像できる。


子要素までaddしたときのキー(実際はBase64でエンコードされている)

 j reflexworksr,  InvoiceBase" InvoiceBase Invoice"ID2
 j reflexworksr,  InvoiceBase" InvoiceBase Invoice"ID1


 よく、KeyをStringとしてもたせるような記述、「@Extension(vendorName = "datanucleus", key = "gae.encoded-pk", value = "true")」を見かけるが、階層型の場合、連結させたDatastoreのKeyがそのままStringに変換されてしまうのでおもしろくない。例えば、先のEntityのKeyをStringにするとその中身は、「j reflexworksr,  InvoiceBase" InvoiceBase Invoice"ID1 」となってしまう。業務アプリで意識すべきは、業務アプリのKeyであって、DatastoreのKeyではない。DatastoreのKeyはむしろ隠蔽させて、ID1という業務アプリのキーだけを意識させる方がわかりやすいだろう。なので、Reflexでは、JDO KeyをStringとしてもたせる記述はせず、アプリのPKをKeyに変換するという方法をとっている。以下はアプリのPKであるinvoiceNoをJDOのKeyに変換する例である。(keyUtilsはReflexGaeのAPI)


 Key childKey = keyUtils.getChildKey(Invoice.class, param.invoiceNo);


Keyによる検索

 このPKを使った検索の例は次のとおり。pm.detachCopy()をすることで、Persistentではない、自由に扱えるEntityのコピーを得ることができる。Persistentなものは、まだDatastoreにAttachされていて、コネクションが張られている状態のような感じ。実際に値を書き換えるだけでDatastoreの中身も変更される。



Paging検索

 Paging検索では、以下のように、idで開始点を与えて、かつ、withLimitで範囲を絞るといった感じになる。JDOでは6千件でフリーズしてしまったが、DatastoreAPIを使っているため理論的には無限で、このサンプルではcounterの最大値(longの2147483647件)までは扱えることになる。とりあえず1万件でテストしてみたが非常に高速にレスポンスが返ってきたので問題なく使えると思う。

 1) filterで開始点となるidの値をセットして、それ以上のものを検索対象とする

query.addFilter("id", Query.FilterOperator.GREATER_THAN , Long.parseLong(nextId));

 2) FetchOptionsでwithLimit()をつけることで、検索結果の件数を絞る

FetchOptions fetchOptions = FetchOptions.Builder.withLimit(limit);




指定可能なfilter条件

 DataStoreAPIでは、GREATER_THANなどのinequality filter(<,>など)が1つと、equality filter(=)を複数指定できる。連番でinequality filterを使ってしまっているため、あとはequality filterだけが使えることになる。Reflex Gaeライブラリでは、QueryUtilsというものを用意していて、検索条件を格納したParameterBeanからequality filterを自動的に追加できるようにしている。

 // 検索項目(Indexがあるもの) を指定してnewする
 QueryUtils queryUtils = new QueryUtils(new String[]{"invoiceNo","companyName","job","issuedDate"});

// paramの検索項目がnullでなければaddFiler(FilterOperator.EQUAL)される
queryUtils.setParam(param, query);


EntityConverterによる変換

 DatastoreAPIで検索すると、MAPで返ってくるのでEntityのPropertiesからListに変換する必要がある。DatastoreAPIには、DataTypeTranslatorというのがあったが、使い方がよくわからなかったので自前で作成した。それがEntityConverterである。

List result = entityConverter.convert(Invoice.class, resultIterable,null,condition);


 filterで指定できる条件はequality filterだけなので、Like検索など複雑なことをやりたい場合には、Entityを取得した後で別途Java側で処理しなければならない。EntityConverterにソート機能や条件抽出機能をもたせたているのはこういった理由からだ。
 comparatorをEntityConverterの第三パラメータに与えることでEntityをソートして返すことができる。また、EntityConverterの第4パラメータにconditionを与えることで、conditionに合致するものだけを変換対象にするといったことができる。
 
Indexについて

 上記ソースのQueryはDatastoreAPIのものであってJDOQLではないので注意が必要だ。JDOQLでは自動的にIndexが作成されるが、DatastoreAPIではindexがないとエラーになるので自分で作成する必要がある。(これはむしろ好都合)
 作成するには、すべての検索パターン分のindexを定義した、datastore-indexes-auto.xmlを所定の位置(WEB-INF/appengine-generated)の下に置いてDeployするだけだ。(datastore-indexes.xmlではダメだった。また、index作成には結構時間がかかるので辛抱づよく待つ必要がある。管理画面のDatastore=>Indexesで、StatusがServingになればOKだ。とにかく待とう)
 すべての検索パターン分のindexとは、検索条件の組み合わせをすべて定義しなければならないという意味である。例えば、請求書アプリでは、"id"と"companyName"の2つの条件で検索したい場合もあれば、"invoiceNo","companyName","invoiceNo","issuedDate"の4つで検索したい場合もある。検索する可能性の組み合わせすべてを定義しなければならない。



登録処理 

 登録では次のような感じになる。



大規模なデータをもつ子要素に対してInsertして問題ないか

 まず、pm.getObjectById()でinvoiceBaseを取得する。存在しなかったら新規作成して、pm.makePersistent(invoiceBase)を実行する。これは更新時には必要ないため新規作成時のみ実行するようにする。Invoiceレコードを追加するたびに、InvoiceBaseのcounterをインクリメントする。
 InvoiceレコードはinvoiceBaseの子要素であるが、いわゆる、eager loadではないため、pm.getObjectById()しただけでは、Invoiceの全レコードがメモリに乗ることはない。getter/setterでアクセスしたものだけが実際のDatastoreへのアクセス対象となるので、膨大なレコード数が対象であってもこれでOKだ。(実際に1万件に対して実行しても大丈夫だった)

パフォーマンス

 パフォーマンスへの影響を考えた場合、EntityGroupは最小化すべきとよく言われる。実際、この例はテーブルロックのイメージに近いため、RDB設計に携わってこられた方は、どうしても気になるところだろう。counterとレコードの関係を考えると、トランザクションは切り離せない部分であるため、どうしてもこのような実装になってしまうが、それでも、コンテンションが起きた場合は、JDOCanRetryExceptionを拾ってリトライできるので、スループットは出せるのではないかと思う。実際に測定してみたところ、遅くなる要因としては、EntityGroupの大きさというより、トランザクション処理の件数の方がインパクトが大きかったので、TaskQueueとMemcacheを駆使して、複数のトランザクションを1つにまとめて非同期に一括処理するようなものを介すことでパフォーマンスを改善できると思われる。このあたりの話は深いのでまた別の機会に詳しく述べたいと思う。
 ちなみに、上記コード中で、JDOCanRetryException内で都度rollbackしているのは、そうしないとうまく動作しなかったからだ。(Documentどおりだと動作しないと思われるので注意)

楽観的ロック

 同一レコードの書き換えでコンテンションが起きてしまう場合は楽観的ロックが有効である。@Version(strategy = VersionStrategy.VERSION_NUMBER)を使用する方法があるようだが、この程度のものは潰しがきくので自前で実装する方がよい。具体的には、Updateの際にrevisionを比較して同じであれば更新実行してrevisionを+1するだけ。比較して違う場合にエラーとすればよい。

<関連>
Entityとトランザクション
JDOから直接JSON、XML
JDOから直接SOAP、ATOM。それからDeep Copy
AJAX CRUDサンプルとJDO代替ライブラリ
RESTfulアプリのCRUDサンプル -Servlet編-
RESTfulアプリのCRUDサンプル -Modeling編-

土曜日, 7月 25, 2009

【Google App Engine】 AJAX CRUDサンプルとJDO代替ライブラリ このエントリーを含むはてなブックマーク

RESTfulなWebサービスとワンソースマルチビュー

 先日行ったプレゼンの請求書DEMOでは、以下のように、HTMLの請求書を直接編集後、PDFを表示させるというところをお見せした。(以下のHTMLを表示させて右クリック、Searchで何かを選択してデータを読み込む、さらに右クリックでPDFを選択して生成)

 請求書デモ








 これは一見、統合されたアプリのように思えるかもしれないが、単なる疎結合なサービスの組み合わせであるにすぎない。つまり、JSONやXMLでCRUDできる機能だけが実装されたRESTfulなGAE上のサービスと、そのサービスに対してAJAXでアクセスするHTMLとJavaScript。そして、GAEのサービスからXMLを取得してPDFを生成するReflex iTextサービスの3つの組み合わせで成り立っているものである。特筆させていただきたいのは、ブラウザで表示させたHTMLとPDF生成で使ったテンプレートHTMLは全く同じものを使用しているという点。GAEのサービスから取得したものであるから、データはもちろん同じものである。このように、Reflexは、ブラウザやPDFなど様々なViewに対して、同じEntityを与えさえすれば、同じように表示させることができる。これをワンソース・マルチビューという。(そもそも、1つのEntityから複数の写像をとれるという意味で、名前をReflex(写像)としている)
 このように、ブラウザ以外にも、携帯やiPhone、Android、Flex2などの異なるメディアや端末に対象を広げられれば、コミュニケーションの幅を簡単に広げることができるため、ワンソース・マルチビューは今非常に重要視されている概念となっている。また、 Chrome OS戦略とIT世界の対立軸 でも述べているとおり、Googleは、RESTfulなWebサービスを基本とすることで、コミュニケーションに革命をもたらすとまでいいきっている。

Entityをそのまま表示させる

 これまで説明したように、Reflexではサーバで画面を作ることはしない。JSPも使わない。こうしてしまうと、どうしても密結合なアプリになってしまう。これは非常に大きなデメリットである。Reflexでは、JSONやXMLのCRUDだけが行えるサービスを基本とするが、JSPも使わないとなると具体的にどのようにブラウザで表示させればよいかが問題となる。この点について説明しよう。そんなに難しく考えないことがポイントである。

 まず、HTMLでモックを作り、表示させたいタグにIDを振る。画面が出来たら、IDを全部抽出して並べてルートタグで括る。これが表示用エンティティ。あとはそのエンティティをJavaでも実装して、AJAXを使って受け取れるようにする。JSONだとeval()できるので楽である。あとは、evalした個々のデータをHTMLのidに対してJavaScriptで挿入すればよい。具体的には、DEMOのinvoice.htmlとdisplay.jsを見ていただくのがよいと思う。ちなみに、Reflex iTextのデータ挿入もほぼ同じやりかたで行っている。また、高速化プロジェクト その1で述べたように、パフォーマンスも特に心配することはない。

 ただ、何でもかんでもこのスタイルがよいわけではないことも付け加えておく。SEO対策などで、サーバが出力する最初の段階から完全なHTMLでないとまずい場合などがあるからだ。そういう場合は、Reflex iTextのように、テンプレートとXMLから完全なHTMLを生成するようなサービスを介すとよいと思われる。いずれにしても、画面をベタ書きして疎結合を損なう設計になることだけは避けたいところである。

JDOとスケーラビリティ

 ここからが本当に言いたいところ。

 我々が検証した限り、JDOは大規模データを処理できないことがわかっている。正確には、JDOQLであるが、たった6千件のデータでさえ検索することができないのである。Indexが勝手に作成されてIndex爆発が起きる問題など、いくつか致命的なものもあるが、最も酷いのは、検索対象を絞るページングなどの範囲指定ができないことである。実は、JDOQLは5千件ぐらいまでは何とか検索できる。あれ?上限は千件までじゃなかったっけ?と不思議に思う方はGAEをよく知っている方。そう、一度にFetchできるのは千件までなのだが、それはDatastoreAPIの話。上位実装であるJDOではなんと5千件一括検索ができてしまうのだ。余計なことに、そんなことができる反面、6千件だともうフリーズしたように無反応になる。setRange()を指定してもダメ。getObjectById()やequalの検索条件を指定してやっと1件取得できるのみ。keyの複数件検索も未サポートなので、もう、これは使い物になりません。(※ただし、トランザクションや更新系はJDOのPMで問題なく使えている)
 そこで、私たちはJDOQLを諦め、低レベルのDatastoreAPIだけを使った検索ユーティリティを作成することにした。これを使えばPagingもできて大規模データの操作も問題ない。先の請求書DEMOも使用している。次回はこの中身について説明していきたいと思う。

<関連>
Reflex GAE
Entityとトランザクション
JDOから直接JSON、XML
AXIS2のデータバインディング能力
JDOから直接SOAP、ATOM。それからDeep Copy
Pagingをどうやって実現するか
RESTfulアプリのCRUDサンプル -Servlet編-

金曜日, 7月 17, 2009

【Google App Engine】 TaskQueue API for Javaが数週間以内にリリース!? このエントリーを含むはてなブックマーク


AppEngine SDK1.2.2にTaskQueue APIが含まれていなかったことで残念に思っている方も多かっただろう。MLではgoogleにかなり圧力がかけられていたようで、今朝、ようやくリリースの見通しについて口を割ってくれた。それによると、数週間以内にリリースされるだろうとのこと。やったー!\(^o^)/

TaskQueue API for Java ETA

Marcel,

ASAP means that it is one of our top priorities. Rather than give you a specific date and disappoint you if we don't meet it, we're working hard to release it as soon as we feel it has reached a quality level people can be happy with it. We typically provide new SDK releases on the order of weeks, so you can do some extrapolation from there.


火曜日, 7月 14, 2009

【Reflex iText】 Google Spread SheetからPDF生成するBookmarklet このエントリーを含むはてなブックマーク


 マイクロソフトがネット版オフィスを発売するそうだ。ネット化により、軽量化され、どこからでもアクセスできるという利便性もあるが、それ以上に、他のサービスやアプリと協調できるメリットの方が大きいと私は思う。例えば、Reflex iTextは、ネット上の様々なデータを元にPDF化できるが、GoogleのSpread Sheetと連携することもできる。ネット上でデータを作成してPDF帳票を出力する、といったBIツールのようなことが個々のサービスをマッシュアップさせることで可能になるのである。


* GoogleSpreadSheetCsvParser
http://csv2xml.latest.reflex-itext.appspot.com/

* GoogleSpreadSheet > Shre > Get the link to share
1. Allow anyone with the link to view (no sign-in required) にチェック
2. Also allow them to edit にチェック
3. Save&Close
4. スプレッドシート共有URLをコピー
5. http://csv2xml.latest.reflex-itext.appspot.com/csvparserservlet のGETパラメータとして
row=record
url=スプレッドシート共有URL
を指定


詳細については以下にまとめてあるのでぜひ参照いただきたい。

Google Spread Sheetから帳票作成

木曜日, 7月 09, 2009

【雑記】 Chrome OS戦略とIT世界の対立軸 このエントリーを含むはてなブックマーク

Chrome OS戦略


 昨日、Google Chrome OSの発表があった。Google Chrome OS のご紹介

Google Chrome OS の重要な要素は、スピードと使いやすさ、安全性です。
ユーザーが数秒でコンピューターを立ち上げてウェブにアクセスできるように、非常に高速で軽量の OS を設計中です。ユーザーインターフェイスはユーザーの妨げにならないよう最小限に抑えられ、ユーザーエクスペリエンスのほとんどはウェブ上で提供されます。


 Chrome OSは、「Webがすべて」という、これまでのGoogle戦略の延長線上にあるのは間違いない。これは、要は、クラウドとシンクライアントだけの世界をめざすものである。また、オープンソースの軽量オペレーティング・システムでLinuxを使うとしていることから、実際にはChrome(メッキ)のような薄いものを被せるだけのようだ。WindowsのようなファットなOSを開発するつもりは毛頭ないのだろう。というわけで、Chrome OSの上に高速なレンダリング機能をもつブラウザを載せてアプリを動かすことが中心となる。クライアントが入力したデータは、タイムスタンプのタグがつけられて、サーバに蓄えられる。そして、それがそのままクライアントに送り返される。あとは、それを受け取ったクライアントが自由にレンダリングする、といった感じのアプリケーションになっていくだろう。これはRESTfulなWebサービスを基本とするものである。RESTfulなWebサービスによる疎結合がスケーラビリティをもたらし、また、コミュニケーションにも革命をもたらすということだ。

携帯とのすみわけ

 ただ、Androidでも、そこで開発されているアプリのほとんどは、ブラウザを利用したWebアプリではない点には注意が必要だ。携帯とPCとは、異なるメディアである。そもそも画面サイズという根本的な違いがある。RIA的に進化したブラウザもアリかもしれないが、今の段階では、RIA的な濃さがPCと携帯では違うという認識は重要だろう。ネットブックには、Androidも搭載可能といわれてきたが、GoogleはAndroidに統一するのではなく、Chrome OSを登場させてきた。その大きな理由は、携帯とPCとでレンダリング方法の根本的な違いがあるのかもしれない。
 ただ、携帯アプリがブラウザではないにせよ、先に述べたRESTfulなWebサービスを解釈し、レンダリングするアプリという位置づけであることは変わらない。PCを含め、今後のブラウザはデータを処理できるビジネスロジックを備えたRIA化の方向で進化していくから、ブラウザとRIAとの違いの差はなくなっていくと思われる。なので、携帯がブラウザ中心でないことをそれほど気にするする必要はないと私は感じている。

IT世界の対立軸

 インターネットビジネスにおける「オセロの四隅」は、PC、Enterprise、Cloud、Mobileである。(と、私が勝手に妄想している)
 現在、これらを抑えているのが、Microsoft、IBM、Oracle、Apple、Google、Amazonなど。今一番元気なのがGoogleで、それに対抗しようとしているのがMicrosoft。ディフェンディングチャンピオンに立ち向かう挑戦者の構図は、一昔前であれば、IBM vs Microsoftであった。もちろん、今もIBMは健在なのだが、なにぶんGoogleからは遠すぎる。当然、Googleもエンタープライズにいきたいと思っているけれどもIBMやMicrosoftのように顧客をもっていない。これらは営業が汗水流して長年培ってきた財産であり、そんなに簡単には手に入らない。Googleがすぐにエンタープライズにいけない理由は、Webでしか商売をしない「Webがすべて」という戦略の裏返しでもある。なので、Googleの戦略からいくと、まずはコンシューマで、次にエンタープライズという順番なのだろう。コンシューマですぐ隣にいるのがMicrosoftなのだから当然ガチンコになるというわけだ。









 

水曜日, 7月 08, 2009

【おしらせ】 7/22(日食の日)次世代Web活用部会 セミナーでReflex iTextについて話します このエントリーを含むはてなブックマーク


2009年7月22日(水)日食の日、第1回次世代Web活用部会(XMLコンソーシアム) にて、Reflex iTextの概要を話します。

・『Reflex iTextサービスについて』
~クラウドならではの無限のスケーラビリティを簡単に体験

デモ中心であんまり濃い話はしないつもり。

受講参加するには事務局にメールすればいいじゃないかなあ。たぶん。

土曜日, 7月 04, 2009

【Amazon EC2】 GAEからEC2を管理するツール、CloudWatchなど このエントリーを含むはてなブックマーク




 Scalable PDFでは、EC2インスタンスの起動/終了やCloudWatchを使ったリソースのモニターなどを、Google App Engineからできるようになっている。わざわざGAEを使っているのは、利用料削減とレイテンシーの改善のためである。EC2インスタンスをずっと起動しているとチャージされてもったいないので、必要なときに必要な分だけのインスタンスを起動する、というオンデマンド的な利用がおすすめだ。インスタンス起動は数十秒で可能であり、また、Reflex iTextが揮発性で疎結合であるため、このような経済的なシステム利用(グリーンIT?)が可能になっている。レイテンシーについては、前記事にも書いたとおり、GAEを介す方が50%も改善できるという結果もある。

 さて今回は、このScalable PDFでも採用しているテクニックをいくつかご紹介したいと思う。まずはCloudWatchから。

GAEでCloudWatch

【Amazon CloudWatchとは】
 ・Amazon EC2のインスタンス(仮想サーバ)の状態を監視するサービス。
 ・インスタンスのCPU使用率やネットワークI/O、ディスクI/Oをチェックすることができる。
 ・$0.015 per Amazon EC2 instance hour
 ・インスタンスを落としても2週間は参照可能。
 ・AWS Management Consoleからは2009-07-02現在使用できないため、利用開始時にはEC2コマンドラインツールの「ec2-monitor-instances」を実行する必要がある。
 ・モニターしたインスタンスの情報はCloudWatchコマンドラインツールの「mon-get-stats」で参照できる。

【リンク】
Amazon CloudWatch on GAE/J (beta)デモ
ソース

【このツールの内容】
 ・CloudWatchの監視対象としたインスタンスの情報をGAE/J上から閲覧する。
 ・公開版は過去に使用したインスタンスの情報のみ参照できるようにしている
 ・Firefox3.0とIE6では動作確認済み。

【このツールの仕組み】
 基本的に、CloudWatchAPIの実行結果XMLをReflexを使って一旦パースし、それをJSONに変換してブラウザに渡して、JavaScriptを使って表示している。

 1.GAE/J→CloudWatch
  CloudWatchAPIのListMetricsを叩いて、参照可能な監視情報一覧を取得(json)。
 2.GAE/J
  参照する情報を指定。
 3.GAE/J→CloudWatch
  CloudWatchAPIのGetMetricStatisticsを叩いてインスタンスの情報を取得(json)。
 4.GAE/J
  取得した情報を表示。

【項目の説明】
  MeasureName
  参照する監視内容。
    CPU使用率、ネットワークI/O、ディスクI/O。
  DimensionName
    監視対象の分類。
    インスタンスIDごと、イメージIDごと、インスタンス種別ごと。
  DimensionValue
    監視対象の詳細。
    インスタンスID、イメージID、インスタンス種別。
  From
    参照開始日時。
  To
    参照終了日時。
  Period
    抽出する間隔(秒)。
    60なら1分ごとのデータ。
    3600なら60分ごとのデータ。

【使用例】
  1.Amazon CloudWatch on GAE/J (beta) > Menu > CloudWatch > Control
  2.-MeasureNameでCPUUtilizationを選択。
  3.-DimensionNameでInstanceIdを選択。
  4.-DimensionValueでi-b3ac89daを選択。
  5.Fromを2009-06-22T00:00にセット。
  6.Toを2009-06-24T00:00にセット。
  7.Periodを3600にセット。
  8.GetStatisticsMetricsボタンをクリック。
  9.結果テーブルのタイトル行をクリックするとソート可能(TinyTable JavaScript Table Sorterを利用)

  ※参照可能なデータが無い場合や表示データ上限数を超えている場合などは「Error!」と表示される。

【リンク】
  Amazon CloudWatch
  AWS Management Console(EC2)
  TinyTable JavaScript Table Sorter - 2.5KB

GAEでEC2インスタンスを管理する

 
 次に、EC2インスタンスの起動や終了をGAEに通知する方法について説明したいと思う。まずは、EC2インスタンス作成から。

【EC2インスタンスを作成】

 【初期操作】
  AWS Management Console等からAWSのデフォルトAMIを指定して新規インスタンスを作成。
    ↓
   インスタンスに接続してカスタマイズ。
    ↓
   AMIをS3に登録・保存
    S3に保存することで次回からも同じ設定のインスタンスを使用できる。

 【Amazon EC2のAMIをS3に登録する】
    インスタンスへsshして保存用イメージファイル(AMI)を作成→S3へアップロード→AMI登録

   * 保存イメージファイル(AMI)作成
     /mntディレクトリに移動して「ec2-bundle-vol」を実行。

    #cd /mnt/
    #ec2-bundle-vol -d /mnt --privatekey /path/to/pk-XXXX.pem --cert /path/to/cert-xxxx.pem --user YOUR-ACCOUNT-NUMBER --fstab /etc/fstab

    ** pk-XXXX.pem: Secret Access Key File
    ** cert-xxxx.pem: X.509 Certificate File
    ** YOUR-ACCOUNT-NUMBER: Account Number

    * S3へアップロード
    「ec2-upload-bundle」コマンドをバケット名を指定して実行。

    #ec2-upload-bundle --bucket /path/to/BUCKET_DIRECTORY_NAME --manifest image.manifest.xml --access-key XXXX --secret-key xxxx

    ** BUCKET_DIRECTORY_NAME: S3側のBucket Directory
    ** XXXX: Access Key ID
    ** xxxx: Secret Access Key

    * AMI登録
     インスタンスからログアウトして、「ec2-register」を実行。

     C:\>ec2-register /path/to/BUCKET_DIRECTORY_NAME/image.manifest.xml

【起動時にGAEに伝える仕組み】

 EC2インスタンスの起動終了時に、GAEのWebサービスプロバイダに対してステータスを通知することで、GAE上で利用可能なEC2インスタンスを管理することができるようになる。

  【仕組み】
   インスタンス起動時のnetworkが立ち上がる箇所で、自身のエンドポイント(ホスト名、インスタンスID)をec2nodeに登録するスクリプト(post_endpoint_to_gae.sh)を実行。
   インスタンス終了時にec2nodeから登録を削除するスクリプト(delete_endpoint_from_gae.sh)を実行。削除キーはインスタンスID。

  【/etc/rc.d/ini.d/networkの変更】
   インスタンス起動・終了時にエンドポイントの登録・削除を行うための設定。

    startコマンド最終行に以下を追加。(エンドポイント登録用)
     /bin/sleep 5
     /path/to/post_endpoint_to_gae.sh

     stopコマンド先頭行に以下を追加(エンドポイント削除用)
      /path/to/delete_endpoint_from_gae.sh
      /bin/sleep 20
      ※sleepをかけないとdelete_endpoint_from_gae.shの実行前にネットワークが切断されてしまい削除できない。

【エンドポイント登録・削除用スクリプト】

    登録(post_endpoint_to_gae.sh)
     エンドポイント情報を取得し、ec2nodeへPOSTする。
     「http://169.254.169.254/latest/」はAWS側で用意されているインスタンスID等の問い合わせ先。



     #!/usr/bin/php
      <?php
        $publicHostname = trim(file_get_contents("http://169.254.169.254/latest/meta-data/public-hostname"));
        $instanceId = trim(file_get_contents("http://169.254.169.254/latest/meta-data/instance-id"));
        $status = "IDLE";

        $data = array(
            "ec2record" =<
                  array(
                      "ec2node" => "{$instanceId}",
                      "endpoint_url" => "{$publicHostname}",
                      "status" => "{$status}"
                  )
            );

        $json = json_encode($data);

        $url = "http://www.example.com/ec2node";
        $options = array(
          "http" => array(
               "method" => "POST",
               "header" => "Content-type: text/plain",
               "content" => $json
            )
        );

        $context = stream_context_create($options);
        $fp = fopen($url, "r", false, $context);
        fpassthru($fp);
        fclose($fp);
        exit("\n");
       ?>


     削除(delete_endpoint_from_gae.sh)
      インスタンスID(削除キー)を取得し、ec2nodeへDELETEする。

      #!/usr/bin/php
      <?php
        $instanceId = trim(file_get_contents("http://169.254.169.254/latest/meta-data/instance-id"));
        $url = "http://www.example.com/ec2node?ec2node={$instanceId}";
        $options = array(
          "http" => array(
               "method" => "DELETE",
               "header" => "Content-type: text/plain"
            )
        );

        $context = stream_context_create($options);
        $fp = fopen($url, "r", false, $context);
        fpassthru($fp);
        fclose($fp);

        exit("\n");
      ?>


 
© 2006-2015 Virtual Technology
当サイトではGoogle Analyticsを使ってウェブサイトのトラフィック情報を収集しています。詳しくは、プライバシーポリシーを参照してください。