2012年2月1日水曜日

Slim3 Source Code Reading #6

今日はSlim3 Source Code Reading #6でした。

今日は、FileUploadの辺りを読む予定でしたが、
最近Slim3で開発を始めたので、それに対する疑問を聞いてみたり、BlobstoreAPIの
使い方を説明したりしました。

あとは、最近Google App EngineのアプリケーションをSlim3を使って
開発しているので、その時に疑問に思ったことを聞いてみたりしました。
先週と同じく、言いだしっぺの2人だったので、すぐ脱線してしまいました。

ちゃんと、FileUploadの部分も読みました。

が、HTTPのリクエストに対する知識がないと、意味不明な部分もあったりして、
Chromeのデベロッパーツールでhttp headerの中身をみたりしました。
個人的には十分な内容だったと思います。

では、一つ目のBlobstoreAPIを利用したファイルのアップロードについては、
以下の手順でファイルをBlobstoreに格納します。

  1. アプリケーションのファイル選択画面を表示する。
    1. ここで、POST先のURLはBlobstoreService#createUploadURL(url)を呼び出して、BlobstoreAPIで利用するURLを指定する。
    2. 引数のURLはBlobstoreにデータを格納後にリクエストされるURLを指定する。
  2. アップロード後に呼び出されるURLで、BlobstoreService#getUploadsを呼び出し、Blobstoreに格納したデータのBlobkeyを取得する。(複数ファイルに対応しているため、戻り値がMap<String, List<BlobKey>>の型になっている。)
    1. この時取得したBlobKeyは忘れてはいけない。(BlobKeyで格納しているデータにアクセスするため、BlobKeyを参照できないとデータを参照する事も、削除することもできなくなる。※ただし、admin consoleからなら削除可能)
  3. BlobKeyを引数にBlobstoreService#serve()を呼び出すことで、HttpResponseにBlobstoreに格納されているデータを書きこんでくれる。
    1. 画像であれば、<img>タグのsrcに指定することで、画像が表示される。
    2. 動画であれば、<video>タグのsrc
    3. 音声であれば、<audio>タグのsrc
  4. 同じく、Blobkeyを引数にBlobstoreService#delete()を呼び出すことで、Blobstoreのデータを削除する。ただし、Entityに格納している場合は、Entityの中身(BlobKey)を読みだした後に削除しないと、BlobKeyが参照できないのでdeleteできなくなる。
ちなみに、私のGoの本を参考にサンプルプログラムを作りましたが、(私だけかもしれませんが)意外と作れます。(Go→Javaができたという事は、その逆もまた可能なはず)

BlobstoreAPIはファイルサイズが最大32Mバイトまでとなっていますので、
大きなデータは格納できません。

脱線した話として、

  1. 自作のControllerに親クラスを作ったりするかどうか。
    1. これはやる。Sessionなど、全体に渡って取得する場合はやる。
  2. データストアから読みだした後、表示用に編集した値を保持するクラスをどこに作るか。
    1. 特にルールはないので、どこでもいいんじゃないか。
    2. Modelで編集用のデータを返すメソッドを定義する
  3. Keyのnameに名前を格納しておくと結構いい感じ。
    1. エンティティを読み出すのとKeyのみ読み出すのとでは、コスト的に7分の1
  4. とりあえず、良く使うデータはmemcacheに突っ込んどけば良いんじゃないかな?
    1. 更新頻度の問題がある。
      1. インスタンスが停止する場合。→所詮その程度なので安く収まるのでは?
      2. 結局は運用してみないとわからない。
  5. 課金設定にしたら、9ドル/月
    1. 最大9ドル/日と勘違いしていた。(1日9ドルだと月2万以上…)
    2. 無料枠を使い切る→9ドルの枠でがんばる→枠を越えたら…。
  6. Javaで作ったアプリケーションの最初の起動は遅い…
    1. Javaは初回のアクセス(インスタンスを起動する)時は一呼吸あるけど、Goだとそれはない。
最後にFileUploadの話題

  1. FrontControllerで、Content-Typeを確認して、"multipart/"で始まるかどうかをチェック。
    1. "multipart/"の場合、ファイルがアップロードされてきた判定。
      1. この場合、MultipartRequestHandlerのインスタンスが生成される。
    2. その他は、普通の(?)リクエスト
  2. MultipartRequestHandler#handleで、FileUploadクラスのインスタンスを生成する。
    1. FileUploadクラスで、ファイルのサイズの制限設定を読み込む。
      1. 現時点のblankプロジェクトの設定はコメントアウトされているので初期値が使われる。
      2. 初期値は-1なので、「制限なし」
    2. この中で、いろいろな処理をして、FileItemクラスのインスタンスを生成する。
      1. MultipartRequestHandlerはかなり頑張ってます。
      2. ちなみに、ファイルを読み込む時のバッファは8 * 1024(8192)バイトです。ファイルが大きいと実行されるステップ数も増えます。
    3. 最終的にControllerでFileItem item = requestScope("formFile")の1行でファイルが取得できる。
  3. Serviceでアップロードされたデータを格納する処理を実行すれば、Datastoreにファイルが格納される。
個人的に思うこと。

  1. BlobstoreAPIを利用したほうが良い。
  2. ただし、BlobstoreAPIはファイルの中身をチェックできない
    1. UploadはAPI用のリンクへのリクエスト
    2. downloadは直接HttpResponseに書きこまれる
  3. したがって、中身をチェックしたい場合は、Slim3のFileUploadを使うほうが良い。
  4. あと、課金状況で判断するのも必要かもしれない。
  5. 32Mバイトを超えるファイルを格納する可能性があれば、BlobstoreAPIは使えないのでSlim3のFileUpload一択。(※ただし、そんなに大きなファイルを格納しようと思うと、30秒の時間制限を考える必要がある)

次回の#7は2/7(火)19:00〜の予定です。
少人数で行っているので制限が厳しいですが、ご参加をお待ちしております。

2012年1月29日日曜日

JQueryの初歩

最近、「コアjQuery+プラグイン/jQuery UI開発技術実践技法」という本を
読んでいるのですが、

この本です。


jQueryの基本的な部分をまとめておこうと思います。

使い方としては、jQueryのソースコードをダウンロードしてきて、
サーバのディレクトリに置いておき、それをロードして使えば良いのですが、
Googleが提供しているJavaScriptAPIを利用すれば、指定したバージョンの
jQueryのソースコードをGoogleからダウンロードして使う事ができます。

例えば、以下のようになります。

<script src="http://www.google.com/jsapi">
</script>
<script>
 google.load("jquery", "1.7");
 google.load("jqueryui", "1.8");
</script>
<script>
 $(function() {
  $("#datepicker").datepicker({
   dateFormat: 'yy/mm/dd'
  });
 });
</script>

これは、HTMLのidにdatepickerと指定されている要素を取得し、
そこに日付入力ダイアログを表示し、入力後、要素に反映するというコードです。

headタグの所に、上記のコードを入れておくと、ブラウザにページが表示された時には
日付入力ダイアログが表示されるようになっています。

まずは、jQueryを使う上でよくわからないのが、$()です。
これは、「要素の配列を返す関数」だそうです。引数に特定の文字列を指定すると、
ページの中に配置されている要素から条件に一致する要素の配列を返してきます。

例えば、
$("div.header")
と、指定すると、ページの中にあるdiv要素でclass="header"と指定されている要素が
取り出されます。したがって、
$("div.header").hide()
とすると、divタグのclass="header"の要素が非表示になります。

次に、
$("div#header")
と指定すると、ページの中にあるdiv要素でid="header"と指定されている要素が
取り出されます。したがって、
$("div#header").hide()
とすると、divタグのid="header"と指定されている要素が非表示になります。

最後に、
$("div a")
と指定すると、divタグの中に含まれるaタグの要素が取得されます。
$("div a").style.backgroundColor="#FF0000"
とすると、divタグの中に含まれるリンクの背景が赤くなります。

という事で、まとめると、

  • $()関数の中に指定する文字列はページ内の要素を取得する条件を指定する。
  • $()関数は条件に一致する全ての要素を取得する。
  • "."で区切ると、classの条件、"#"で区切るとidの条件、" "(空白)で区切ると要素の中に含まれる要素が取得される。
  • "."、"#"、" "は混在(複数の条件を指定)して使うことができる。基本AND条件となる。


これを利用すると、マウスの動きに連動して背景を変えるなんてことも簡単にできるようになります。

他にも、$()関数の引数に関数を渡すと、「ドキュメント・レディ」ハンドラと
言って、JavaScriptでいうと、window.onloadで実行する処理に相当するものだそうです。
したがって、headタグの中のscriptタグ内で、
$(function () {
    $("input#name").hide();
});

なんて処理を書いておくと、inputタグのid="name"の要素を初期状態を非表示にできます。

便利なので、しっかり使ってみたいと思います。

2012年1月25日水曜日

Slim3 Source Code Reading #5

今日は、Slim3 Source Code Reading #5の日でした。

今回も@sinmetalさんと2人での開催となりました。
内容は、

  1. 私の仕事でGoogle App Engineを使ったアプリケーションを作ることになったのでその質問
  2. Memcache.javaを読み進める。
  3. UserServiceを使った認証についての雑談
  4. org.slim3.datastoreパッケージのCreationXX.javaについて

でした。

1.は単純に仕事で使う事になった(した)ので、アプリケーションを開発している話を少ししました。

2.が本題のMemcacheの機能です。
方向性として、Memcacheの存在確認、MemcacheへのGet、Putを中心に読みました。
(今、deleteも確認しましたが、deleteもラッパーでした)
読み進めた所、Memcacheの存在確認はcontains(key)、Memcacheへの登録はput()を
それぞれ呼び出しますが、中身は、MemcacheServiceのメソッドを呼び出している
だけでした。

しかし、Getだけはそのままではなく、MemcacheDelegate.javaの214行目から
始まる、getInternal()でいろいろ処理されています。
MemcacheServicePb.MemcacheGetRequestクラスのインスタンスと、
MemcacheServicePb.MemcacheResponseクラスのインスタンスを生成し、
233行目のmakeSyncCall()を呼び出します。

このmakeSyncCall()は中で、ApiProxy.makeSyncCall()を呼び出し、
(恐らくSDKよりも下の)APIを呼び出し、Memcacheに登録しています。
MemcacheはKey-Value形式で、しかも、Valueはbyteデータを格納する事に
なっていますので変換処理が必要ですが、オブジェクトをbyteデータに変換する処理も
このあたりで行われているようです。

なぜ、このようにGetだけ、ただのラッパーではなかったのかは、分かりませんでした。
(恐らく、AppEngineのAPIを実行すると処理が遅いとか、無駄な待ち時間がかかるなど
あるのだろうと判断して終わらせました)

その次は、Memcacheの次に読み進めるのをどれにするか選んでいた時に、
ユーザ認証の話になり、私が、UserServiceのすばらしさを一方的に話した感じでした。

利点として、

  1. Googleの認証機構に全てお任せできる。
  2. 認証済みであれば、ユーザ情報が取れるので、利用できる。(Mail APIによる通知や、ユーザの識別、データの管理、アクセス管理など)
  3. データストアに(多分)SessionIDなどを登録する必要がなくなる。


1つ目は自力で認証処理を作る手間が省ける。かつ、(多分)自分よりも質の良いセキュリティが確保できる。しかも、Gmailが見れる状態(すでにログイン済み)だと、
アプリケーションの認証の処理が不要な場合もでてくる(入力の手間が減る)。
2つ目は自力認証の必要が無いので、ユーザ情報の有無で表示の制御ができる。何らかの処理の完了通知などにも利用できる。限られたユーザのみ見せるという機能も簡単に作れる。しかも、認証情報をデータストアに残す必要もないので、データストアのオペレーションの削減に繋がる。
3つ目はSlim3だとSession情報をデータストアに格納している(?)ということなので、
cronなどで、データストアの不要な情報を削除する処理を実行しなければならないが
その手間が省ける。データストアのオペレーションにも影響する。

ということが考えられます。

最後に4つ目のCreationXX.javaについてですが、org.slim3.datastoreパッケージに
CreationUser.java
CreationDate.java
CreationEmail.java
ModificationUser.java
ModificationDate.java
ModificationEmail.java

というファイルが存在しています。
このファイルはAttributeListenerインターフェイスを実装しているのですが、
何のためにあるのかよくわからないファイルでした。

が、実は(ドキュメントにも書かれているのですが)、アノテーションを設定すると、
ModelMetaで、データストアにPutする前に、現在の日付や、ユーザ情報を自動的に
取ってきて、プロパティに設定してくれます。
(Serviceで設定する必要がありません)

    @Attribute(listener = CreationUser.class)
    private User user;

上記のようにすることで、勝手に登録されます。
CreationXXとModificationXXの違いは、
CreationXXはすでに設定済みかどうかを判定し、設定されていない場合のみ登録されます。
ModificationXXの場合は、内容にかかわらず、値を上書きします。

次回は1/31(火) 19:00〜で、util系を読み進める予定です。

2012年1月22日日曜日

本格アプリを作ろう!Androidプログラミングレシピを読みました。



本格アプリを作ろう!Androidプログラミングレシピを読みました。
この本はインプレスジャパンから発行されている書籍です。

内容は、Android2.3(Gingerbread)で動作するソースコードを元に
記述されています。翻訳担当の方が動作確認とAndroid4.0での動作についても
注釈として記述されているため、最新のAndroidに対するサポートもされています。

概要は以下の内容です。

  1. Androidを始めよう
  2. ユーザインターフェイスのレシピ
  3. 通信ネットワークのレシピ
  4. 端末ハードウェアやメディアと連携するレシピ
  5. データ永続化のレシピ
  6. Androidシステムと連携するレシピ
  7. ライブラリを活用するレシピ
  8. 付録
    1. スクリプト環境の構築
    2. Android NDKで性能を高める
    3. アプリ設計のガイドライン
本書は、基本的な所から、大体のデバイスの使い方、アプリケーションの連携が
説明されています。
文章量は多く感じられますが、詳細に説明されているため、じっくり読んでみる方が
良いと思います。また、レシピ形式で細かく分けられているため、ゆっくり進める事も
可能です。ソースコードもほぼ掲載されているので、写経も可能です。

1点気になったのはリソースの解放処理が省略されている事です。
Android2.3だと不要なのか、本来の機能の説明のために省略したのか
分かりませんが、画像とか、ネットワークリソースの解放の記述が無かったのが
気になりました。

この書籍は今回は献本して頂きましたが、この書籍は購入しても損はしないと
思いました。

Google+に投稿したコメント
https://plus.google.com/u/0/114183076079015753160/posts/ULQoezrSWN4
https://plus.google.com/u/0/114183076079015753160/posts/UPiADfBJ1TG

オープンセミナー2012@広島で発表してきました。

オープンセミナー2012@広島で講師として発表してきました。

今回はQueryとTransactionについてお話ししました。
資料とソースコードは公開してありますので、興味がありましたらごらんください。

資料
https://docs.google.com/present/edit?id=0ASEzu8hKOt7rZHJwbjl2NV83MWRreDV6dmMy

デモ用ソースコード
https://github.com/tyokoyama/osh2012demo

今回はQueryとTransactionなのでそんなに話題として
少ないかな?と思ったのですが、
よく考えると、Queryを語るにはIndexの前提も必要で、
Transactionの話をしようとすると、エンティティグループの前提も
必要だったので、当日追加してみたのですが、早口でお話ししないと
いけないぐらいの内容になってしまいました。
今思えばTransactionは外せば良かったかもしれません。
しかも、Go言語と言いながらほとんどGoogle App Engineの仕組みの話になってしまったような。

Queryについても文字列のInequalityFilterに関しての動作も
今回のデモ用のアプリケーションだと良い例ではなかったのかもしれません。
住所録の方が良かったかも。前方一致の検索結果になるのを見せるには
そちらの方が説明しやすい結果もでたかもしれないし。

他の講師の方の話も聞きたかったのですが、資料の調整と
すぐに反省モードに入ってしまったので、あまり聞けていません。

その後、懇親会で、宇品港(広島港)の前の公園の中にある、テントでかきを食べる
かき小屋で懇親会をしました。食べ物は精算所で先に買って、(追加する時も精算所に行く)飲み物は自販機で買うスタイルで、テント小屋の中で炭焼きで海鮮、肉類を焼いて食べました。

その後、@Toro_kunの家に行って、たこ焼きを初めて焼きました。そして、食べました。
深夜まで色々な真面目な話をして、満足して帰りました。
ありがとうございました。

2時から@soudai1025さんを車で送り届けて、朝5時に無事家に付きました。

次は第12回中国GTUG勉強会に出席の予定です。

2012年1月18日水曜日

Slim3 Source Code Reading #4

今日はSlim3 Source Code Reading #4を開催しました。
参加者が私と@sinmetalさんの2人だったので、当初想定していたメンバーになりました。

今回は、

  1. EntityGroupとQueryについての前提知識の共有(というよりは、前回間違った説明があったのでその辺りの共有)
  2. Transaction、GlobalTransactionのソースコードを読み進めました。
  3. EntityGroupに関する実装上の話題。構成とか。Ancestorクエリにするケースなど。
  4. sinmetalさんの「サービス単体でテストコードを動かすと、Cross Group Transactionが正常に動作するのに、プロジェクト全体のテストを行うとテストに失敗する件」

を行いました。

Transactionについては、Datastore.javaにあるDatastore#beginTransaction()を読み進めました。
ただ、この中身は、Google App EngineのTransactionAPIを利用しているだけで、Slim3で特別何かをしているということはありませんでした。
ただし、TransactionのOptionでSystem.getProperty("slim3.useXGTX")の結果がTrueである場合、Cross Group Transactionが有効になります。
System.getProperty("slim3.useXGTX")はappengine-web.xmlに記述してある値を取得します。※現時点ではデフォルトでは記述がないので、値はfalseです。

GlobalTransactionに関しては、かなり大変な事をしてあるので、概要だけ説明を書いておきます。
(sinmetalさんが検証コードを書いて試してくれるという事なので、その結果を待ちます)
実際、GlobalTransactionが必要になる事はこれからは少なくなってくると思いますので
(現時点で既にDeprecated)Cross Group Transactionを利用した方が良いと思います。

具体的にはどうしているかと言うと、

  1. トランザクションを開始する。Datastore.beginGlobalTransaction();
  2. エンティティをGet or Putする時、Lock用のキーMapに、現在のキーの祖先が存在しているかを確認する。存在していれば、例外をスローする。ただし、存在していても、キーのTimestamp+30秒を超えていれば無視する。
  3. GlobalTransaction#Get or Putを呼び出す。その時、対象のエンティティの祖先のキーを取得し、Lock用のMapに退避しておく。キーにはTimestampを保存しておく。また、Getの場合はKeyのみ、Putの場合はKeyとEntityのペアをJournal用のMapにPutする。
  4. トランザクションをCommit or Rollback(GlobalTransaction#commit、rollback)する。この時、GlobalTransactionの場合は、Journal用のMapからGet用のKeyを削除し、Put用のEntityをslim3.GlobalTransactionカインドにPutする。Get用のKeyにはEntityが登録されていないので、Entity==nullであれば、Get用のKeyと判定する。ただし、単一EntityGroupのみの場合(Lock用のMapが空)はLocalTransactionと判断しそのままCommitする。
  5. GlobalTransactionの場合、TaskQueueにキューを登録する。登録するキューはGlobalTransactionServletへのリクエストとして登録する。
  6. GlobalTransactionServletで、RollForwardトランザクションの場合、Commitされた場合は、GlobalTransaction用のカインドにある、KeyとEntityのペアを取り出し、本来のカインドのエンティティにPutする。Rollbackトランザクションの場合は、カインドから対象のエンティティを削除する。

大体の処理は上記のようになっています。
正確な処理については、大変ですが、ソースコードを読んでみてください。

最後にsinmetalさんから相談された、「単体テストでHRDのテストが動作しない件」ですが、見せていただいたテストコードには、setUp()でSystem.setProperty("slim3.useXGTX", "true");の
記述がありましたが、この設定を反映するAsyncDatastoreDelegateが何度もインスタンスが生成されるようであれば良いのですが、メンバとして保持している、
Datastoreクラスがstaticであるため、実行開始後、1回しか設定を読み込まないようになっているようです。したがって、System.setProperty()が呼び出される前にDatastoreクラスのメソッドが呼び出されてしまうとその時点でデフォルトのトランザクションオプションが反映されてしまって、System.setProperty()が意味をなさなくなってしまいます。

これについても検証コードの結果を待ちましょう。

ただ、この後にも話をしたのですが、開発環境HRDのオプションの遅延は、
かなり大きいので、Queryのテストをしたい時にまでHRDの遅延が発生すると
テストが通らない問題に直面してしまいます。
一つは、遅延確率を設定するオプションを0にして遅延しなくするという方法を
提案しましたが、テストクラスの本数が多くなってくると面倒という問題があるようです。
また、Thread.sleep(2000)とか入れてみるという提案もしたのですが、
これも同様に本数が増えてくると、テストの実行時間が長くなってしまうので
全体のテストを行う時に問題になる。ということでした。

今日は、時間も長めにやりましたが、内容も濃かったです。
疲れました。

2012年1月14日土曜日

Go言語プログラミング入門の正誤表を更新しました。

Go言語プログラミング入門正誤表に追記しました。
http://takashi-yokoyama.blogspot.com/2011/12/go.html

appengine/datastoreパッケージのPropertyLoadSaverについて、
誤記があったので、詳細に説明をしておきたいと思います。

PropertyLoadSaverインターフェイスのドキュメントです。
http://code.google.com/intl/ja/appengine/docs/go/datastore/reference.html

書籍では、「データを登録した状態でないとプログラムが応答を返さなくなる」という
事が書かれているのですが、これは、サンプルコードを作成している時の試行錯誤の
途中段階の誤認識をした状態で記述をしていました。

実は、PropertyLoadSaverインターフェイスのLoadメソッド、Saveメソッドの定義は
以下の通りです。(書籍のサンプルコードから抜粋したものです)
func (g *Guest) Load(c <-chan datastore.Property) os.Error {
func (g *Guest) Save(c chan<- datastore.Property) os.Error {
どちらもchannelが引数になっています。このchannelについての説明はP.63付近に
少し記述をしていますが、CriticalSectionと同じで、自分のスレッドをブロックします。
ブロックが解除されるのはchannelに値がセットされた時か、close(c)された時と
なります。LoadStruct()、SaveStruct()はメソッド内でchannelの処理を行っているため、呼び出せば構造体に値がセットされ、channelの処理も適切に行ってくれます。

書籍のプログラムだと、Saveメソッドの方では、何もしていないため、channelのブロックが解除されないことになり、現時点ではプログラムが応答を返さなくなってしまいます。

したがって、Load()の方でも、同様に「何もすることがない」からといって何もしないと
プログラムが応答を返さなくなってしまいます。